news 2026/10/4 22:48:08

VueUse 函数详解:useArrayFindLast——响应式获取数组中最后一个匹配元素

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
VueUse 函数详解:useArrayFindLast——响应式获取数组中最后一个匹配元素
  • 前端

【免费下载链接】vueuse

Collection of essential Vue Composition Utilities for Vue 3

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

useArrayFindLast 是 VueUse 提供的一组响应式数组工具函数之一,它把原生Array.prototype.findLast封装成了基于computed的响应式派生数据:只要传入的数组或回调依赖发生变化,返回的ComputedRef就会自动重新计算。本文以 packages/shared/useArrayFindLast/index.md 为核心,结合仓库源码 index.ts 与测试用例 index.test.ts,完整讲解它的用法、参数、底层实现原理与常见坑位,读完即可在自己的 Vue 3 项目中直接落地使用。

一句话概括

useArrayFindLast(list, fn)返回一个只读的计算属性(ComputedRef),其值始终等于数组中最后一个满足测试函数fn的元素;如果没有任何元素满足条件,则值为undefined。它的行为与原生Array.findLast完全一致,区别在于它是响应式的——数组本身或其元素的变化都会被自动追踪并触发重新求值。

基础用法

首先从@vueuse/core中导入函数:

import { useArrayFindLast } from '@vueuse/core'

最常见的用法是传入一个由ref组成的数组,配合一个测试回调:

const list = [ref(1), ref(-1), ref(2)] const positive = useArrayFindLast(list, val => val > 0) // positive.value: 2

这里数组list中的元素是ref(1)、ref(-1)、ref(2),回调函数val => val > 0用于判断元素是否为正数。由于findLast是从数组末尾向前搜索,因此返回的是最后一个正数2,而不是第一个正数1。

返回值是ComputedRef<T | undefined>类型的只读 ref,在模板或watch中可以直接使用:

<script setup lang="ts"> import { ref } from 'vue' import { useArrayFindLast } from '@vueuse/core' const list = [ref(1), ref(-1), ref(2)] const positive = useArrayFindLast(list, val => val > 0) </script> <template> <p>最后一个正数是:{{ positive }}</p> </template>

回调签名

回调函数的签名与原生Array.prototype.findLast一致,接收三个参数(源码见 index.ts):

参数类型说明
elementT当前正在测试的元素(已自动解包 ref,见下文"元素自动解包")
indexnumber当前元素的索引,从数组末尾向前的搜索过程中索引依次递减
arrayMaybeRefOrGetter<T>[]被搜索的数组本身
const result = useArrayFindLast(list, (element, index, array) => { console.log(element, index, array.length) return element > 0 })

与 useArrayFind 的差异

VueUse 同系列还提供useArrayFind(返回第一个匹配元素)与useArrayFindIndex(返回第一个匹配元素的索引,找不到时返回-1)。三者对比如下:

函数行为找不到时返回
useArrayFind从头开始找第一个匹配元素undefined
useArrayFindLast从尾开始找最后一个匹配元素undefined
useArrayFindIndex从头开始找第一个匹配元素的索引-1

例如同样的数组[ref(1), ref(-1), ref(2)]与回调val => val > 0,useArrayFind返回1,而useArrayFindLast返回2。对应实现可参考 useArrayFind/index.ts 与 useArrayFindIndex/index.ts。

与响应式数组配合使用

useArrayFindLast的强大之处在于,即使传入的是reactive数组(或其长度动态变化),结果也会实时更新。文档中的示例:

import { useArrayFindLast } from '@vueuse/core' const list = reactive([-1, -2]) const positive = useArrayFindLast(list, val => val > 0) // positive.value: undefined list.push(10) // positive.value: 10 list.push(5) // positive.value: 5

逐步解读:

  1. 初始数组[-1, -2]中没有正数,因此positive.value为undefined;
  2. list.push(10)后,数组变为[-1, -2, 10],最后一个正数是10;
  3. 继续list.push(5)后,数组变为[-1, -2, 10, 5],此时最后一个正数变成5(因为5在10之后)。

这个行为在测试用例 index.test.ts 中被完整验证:

