VueUse useArrayJoin:响应式 Array.join 组合式函数完全指南
VueUse useArrayJoin:响应式 Array.join 组合式函数完全指南
useArrayJoin 是 VueUse 中一个轻量而实用的响应式工具函数,它将原生 Array.prototype.join 的能力封装为可响应式求值的组合式函数:数组元素、分隔符乃至数组本身都可以是 ref、getter 函数或普通值,任何变化都会自动触发结果重新计算。本文以 packages/shared/useArrayJoin/index.md 为主体,结合 index.ts 源码与 index.test.ts 测试用例,带你完整掌握它的用法、原理、边界行为与适用场景。
一句话理解:Reactive Array.join
useArrayJoin 的定位非常清晰,官方文档的标题就是 "Reactive Array.join"。它与 Vue 的 computed 深度绑定,核心作用是把一段依赖多个响应式来源的字符串拼接逻辑声明式地表达出来,而无需手动编写 watch 与更新逻辑。
完整实现只有一行核心表达式(见 index.ts):
export function useArrayJoin(
list: MaybeRefOrGetter<MaybeRefOrGetter<any>[]>,
separator?: MaybeRefOrGetter<string>,
): UseArrayJoinReturn {
return computed(() => toValue(list).map(i => toValue(i)).join(toValue(separator)))
}
其中 UseArrayJoinReturn 类型为 ComputedRef<string>。从源码可以看到三个关键设计点:
- 基于
computed:返回的是一个ComputedRef,而非一次性计算的结果,任何依赖变化都会触发惰性重算; - 双层
toValue:外层toValue(list)取出数组本身(可能是 ref 或 getter),内层.map(i => toValue(i))再逐元素解包,因此数组中每个元素都可以单独是ref或 getter; toValue(separator):分隔符同样支持响应式,这构成了"数组响应式 + 元素响应式 + 分隔符响应式"三重视图。
基本用法:三种典型调用方式
官方文档给出了三种具有代表性的使用场景,下面逐一展开并结合测试用例验证。
1. 与多个 ref 组成的数组配合
当数组中每个元素都是独立的 ref 时,useArrayJoin 会监听每一个元素的变化:
import { useArrayJoin } from '@vueuse/core'
const item1 = ref('foo')
const item2 = ref(0)
const item3 = ref({ prop: 'val' })
const list = [item1, item2, item3]
const result = useArrayJoin(list)
// result.value: foo,0,[object Object]
item1.value = 'bar'
// result.value: bar,0,[object Object]
此场景对应的测试用例见 index.test.ts:修改 item1 后,result.value 立即从 foo,0,[object Object] 变为 bar,0,[object Object]。注意这里 list 是普通数组,useArrayJoin 通过内部 toValue(list) 读取当前元素并解包,因此元素 ref 的替换即可驱动重算。
2. 与响应式数组(ref 包裹的数组)配合
数组本身是一个 ref 时,增删元素、整体替换都能触发更新:
import { useArrayJoin } from '@vueouse/core' // 更正:实际为 '@vueuse/core'
const list = ref(['string', 0, { prop: 'val' }, false, [1], [[2]], null, undefined, []])
const result = useArrayJoin(list)
// result.value: string,0,[object Object],false,1,2,,,
list.value.push(true)
// result.value: string,0,[object Object],false,1,2,,,,true
list.value = [null, 'string', undefined]
// result.value: ,string,
这个示例非常典型地展示了 Array.join 的原生语义在响应式环境中的完整保留(对应测试见 index.test.ts):
null与undefined会被转换为空字符串,因此结果中相邻元素之间出现"空槽"(连续的逗号);- 一维数组
[1]被展开为1,而二维数组[[2]]被进一步展开为2(join对嵌套数组递归调用toString); - 空数组
[]也被转换为空字符串; push新元素、整体=替换数组,都会触发result重新计算。
3. 与响应式分隔符配合
分隔符同样可以是响应式的,这是 useArrayJoin 相较原生 join 最灵活之处:
import { useArrayJoin } from '@vueuse/core'
const list = ref(['string', 0, { prop: 'val' }])
const separator = ref()
const result = useArrayJoin(list, separator)
// result.value: string,0,[object Object]
separator.value = ''
// result.value: string0[object Object]
separator.value = '--'
// result.value: string--0--[object Object]
对应测试见 index.test.ts。值得注意的是 separator = ref()(未赋值时值为 undefined):按 Array.join 的原生语义,undefined 分隔符会被默认替换为逗号 ",",这正是 result 初始值为 string,0,[object Object] 的原因——这是原生 join 行为的继承,而非 VueUse 的自定义处理。
深入源码:实现原理与类型设计
useArrayJoin 之所以如此精简,是因为它站在了 Vue 3.3+ 的 toValue API 之上。理解它需要看清三个层面:
MaybeRefOrGetter 参数设计
list: MaybeRefOrGetter<MaybeRefOrGetter<any>[]>
separator?: MaybeRefOrGetter<string>
- 外层
MaybeRefOrGetter<...[]>:list本身可以是普通数组、ref<数组>或返回数组的 getter 函数; - 内层
MaybeRefOrGetter<any>:数组内的每一个元素也都可以是 ref 或 getter; separator:可选参数,支持 ref 或 getter,缺省时走原生join的逗号默认值。
这样的类型签名意味着:list 中混合存放"普通值 + ref"甚至"返回值的函数"都是合法的,useArrayJoin 会在求值时统一解包。
为什么是 computed 而不是 watch
从源码可知,返回值是 ComputedRef<string>,这是 VueUse 工具函数最常见的形态之一:
- 惰性求值:只有读取
result.value时才计算,无读取则零开销; - 依赖自动追踪:计算过程中访问的所有响应式源(数组 ref、元素 ref、分隔符 ref)都会被记录,任一变化触发重算;
- 可组合性:
result可以继续作为其他computed、模板表达式或useArrayJoin本身的输入,参与更复杂的响应式链路。
与 VueUse Array 家族的关系
useArrayJoin 是 VueUse 的 Array 工具函数家族成员,与 useArrayMap、useArrayFilter、useArrayReduce、useArrayEvery 等共享同类的"响应式数组变换"设计哲学(见 packages/shared/index.ts 的导出清单)。它定义于 shared 包,并通过 packages/core/index.ts 的 export * from '@vueuse/shared' 被 @vueuse/core 重新导出,因此两种导入方式均可用:
import { useArrayJoin } from '@vueuse/core' // 常用入口
import { useArrayJoin } from '@vueuse/shared' // 按包细分导入
体积与副作用
- 根据 packages/export-size.json 与 packages/export-size.md,
useArrayJoin的打包体积约为 136 B,属于极轻量工具; - 源码 JSDoc 标注了
@__NO_SIDE_EFFECTS__(见 index.ts),向打包器声明该函数无副作用,便于 tree-shaking 与优化。
边界行为与注意事项
结合文档示例、源码与测试,useArrayJoin 有以下需要留意的行为特征:
| 输入情况 | 行为 | 依据 |
|---|---|---|
元素为 null / undefined |
转为空字符串,产生空槽 | 文档示例与 index.test.ts |
| 元素为嵌套数组 | 递归 toString 展开,如 [[2]] → 2 |
文档示例与测试 |
| 元素为对象 | 显示 [object Object](默认 toString) |
文档示例 |
元素为空数组 [] |
转为空字符串 | 文档示例 |
separator 为 undefined(如 ref()) |
回退到逗号 , |
文档示例与测试 |
| 数组长度 0 | 返回空字符串(继承原生 join) |
index.ts JSDoc |
需要强调的边界语义:
- 对象转字符串并不安全:对象元素默认输出
[object Object],若要展示可读内容,应先通过useArrayMap或map将对象映射为字符串,再交给useArrayJoin。 null/undefined转空字符串是原生join的语义,如果业务上需要保留占位符(如-),需先对数组做预处理替换。separator缺省/undefined时使用逗号:这属于原生行为继承,不要与 VueUse 自定义默认值混淆。- 解包是浅层的:
useArrayJoin只对数组元素做一次toValue解包,元素内部对象的属性变化不会触发重算;如需深度响应,应搭配useArrayMap+computed或使用ref包裹的对象值。
典型应用场景
useArrayJoin 适合任何"字符串拼接结果需要跟随数据自动更新"的场景,例如:
- 动态标签列表展示:将多个响应式标签(如选中项、筛选项)拼成一行文本;
- 配置项序列化:把一组响应式参数拼成 URL query、CSV 行或日志摘要;
- 状态面板摘要:将多个状态 ref 实时拼成可读的一句话;
- 列表文本化展示:配合
useArrayMap先转换再拼接,避免[object Object]问题。
小结
useArrayJoin 以极简的实现(约 136 B)完整复刻了原生 Array.join 的语义,并通过 computed + toValue 让"数组、元素、分隔符"三者全部具备响应式能力。掌握它,意味着你可以在模板与组合式函数中声明式地编写字符串拼接逻辑,无需手动管理 watch 与更新。若需阅读完整实现与验证用例,可继续查看 index.ts 与 index.test.ts。