【Vue3】你真的会在v-for中使用Ref 吗?

10,627 阅读2分钟

本文中涉及的技术是 Vue3.x + Vite.js,

你以为你对 Vue 已经足够熟练使用了,但是现实偶尔还是会给你一巴掌然后问你你真的会吗?呜呜呜...

1. v-for 中的 Ref 错误使用

事情是这样的:

有一个需求是某个页面中有一个流程图比对的功能 (流程图已经被抽离出了Graph.vue),既然是比对,那就需要两个流程图啦,一顿劈里啪啦敲下一个页面结构, 假设它是这样:

template:

<section>
  <div v-for="item in ['A', 'B']" :key="item">
    <Graph :ref="`graphRef${item}`" />
  </div>
</section>

script:

const graphRefA = ref<typeof Graph | null>(null)
const graphRefB = ref<typeof Graph | null>(null)

如果用户修改了某个比对因素,这时候假设需要 Graph 组件里面执行以下doSomething方法吧,这个时候敲下类似这样代码:

graphRefA.value?.doSomething();
graphRefB.value?.doSomething();

本地跑起来,对着那些比对因素一顿改,嗯嗯看起来稳的一批,Graph里的doSomething执行的很顺畅呢,就是这样,上测试环境吧!

就在我悠哉游哉划水的时候,测试同学找我说,修改比对因素后,页面图形没反应呀,我第一反应就是"这不可能啊", 赶紧打开测试环境试了一下,咦?啥情况??


通过在source中找到对应的源文件,发现打包后的代码里,ref 那块变成这样

source.png 找到在source里找到graphRefA.value?.doSomething()对应的位置,加个断点 debug 了一下,惊呆了graphRefA直接就是 null 呀。这可是能doSomething呢!

然后又结合别的页面单独引入Graph组件使用的时候没有这个问题呀,那可以排除是Graph组件自身的问题,那问题可能就只剩下这个 循环+ref 的操作了。

2. 官方教你怎么做事

后面再官方文档里找到v-for 中的 Ref 数组, 啊,原来这个操作官方文档还专门给了使用方法。

template:

<div v-for="item in list" :ref="setItemRef"></div>

script:

import { onBeforeUpdate, onUpdated } from "vue";

export default {
  setup() {
    let itemRefs = [];
    const setItemRef = (el) => {
      if (el) {
        itemRefs.push(el);
      }
    };
    onBeforeUpdate(() => {
      itemRefs = [];
    });
    onUpdated(() => {
      console.log(itemRefs);
    });
    return {
      setItemRef,
    };
  },
};

果然是我使用问题了。。。

3. 官方操作补充

可以看到,官方网站里面的itemRefs是一个数组,并且打印后会发现这个数据里的 item 是没有属性可以去区分这个 ref 是graphRefA还是graphRefB呢,所以我决定采取文档下面的 Tip 里提到的,将itemRefs设成对象,那样的话,取的时候就很轻松了:

template:

<div v-for="item in ['A', 'B']" :key="item">
  <Graph :ref="(el) => setGraphRef(el, item)" />
</div>

script:

let graphRefs: { [key: string]: typeof Graph } = {};
const setGraphRef = (el: typeof Graph, type: string) => {
  if (el) {
    graphRefs[type] = el;
  }
};

在修改比对因素后想要调用doSomething的话就这样写:

for (const item of ["A", "B"]) {
  const targetRef = graphRefs[item];
  if (targetRef) targetRef.doSomething();
}

emmm...这次才真的稳了。