记录在使用 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
的值是数组,需要注意。