it('should work with reactive array', () => { useSetup(() => { const list = reactive([-1, -2]) const positive = useArrayFindLast(list, val => val > 0) expect(positive.value).toBe(undefined) list.push(10) expect(positive.value).toBe(10) list.push(5) expect(positive.value).toBe(5) }) })

也就是说:数组内容变化(增删元素、元素值变化)都会让计算结果自动刷新,这是它相对于手动调用findLast的核心价值——你不需要自己去监听数组并手动更新状态。

参数类型详解

从源码的类型签名(index.ts)可以看出两个参数的完整类型:

export function useArrayFindLast<T>( list: MaybeRefOrGetter<MaybeRefOrGetter<T>[]>, fn: (element: T, index: number, array: MaybeRefOrGetter<T>[]) => boolean, ): UseArrayFindLastReturn<T>

其中:

  • list:类型为MaybeRefOrGetter<MaybeRefOrGetter<T>[]>。这意味着它可以接受:
    • 普通数组:[1, 2, 3];
    • 元素为ref的数组:[ref(1), ref(2)];
    • 元素为getter函数的数组:[() => 1, () => 2];
    • 由ref包裹的数组:ref([1, 2, 3]);
    • 由getter函数返回的数组:() => [1, 2, 3]。
  • fn:测试回调,返回boolean,决定元素是否"匹配"。
  • 返回值:UseArrayFindLastReturn<T>,即ComputedRef<T | undefined>。

元素自动解包

有一个容易被忽略的细节:当数组元素是ref时,回调函数接收到的element参数已经被自动解包(unwrapped)。源码中通过toValue(element)完成了这一操作:

return computed(() => toValue<T | undefined>( !Array.prototype.findLast ? findLast(toValue(list), (element, index, array) => fn(toValue(element), index, array)) : toValue(list) .findLast((element, index, array) => fn(toValue(element), index, array)), ))

因此你不需要在回调里手动写val.value,直接写val => val > 0即可,即使数组元素是ref。

源码原理:computed 封装与旧环境 Polyfill

useArrayFindLast的完整实现只有约 20 行(index.ts),核心逻辑可以拆解为三层:

1. 响应式外层:computed

函数整体由computed()包裹,toValue(list)会在求值阶段取出当前数组。由于 Vue 的computed会自动追踪求值过程中读取到的所有响应式依赖,因此:

  • 数组本身是ref/reactive时,数组的增删与替换会被追踪;
  • 数组元素是ref/getter时,读取元素的过程(toValue(element))也会被追踪。

任一依赖变化,都会触发重新计算。这也是"响应式"的来源。

2. 功能主体:原生 findLast 优先

实现优先使用原生Array.prototype.findLast(ECMAScript 2023 规范,Chrome 97+、Node.js 18+ 等现代环境已支持):

toValue(list) .findLast((element, index, array) => fn(toValue(element), index, array))

注意这里回调的element同样经过toValue(element)解包,所以fn中拿到的是解包后的值。

3. 兜底方案:内置 Polyfill

对于不支持findLast的旧环境(如 Node.js < 18),源码内置了一个手动实现的findLastPolyfill(index.ts):

// Polyfill for node version < 18 function findLast<T>(arr: T[], cb: (element: T, index: number, array: T[]) => boolean): T | undefined { let index = arr.length while (index-- > 0) { if (cb(arr[index], index, arr)) return arr[index] } return undefined }

其算法是从数组末尾向前遍历(index从arr.length递减到0),找到第一个满足回调的元素立即返回;全部不满足则返回undefined。选择逻辑通过!Array.prototype.findLast判断:只有环境缺失该原生方法时才走 Polyfill,现代环境下零额外开销。

导出与体积

useArrayFindLast经由 packages/shared/index.ts 统一导出,随@vueuse/core一起发布。根据仓库中的 packages/export-size.json 记录,其打包后体积约为199 B(gzip 前,按项目 export-size 统计口径),非常轻量。

响应式失效演示:元素 ref 变化也会触发更新

除了reactive数组,元素级ref的变化同样会被追踪。测试用例 index.test.ts 演示了这一场景:

