记录在使用 Vue3 setup 语法时,在立即执行的watch里使用 DOM 元素遇到的情况。

推荐方案

需求:在 DOM 元素存在后,每当一些数据改变,执行关于 DOM 的操作。比如使用第三方统计图,需要等到元素存在后,根据数据的改变更新统计图。

<div ref="el"></div>
const el = ref();
const data = computed(() => "你的数据");
watch(
  () => [el.value, data.value],
  () => {
    if (!el.value) return;
    // 操作DOM
    console.log(document.body.clientHeight);
  },
  { immediate: true }
);

有人可能会把watch写在onMounted里面,这样确保 DOM 已经加载完毕。这样代码能执行,但是有风险。因为不在setup里同步执行的watch和任何await之后的watch, Vue 不能知道它属于哪个组件,所以当组件销毁后,它还会运行,有内存泄露风险。当然你可以手动销毁这种watch,请看官方文档。

实验

<div ref="el"></div>
const el = ref();
const data = computed(() => "你的数据");
watch(
  () => [el.value, data.value],
  async () => {
    if (!el.value) return;
    // 获取body的高度
    console.log(document.body.clientHeight);
    await $nextTick();
    // 第二次获取body的高度
    console.log(document.body.clientHeight);
  },
  { immediate: true }
);
获取 body 高度实验第一次第二次($nextTick 之后)
Vue高度为 0高度正常
Nuxt报错,document 是 undefined高度正常
Nuxt 不开启 ssr高度为 0高度为 0

Nuxt 解决方案

我之所以设计这个实验,并且需要获取元素高度,是因为我使用 echarts 时遇到了问题。echarts 需要元素宽度和高度不为 0。

从实验结果可以看到,Vue 和开启 ssr 的 nuxt 表现都还在预期内,没开启 ssr 的 nuxt 就很奇怪了。解决方法也很简单,强烈推荐,不管有没有开启 ssr,需要操作的 DOM,比如图表,幻灯片,视频播放器,都用 nuxt 内置的client-only组件包裹起来。这样当元素可用时,它的高度也是正常的。

<client-only>
  <div ref="el"></div>
</client-only>

v-for 中的 ref

<div v-for="...">
  <div ref="el"></div>
</div>

这种情况下, el.value的值是数组,需要注意。