☰
VueUse useArrayDifference 实战指南:响应式数组差集、自定义比较与对称差集
2026/10/5 14:30:44 网站建设 项目流程
  • 前端

【免费下载链接】vueuse

Collection of essential Vue Composition Utilities for Vue 3

项目地址:https://gitcode.com/gh_mirrors/vu/vueuse
点击查看免费下载

useArrayDifference是 VueUse 中用于响应式求两个数组差集的组合式函数,它把集合论中的**相对补集(A \ B)与对称差集(A △ B)**运算带进了 Vue 3 的响应式世界:只要传入的数组是 ref、reactive 数组或 getter 函数,结果就会自动跟随数据变化而更新。本文将以官方文档 packages/shared/useArrayDifference/index.md 为骨架,结合其源码实现 index.ts 与测试用例 index.test.ts,完整讲解它的三种调用形态、symmetric选项的底层机制,以及它在 VueUse 数组工具家族中的定位,读完后你可以在自己的项目中直接落地使用。

一、函数签名与核心概念

useArrayDifference是一个惰性计算的组合式函数:它本身不执行差集运算,而是返回一个ComputedRef<T[]>,只有当你访问.value时才会真正计算结果,并在依赖的数组变化时自动重新计算。

参数总览

参数类型必填说明
listMaybeRefOrGetter<T[]>是主数组(集合 A),可以传 ref、普通数组或返回数组的 getter 函数
valuesMaybeRefOrGetter<T[]>是被比较的数组(集合 B),同样支持 ref / 普通数组 / getter
key(形态一)keyof T否指定按对象哪个属性比较,等价于compareFn = (a, b) => a[key] === b[key]
compareFn(形态二)(value: T, othVal: T) => boolean否自定义比较函数,返回true表示两个元素"相等"
optionsUseArrayDifferenceOptions否配置对象,目前仅含symmetric选项

UseArrayDifferenceOptions的定义在 index.ts 中:

export interface UseArrayDifferenceOptions { /** * Returns asymmetric difference * @see https://en.wikipedia.org/wiki/Symmetric_difference * @default false */ symmetric?: boolean }

返回值类型为UseArrayDifferenceReturn<T> = ComputedRef<T[]>(见 index.ts)。

两种差集语义

  • 相对补集(Asymmetric Difference):默认行为,即A \ B,只包含属于 A 但不属于 B的元素,数学上称为 "B 在 A 中的相对补集"。
  • 对称差集(Symmetric Difference):传入{ symmetric: true }后,得到A △ B,即只属于 A 或只属于 B 的元素并集,等价于(A \ B) ∪ (B \ A)。

这两者也是官方文档开篇就点明的核心语义(index.md)。

二、基础用法:与响应式数组配合

最直接的使用方式是传入两个 ref 数组,官方文档给出了一个完整的响应式示例(index.md):

import { useArrayDifference } from '@vueuse/core' const list1 = ref([0, 1, 2, 3, 4, 5]) const list2 = ref([4, 5, 6]) const result = useArrayDifference(list1, list2) // result.value: [0, 1, 2, 3] list2.value = [0, 1, 2] // result.value: [3, 4, 5]

这里有两个值得注意的细节:

  1. 结果是响应式的:result.value在list2被整体重新赋值后自动从[0, 1, 2, 3]变为[3, 4, 5],这正是computed的特性——useArrayDifference内部使用toValue()读取 ref 的最新值,并把这些读取包装在computed中建立依赖追踪。
  2. 保持原数组顺序与元素形态:结果来自Array.prototype.filter,因此输出顺序与list1一致,且保留的是list1中的原引用,而不是拷贝。

测试用例 index.test.ts 验证了这一行为,包括把list1也改掉后结果变为空数组的场景:

const list1 = deepRef([1, 2, 3, 4, 5]) const list2 = deepRef([4, 5, 6]) const result = useArrayDifference(list1, list2) expect(result.value).toEqual([1, 2, 3]) list2.value = [1, 2, 3] expect(result.value).toEqual([4, 5]) list1.value = [1, 2, 3] expect(result.value).toEqual([])

三、自定义比较函数:处理对象数组