it('should find positive', () => { useSetup(() => { const item1 = shallowRef(1) const item2 = shallowRef(2) const item3 = shallowRef(3) const positive = useArrayFindLast([item1, item2, item3], val => val > 0) expect(positive.value).toBe(3) item3.value = -1 expect(positive.value).toBe(2) item2.value = -1 expect(positive.value).toBe(1) item1.value = -1 expect(positive.value).toBe(undefined) }) })

执行过程:

  1. 初始[1, 2, 3],最后一个正数为3;
  2. item3变为-1,数组变为[1, 2, -1],最后一个正数变为2;
  3. item2变为-1,数组变为[1, -1, -1],最后一个正数变为1;
  4. item1变为-1,数组变为[-1, -1, -1],无正数,结果为undefined。

这说明即使数组本身不变,只要某个元素的ref值变化,useArrayFindLast的结果也会自动更新——非常适合在列表项状态频繁变动的场景(如待办事项的完成状态、选中状态判断)中直接使用。

常见场景建议

结合以上特性,useArrayFindLast适合以下实战场景:

  • 取"最后一个满足条件"的条目:如判断一组校验规则中最后一个未通过校验的字段、获取列表中最后一个处于某状态的节点;
  • 与 ref 元素数组搭配:当元素本身是响应式状态且会被单独更新时,无需手动重组数组;
  • 与响应式数组搭配:当数组长度与内容会动态变化(如push、splice)时,结果自动跟随刷新;
  • 替代手动 watch:相比watch(list, () => { ... })后再手动调用findLast,直接使用本函数更简洁,且天然惰性求值(仅在读取.value时计算)。

需要从数组头部找第一个匹配元素时,改用 useArrayFind;需要拿到匹配元素的索引时,改用 useArrayFindIndex。三者共享相同的响应式设计:都基于computed+toValue,同样支持ref数组、reactive数组与元素自动解包。

  • 前端

【免费下载链接】vueuse

Collection of essential Vue Composition Utilities for Vue 3

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/4 22:44:17

2026神器榜!好用的AI智能降重工具全盘点,效率暴涨300%!

2026 年 AI 论文写作工具的综合王者是 千笔AI&#xff0c;国内毕业全流程首选千笔AI&#xff1b;千笔以中文润色 降重双能与全流程闭环见长&#xff0c;深度适配高校规范与查重系统&#xff0c;AI 率控制行业领先。按需求选对工具&#xff0c;论文效率可提升70%-90%&#xff0…

作者头像 李华
网站建设 2026/10/4 22:21:21

黑苹果安装工具包全解析:从镜像制作到OpenCore引导与Kext驱动

简介&#xff1a;面向想在非苹果电脑上安装苹果系统的玩家&#xff0c;这份工具包集中提供了从引导、分区到驱动匹配的常用软件与辅助组件。整个包共20个文件&#xff0c;压缩后仅约36.82MB&#xff0c;包含exe、7z、zip、iso、dll、msi等格式&#xff0c;覆盖安装程序、镜像文…

作者头像 李华
网站建设 2026/10/4 22:21:16

ISIC2018皮肤病变分割实战:U-Net与CNN训练全指南

简介&#xff1a;这是一份面向医学图像分割实战的皮肤癌病变分割资源&#xff0c;基于ISIC2018数据&#xff0c;提供Unet与Mask R-CNN两条技术路线&#xff0c;帮助图像分割学习者、算法工程师和医工交叉研究者解决黑色素瘤病灶自动识别与像素级分割问题&#xff0c;并降低相关…

作者头像 李华
网站建设 2026/10/4 22:18:37

ins5699驱动源码集成实战:从设备树到sysfs的数据链路

简介&#xff1a;面向嵌入式开发者的 INS5699 实时时钟芯片驱动源码与集成方法&#xff0c;适用于需要精确时间基准的产品项目&#xff0c;适合驱动工程师参考&#xff0c;可作为内核移植与调试的参考资料。压缩包内共包含两个文件&#xff0c;整体仅 45KB&#xff0c;其中一份…

作者头像 李华