Post

When Does Vue Update the DOM

Understanding Vue's asynchronous DOM update mechanism and why UI changes don't always happen immediately after data changes.

发现了一个让我想了一会儿的现象:点击提交按钮,loading 没出现。

逻辑很清楚:

1
2
3
4
5
6
7
8
9
const onOk = async () => {
  loading.value = true;
  try {
    await instance?.value?.submit?.();
    unmount();
  } finally {
    loading.value = false;
  }
};

loading.value = true 在最前面,按理应该先出现 loading,然后走后面的逻辑。但当表单校验失败时,loading 一次都没出现过——好像 loading.value = true 根本没执行。

原因在于 Vue 的更新机制是异步的。loading.value = true 是同步代码,执行完之后 Vue 不会立即更新 DOM,它把这次更新放进一个队列,等当前的同步代码全跑完,才去处理队列里的 UI 更新。

而 element-ui 的表单校验是同步的,校验不通过会立刻抛出,进入 finallyloading.value 马上被改回 false。Vue 等到有空处理更新队列时,loading 已经是 false 了,根本来不及渲染 true 的状态。

Vue 在同步代码里的多次 state 变化,最终只渲染最后一次的结果。

接口请求之所以没这个问题,是因为 await fetch() 会让出主线程,Vue 趁这个空档更新 UI。表单校验太快,没给 Vue 任何机会。

如果确实需要让 loading 出现,可以在设置之后加 await nextTick(),强制 Vue 先渲染一次再继续:

1
2
3
4
5
6
7
8
9
10
const onOk = async () => {
  loading.value = true;
  await nextTick();
  try {
    await instance?.value?.submit?.();
    unmount();
  } finally {
    loading.value = false;
  }
};

不过多数情况下,校验失败不显示 loading 也完全可以接受——反正用户马上就看到报错了。知道这个机制,比知道 workaround 更重要。

This post is licensed under CC BY 4.0 by the author.