当数组元素是对象时,默认的严格相等比较(===)无法正确工作,此时需要传入第三个参数——自定义比较函数。官方文档示例(index.md):

import { useArrayDifference } from '@vueuse/core' const list1 = ref([{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }]) const list2 = ref([{ id: 4 }, { id: 5 }, { id: 6 }]) const result = useArrayDifference(list1, list2, (value, othVal) => value.id === othVal.id) // result.value: [{ id: 1 }, { id: 2 }, { id: 3 }]

比较函数compareFn(value, othVal)的语义:返回true表示"两个元素视为相等"。从源码看(index.ts),计算逻辑是:

const diff1 = computed(() => toValue(list).filter(x => toValue(values).findIndex(y => compareFn(x, y)) === -1, ), )

也就是说,list1中每个元素x,只要在list2里找不到任何一个与之相等的元素(findIndex返回-1),就保留在差集结果中。

如果不传比较函数,默认使用严格相等比较(index.ts):

function defaultComparator<T>(value: T, othVal: T) { return value === othVal }

因此对于基础类型(数字、字符串)数组,直接省略第三参数即可;只有对象、NaN等无法通过===精确比较的场景才需要自定义compareFn。

四、按键名比较:更简洁的字符串重载

对于"按对象某个属性比较"这一高频场景,useArrayDifference还提供了更简洁的重载:第三参数直接传属性名字符串。对应的类型签名在 index.ts:

export function useArrayDifference<T>( list: MaybeRefOrGetter<T[]>, values: MaybeRefOrGetter<T[]>, key?: keyof T, options?: UseArrayDifferenceOptions, ): UseArrayDifferenceReturn<T> export function useArrayDifference<T>( list: MaybeRefOrGetter<T[]>, values: MaybeRefOrGetter<T[]>, compareFn?: (value: T, othVal: T) => boolean, options?: UseArrayDifferenceOptions, ): UseArrayDifferenceReturn<T>

当第三参数是字符串时,实现会把key展开为一个比较函数(index.ts):

if (typeof compareFn === 'string') { const key = compareFn as keyof T compareFn = (value: T, othVal: T) => value[key] === othVal[key] }

用法示例:

const list1 = ref([{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }]) const list2 = ref([{ id: 3 }, { id: 4 }, { id: 5 }]) const result = useArrayDifference(list1, list2, 'id') // result.value: [{ id: 1 }, { id: 2 }]

这与显式写(value, othVal) => value.id === othVal.id完全等价。测试用例 index.test.ts 覆盖了该重载,并验证了数组变更后结果同步更新的行为。

五、对称差集:开启symmetric选项

如果业务需要的是"只属于 A 或只属于 B 的元素"(例如同步两份数据时找出两侧各自独有的记录),应使用对称差集。官方文档示例(index.md):

import { useArrayDifference } from '@vueuse/core' const list1 = ref([{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }]) const list2 = ref([{ id: 4 }, { id: 5 }, { id: 6 }]) const result = useArrayDifference( list1, list2, (value, othVal) => value.id === othVal.id, { symmetric: true }, ) // result.value: [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 6 }]

注意结果顺序:先输出A \ B([{ id: 1 }, { id: 2 }, { id: 3 }]),再拼接B \ A([{ id: 6 }]),最终[{ id: 1 }, { id: 2 }, { id: 3 }, { id: 6 }]。

从源码看对称差集的实现(index.ts),它实际计算了两个方向的差集再合并:

if (symmetric) { const diff2 = computed(() => toValue(values).filter(x => toValue(list).findIndex(y => compareFn(x, y)) === -1, ), ) return computed(() => symmetric ? [...toValue(diff1), ...toValue(diff2)] : toValue(diff1)) } else { return diff1 }

两个方向都保持各自原数组的顺序:diff1来自list1的过滤结果,diff2来自list2的过滤结果。测试用例 index.test.ts 对symmetric与 key 形态、compareFn 形态的组合均做了验证,包括两个数组完全一致时结果为空、两个数组互不相交时结果为两者并集等边界场景。

六、响应式原理剖析:toValue+computed的组合

