Skip to content

多路取值

a-calc 3.x 引入了多路取值(Fallback)功能,允许你在表达式中指定多个变量路径,按优先级依次尝试,返回第一个非空值。这在处理不确定数据源或需要默认值的场景中非常实用。

基本语法

使用管道符 | 分隔多个变量路径,需要用括号包裹:

(变量1 | 变量2 | 变量3 | ...)

系统会从左到右依次尝试每个路径,返回第一个非空值。

基础用法

简单取值

javascript
import { calc } from 'a-calc'

// 第一个值为 null,自动取第二个
calc('(a | b) + 1', { a: null, b: 10 })  // '11'

// 第一个值为 undefined,自动取第二个
calc('(a | b) + 1', { a: undefined, b: 5 })  // '6'

// 第一个值有效,直接使用
calc('(a | b) * 2', { a: 3, b: 100 })  // '6'

// 三个路径
calc('(a | b | c) + 1', { a: null, b: undefined, c: 7 })  // '8'

复杂路径

支持对象属性路径、数组索引等复杂路径:

javascript
// 对象属性路径
calc('(obj.a | obj.b) * 2', {
  obj: { a: null, b: 4 }
})  // '8'

// 数组索引路径
calc('(arr[0] | arr[1]) + 5', {
  arr: [null, 10]
})  // '15'

// 复杂嵌套路径
calc('(data.list[0].value | data.default) * 3', {
  data: {
    list: [{ value: null }],
    default: 6
  }
})  // '18'

与普通变量混用

多路取值可以与普通变量自由组合:

javascript
// 多路取值与普通变量混用
calc('a + (b | c) * d', { a: 1, b: null, c: 2, d: 3 })  // '7'

// 多个多路取值组合
calc('((a | b) + (c | d)) * (e | f)', {
  a: null, b: 2,
  c: undefined, d: 3,
  e: null, f: 4
})  // '20'

空值判断

默认情况下,nullundefined 被视为空值。你可以通过配置自定义空值判断逻辑。

默认行为

javascript
// null 和 undefined 是空值
calc('(a | b) + 1', { a: null, b: 10 })      // '11'
calc('(a | b) + 1', { a: undefined, b: 5 })  // '6'

// 0 默认不是空值
calc('(a | b) + 1', { a: 0, b: 10 })  // '1'

// 空字符串默认不是空值(但转数字会失败)
calc('(a | b) + 1', { a: '', b: 10, _error: 'ERR' })  // 'ERR'

使用 _empty_values

通过 _empty_values 配置,可以自定义哪些值被视为空值:

javascript
// 将 0 视为空值
calc('(a | b) + 1', {
  a: 0,
  b: 10,
  _empty_values: [null, undefined, 0]
})  // '11'

// 将空字符串视为空值
calc('(a | b) + 1', {
  a: '',
  b: 5,
  _empty_values: [null, undefined, '']
})  // '6'

// 将 NaN 视为空值
calc('(a | b) + 1', {
  a: NaN,
  b: 7,
  _empty_values: [null, undefined, NaN]
})  // '8'

// 多个空值类型
calc('(a | b | c) + 1', {
  a: null,
  b: 0,
  c: 10,
  _empty_values: [null, undefined, 0, '']
})  // '11'

使用 _empty_check

通过 _empty_check 配置,可以自定义空值判断函数,实现更灵活的逻辑:

javascript
// 负数视为空值
calc('(a | b) + 1', {
  a: -1,
  b: 10,
  _empty_check: (value) => value < 0
})  // '11'

// 特定字符串视为空值
calc('(a | b) + 1', {
  a: 'N/A',
  b: 10,
  _empty_check: (value) => value === 'N/A' || value === null || value === undefined
})  // '11'

// 根据路径和值综合判断
calc('(price | defaultPrice) * quantity', {
  price: 0,
  defaultPrice: 100,
  quantity: 2,
  _empty_check: (value, path) => {
    // 价格为 0 视为空值
    if (path.includes('price')) return value <= 0
    return value === null || value === undefined
  }
})  // '200'

优先级

_empty_check 的优先级高于 _empty_values。如果同时配置了两者,只会使用 _empty_check

与其他特性组合

与格式化组合

javascript
// 带格式化
calc('(a | b) + 0.123 | =2', { a: null, b: 1 })  // '1.12'

// 千分位格式化
calc('(a | b) * 1000 | ,', { a: null, b: 1234 })  // '1,234,000'

// 百分比格式化
calc('(a | b) | %', { a: null, b: 0.5 })  // '50%'

与单位计算组合

javascript
// 带单位
calc('(a | b) + 1', {
  _unit: true,
  a: null,
  b: '5元'
})  // '6元'

// 单位转换
calc('(price | defaultPrice) | !ua:元', {
  price: null,
  defaultPrice: 100
})  // '100元'

与解析模式组合

javascript
// space 模式
calc('(a | b) + 1', {
  _mode: 'space',
  a: null,
  b: 8
})  // '9'

// space-all 模式
calc('(a | b) + 1 | =2', {
  _mode: 'space-all',
  a: null,
  b: 8
})  // '9.00'

与函数组合

javascript
// 函数参数使用多路取值
calc('sqrt((a | b))', { a: null, b: 16 })  // '4'

// 多路取值结果作为函数参数
calc('max((a | b), (c | d))', {
  a: null, b: 10,
  c: undefined, d: 5
})  // '10'

错误处理

当所有路径都是空值时,会抛出错误:

javascript
// 所有路径都是 null,使用 _error 返回默认值
calc('(a | b | c) + 1', {
  a: null,
  b: null,
  c: null,
  _error: 'EMPTY'
})  // 'EMPTY'

// 所有路径都不存在
calc('(a | b) + 1', {
  _error: 'EMPTY'
})  // 'EMPTY'

// 不设置 _error 会抛出异常
calc('(a | b) + 1', { a: null, b: null })
// Error: 多路取值失败,所有路径均为空值: a | b

实际应用场景

场景一:多数据源优先级

javascript
// 优先使用用户设置,其次使用系统配置,最后使用默认值
const data = {
  userConfig: { precision: null },
  systemConfig: { precision: 4 },
  default: { precision: 2 }
}

calc('(userConfig.precision | systemConfig.precision | default.precision)', data)
// '4'

场景二:价格展示

javascript
// 优先展示促销价,没有则展示原价
const product = {
  salePrice: null,
  originalPrice: 199
}

calc('(salePrice | originalPrice) | =2,', product)
// '199.00'

场景三:统计数据处理

javascript
// 处理可能缺失的统计数据
const stats = {
  current: { value: null },
  previous: { value: 100 },
  baseline: 0
}

calc('((current.value | previous.value | baseline) - baseline) / baseline * 100 | =2%', {
  ...stats,
  _empty_values: [null, undefined]
})
// '100.00%'

场景四:表单默认值

javascript
// 表单字段:用户输入 > 历史值 > 默认值
const form = {
  input: '',
  history: 50,
  default: 10,
  _empty_values: [null, undefined, '']
}

calc('(input | history | default)', form)
// '50'

注意事项

  1. 括号必需:多路取值语法必须用括号包裹 (a | b)
  2. 管道符含义:在多路取值上下文中,| 表示备选路径,而非格式化分隔符
  3. 空值概念:默认只有 nullundefined 是空值,其他值(如 0''false)默认不是空值
  4. 性能考虑:多路取值会按顺序尝试每个路径,路径过多可能影响性能
  5. 错误传播:所有路径都是空值时会抛出错误,建议配合 _error 使用

基于 MIT 许可发布