- 前端
【免费下载链接】vueuse
Collection of essential Vue Composition Utilities for Vue 3
导读
useArrayFilter是 VueUse 核心包中一个轻量而强大的组合式函数,它将原生Array.prototype.filter变为响应式:当源数组或其元素发生变化时,过滤结果会自动重新计算并实时更新。本指南将基于官方文档与其在仓库中的源码、测试实现,带你完整掌握useArrayFilter的两种典型用法(元素为 ref 的数组、普通响应式数组)、其底层惰性求值原理、TypeScript 类型守卫能力,以及它与 VueUse 其他数组工具(useArrayMap、useArrayEvery等)的组合运用。
一、什么是 useArrayFilter
useArrayFilter是 VueUse 中 Reactive Array 系列工具之一,官方定位为"ReactiveArray.filter"。它接受一个数组(或其 ref / getter)和一个过滤回调函数fn,返回一个ComputedRef类型的结果:该结果始终是源数组中通过fn测试的那些元素组成的浅拷贝数组;如果没有元素通过测试,则返回空数组。
它的典型应用场景包括:
- 基于多个独立响应式数据(如分页组件中的若干
ref)动态生成筛选列表; - 对响应式数组(如用户输入、接口返回数据)进行实时过滤,无需手动监听变更;
- 在模板中直接使用返回的 ref,自动驱动 UI 更新。
二、两种核心用法
原文档给出了两个最典型的调用模式,两者都保持完全相同的调用签名,区别只在于传入的源数组形态。
用法一:元素为多个 ref 的数组
import { useArrayFilter } from '@vueuse/core' const item1 = ref(0) const item2 = ref(2) const item3 = ref(4) const item4 = ref(6) const item5 = ref(8) const list = [item1, item2, item3, item4, item5] const result = useArrayFilter(list, i => i % 2 === 0) // result.value: [0, 2, 4, 6, 8] item2.value = 1 // result.value: [0, 4, 6, 8]这是"Array of refs"模式:list本身是普通数组,但每个元素都是ref。useArrayFilter会在内部对每个元素再次调用toValue(i)解包,因此当任意元素(如item2)的值变化时,过滤结果会自动更新——这正是它区别于普通filter的关键。
用法二:元素为普通值的响应式数组
import { useArrayFilter } from '@vueuse/core' const list = ref([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) const result = useArrayFilter(list, i => i % 2 === 0) // result.value: [0, 2, 4, 6, 8] list.value.shift() // result.value: [2, 4, 6, 8]这是"reactive array"模式:整个数组装在一个ref中。当对数组执行shift()、push()、splice()等变更操作时,依赖追踪会触发重算,过滤结果同步变化。
两种模式可以自由混用:list既可以是MaybeRefOrGetter<T>[]形式(ref 元素数组),也可以整体是一个ref/ getter 指向的数组,因为第一层参数同样会被toValue(list)解包。
三、源码级原理解析
useArrayFilter的实现极其精炼,完整源码位于 packages/shared/useArrayFilter/index.ts,核心逻辑只有一行:
export function useArrayFilter<T>( list: MaybeRefOrGetter<MaybeRefOrGetter<T>[]>, fn: (element: T, index: number, array: T[]) => unknown, ): UseArrayFilterReturn<T> { return computed(() => toValue(list).map(i => toValue(i)).filter(fn)) }1. 双重toValue解包
实现的核心是两层toValue解包:
- 第一层
toValue(list):把传入的数组(无论是普通数组、ref包裹的数组,还是 getter 函数)解包成真正的数组; - 第二层
toValue(i):对数组中的每一个元素再次解包,从而支持"ref 元素数组"这种模式。
正是这种设计,让useArrayFilter同时兼容"元素为 ref 的数组"和"元素为普通值的响应式数组"两种形态。toValue是 Vue 3.3+ 提供的工具函数(等价于早期版本的unref通用化),也正因如此,MaybeRefOrGetter类型的值都能被优雅处理。
2. 惰性求值与响应式追踪
函数返回的是computed(() => ...),即一个ComputedRef<T[]>(类型别名UseArrayFilterReturn<T>)。这意味着:
- 惰性求值:只有首次访问
result.value时才执行过滤计算; - 自动依赖追踪:计算过程中访问到的所有响应式数据(
list本身以及每个 ref 元素)都会被收集为依赖,任意依赖变化时结果自动重算; - 结果缓存:依赖未变化时重复读取
result.value不会重复执行filter。
从源码结构看,这一实现模式与 useArrayMap 几乎一致(computed(() => toValue(list).map(i => toValue(i)).map(fn))),与 VueUse 整个 Reactive Array 系列工具保持统一的架构风格。
3. TypeScript 重载与类型守卫
useArrayFilter提供了两个函数重载,其中一个支持类型守卫(type guard)用法:
export function useArrayFilter<T, S extends T>( list: MaybeRefOrGetter<MaybeRefOrGetter<T>[]>, fn: (element: T, index: number, array: T[]) => element is S, ): UseArrayFilterReturn<S>当你的过滤函数写成(item): item is SomeType => ...这种谓词形式时,返回类型会自动收窄为S[],让后续代码获得完整的类型推断。这在过滤联合类型(如(string | null)[])时非常有用:
const list = ref<(string | null)[]>(['a', null, 'b']) const result = useArrayFilter(list, (i): i is string => i !== null) // result.value 的类型被推断为 string[]四、测试用例验证
仓库在 packages/shared/useArrayFilter/index.test.ts 中为useArrayFilter提供了三个 vitest 测试用例,可以作为行为契约参考:
- 与 ref 数组协同:用
shallowRef构造元素数组,断言初始结果为[0, 2, 4, 6, 8],修改item2.value = 1后结果为[0, 4, 6, 8]——验证了元素级响应式; - 与响应式数组协同:用
deepRef包裹[0..9],断言过滤结果,再执行list.value.shift(),结果变为[2, 4, 6, 8]——验证了数组级变更的响应式; - 回调返回值不限于布尔:
useArrayFilter(list, i => i % 2)能返回[1, 3, 5, 7, 9]——验证了过滤语义与原生filter一致(采用真值测试,任何 truthy 返回值都会被保留)。
第三点值得注意:fn的返回值并不强制是boolean,只要返回真值即可保留元素,这与原生Array.filter的语义完全一致。
五、参数说明与边界行为
| 参数 | 类型 | 说明 |
|---|---|---|
list | MaybeRefOrGetter<MaybeRefOrGetter<T>[]> | 被过滤的源数组,可以是普通数组、ref/shallowRef包裹的数组,或返回数组的 getter 函数;数组元素也可以是 ref |
fn | (element: T, index: number, array: T[]) => unknown | 对每个元素执行的测试函数,接收当前元素、索引和完整数组(解包后),返回真值表示保留该元素 |
返回值类型为ComputedRef<T[]>(对应类型别名UseArrayFilterReturn<T>),即"通过测试的源数组元素的浅拷贝";若没有元素通过测试,返回空数组。
需要注意的边界行为:
- 惰性计算:过滤逻辑不会在调用时立刻执行,而是在首次读取
.value时执行;因此如果从未读取结果,修改源数组不会产生任何计算开销; - 每次重算执行完整
map+filter:依赖变化时会重新解包整个数组并重跑过滤,对于超大规模数组应评估其性能;对于超大列表场景,可考虑配合useDebounceFn或派生工具做节流/防抖; - 副作用函数请谨慎:
fn在每次重算时都会被调用,不要在fn中放置有副作用的操作,这符合原生filter的纯函数预期。
六、组合与相关函数家族
useArrayFilter属于 VueUse Shared 包中完整的 Reactive Array 工具家族,它们共享同一套"双解包 + computed"架构,全部从 packages/shared/index.ts 统一导出,可组合使用:
- useArrayMap:响应式
Array.map,与useArrayFilter配合可组成"过滤 → 映射"的数据管道; - useArrayEvery:响应式
Array.every; - useArraySome:响应式
Array.some; - useArrayIncludes:响应式
Array.includes; - useArrayUnique:数组去重。
一个典型的数据管道示例:
import { useArrayFilter, useArrayMap } from '@vueuse/core' const items = ref([ { name: 'Apple', price: 5, stock: 3 }, { name: 'Banana', price: 2, stock: 0 }, { name: 'Cherry', price: 4, stock: 10 }, ]) // 筛选有库存的商品,并映射出名称列表 const inStockNames = useArrayMap( useArrayFilter(items, i => i.stock > 0), i => i.name, ) // inStockNames.value: ['Apple', 'Cherry']由于useArrayFilter返回的是ComputedRef,而useArrayMap第一参数同样接受MaybeRefOrGetter数组,两者可以无缝串联,形成完全响应式的数据转换链。
七、总结
useArrayFilter用一行核心计算逻辑(toValue双解包 +computed)就为 Vue 3 应用带来了原生Array.filter的完整响应式能力:
- 入参灵活:支持 ref 元素数组、响应式数组、getter,甚至二者的混合形态;
- 结果自动更新:任何源数据变化都会触发过滤结果重算,模板中使用
result即可实时渲染; - 类型安全:通过函数重载支持类型守卫,过滤后类型自动收窄;
- 与生态协同:可与
useArrayMap等 Reactive Array 家族成员自由组合成响应式数据管道。
无论是构建筛选面板、搜索列表还是实时数据仪表盘,useArrayFilter都能以极低的代码成本完成任务。它的完整实现、类型定义与测试用例都值得在 源码目录 中进一步研读。
- 前端
【免费下载链接】vueuse
Collection of essential Vue Composition Utilities for Vue 3
相关推荐
在 Vue 3 项目中使用 useArrayFilter 实现响应式数组过滤:VueUse 响应式 Array.filter 深度实战
在 Vue 3 项目中使用 useArrayFilter 实现响应式数组过滤:VueUse 响应式 Array.filter 深度实战 useArrayFilt
AI 应用人工智能大模型数字人AI Agent语音前端后端桌面应用移动开发即时通讯3D渲染VueUse useArrayFilter 指南:在 Vue 3 中实现响应式的 Array.filter
VueUse useArrayFilter 指南:在 Vue 3 中实现响应式的 Array.filter useArrayFilter 是 VueUse 在
前端VueUse @vueuse/math useMin:响应式 Math.min 组合式函数完全指南
VueUse @vueuse/math useMin:响应式 Math.min 组合式函数完全指南 useMin 是 VueUse @vueuse/math 扩
前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考