理解了用法之后,值得深入看一下它的响应式内核。整个函数的实现非常精简(index.ts),关键点有三个:

1. 统一解包MaybeRefOrGetter

参数类型MaybeRefOrGetter<T[]>意味着你既可以传ref([1, 2, 3]),也可以传普通数组[1, 2, 3],甚至传一个返回数组的函数:

const result = useArrayDifference(() => [1, 2, 3], list2)

实现内部统一通过 Vue 的toValue()解包(index.ts),这也是 Vue 3.3+ 组合式 API 的标准做法。由于toValue是在computed的 getter 内调用的,任何 ref 依赖都会被自动追踪——这就是"响应式"的来源。

2. 惰性计算与依赖追踪

函数返回的是ComputedRef,差集运算只有在读取.value时才执行。只要list1或list2指向的 ref 发生变更,下一次读取时就会基于最新值重新计算。函数声明上的@__NO_SIDE_EFFECTS__注解(index.ts)也表明它不会产生任何副作用,可以被安全地摇树(tree-shaking)与任意调用。

3. 相等判定的语义边界

由于使用findIndex查找"是否存在相等元素",useArrayDifference处理的是元素级去重视角下的差集,而不是下标对齐的"位置差"。例如list1 = [1, 2, 3]、list2 = [3, 4, 5],结果为[1, 2];即使元素在数组中的位置不同,只要"相等"(由compareFn决定)就被排除。这也是它与filter、includes等原生 API 组合行为的天然差异,使用时需留意重复元素场景(如list1中有两个1,而list2中有一个1,则结果中仍保留一个1,因为findIndex只判断"是否存在")。

七、在 VueUse 数组工具家族中的位置

useArrayDifference属于 VueUse 的 shared 包,并被统一从 packages/shared/index.ts 导出(export * from './useArrayDifference'),最终通过@vueuse/core提供给所有使用者,因此文档中的导入语句import { useArrayDifference } from '@vueuse/core'是标准写法。

它与 shared 包中的其他数组工具共同构成一套完整的"响应式数组操作"能力,这些函数全部接受MaybeRefOrGetter<T[]>并返回响应式结果(全部列于 packages/shared/index.ts):

组合式函数对应原生能力说明
useArrayDifference集合差集本文主角,A \ B 或 A △ B
useArrayEveryArray.every是否所有元素满足条件
useArrayFilterArray.filter响应式过滤
useArrayFind/useArrayFindIndex/useArrayFindLastArray.find系列响应式查找元素 / 下标 / 最后一个匹配
useArrayIncludesArray.includes响应式包含判断(index.md)
useArrayJoinArray.join响应式拼接
useArrayMapArray.map响应式映射
useArrayReduceArray.reduce响应式归约
useArraySomeArray.some是否存在满足条件的元素
useArrayUnique去重响应式数组去重

在数据对比、表单校验、标签同步、购物车合并去重等场景中,useArrayDifference往往与useArrayUnique、useArrayIncludes搭配使用,可以完全摆脱手写watch+ 手动计算的状态同步代码。

八、安装与快速上手

useArrayDifference随@vueuse/core发布,安装后即可直接使用:

npm i @vueuse/core # 或 pnpm add @vueuse/core / yarn add @vueuse/core

然后按需引入即可,无需额外注册:

import { useArrayDifference } from '@vueuse/core' const result = useArrayDifference( ref([{ id: 1 }, { id: 2 }]), ref([{ id: 2 }, { id: 3 }]), 'id', { symmetric: true }, ) // result.value: [{ id: 1 }, { id: 3 }]

由于导出的是纯 ESM 组合式函数且带@__NO_SIDE_EFFECTS__标记,主流打包器都能对它做 tree-shaking,只把用到的函数打进产物。若你想亲自验证本文所有行为,可以直接查看并运行仓库中的测试文件 index.test.ts,它用 Vitest 完整覆盖了基础差集、compareFn、key 形态以及两种 symmetric 组合共 5 组断言场景。

  • 前端

【免费下载链接】vueuse

Collection of essential Vue Composition Utilities for Vue 3

项目地址:https://gitcode.com/gh_mirrors/vu/vueuse
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询