💡 问题背景

在实际业务开发中,我们经常会在弹框中使用 树形结构(el-tree 来展示和选择数据。
我在做一个“事件类型管理”的功能时遇到了这样的问题:

第一次打开弹框时,树形数据展示的是上一次的旧勾选状态;
关闭弹框再重新打开一次后,才会变成最新的数据。

接口确认返回的是新数据,但前端渲染总是慢一拍。

🧩 问题代码示例

我们来看下核心逻辑(精简版)👇:

async function getEventHandingList() {
  const res = await eventHandingList();
  checkKeys.value = []
  treeData.value = convertToTreeData(res.data, eventTypeArr1, eventTypeMap3)
  console.log(treeData.value, '888888')
}

function convertToTreeData(rawData, parentMap, childMap) {
  return parentMap.map(parent => {
    const children = (rawData[parent.value] || []).map(item => {
      const match = childMap.find(c => c.value === item.eventType)
      if (item.status === 1) checkKeys.value.push(item.id)
      return {
        ...item,
        label: match ? match.label : ''
      }
    })
    return {
      id: parent.value + '111',
      label: parent.label,
      status: 0,
      children
    }
  })
}

逻辑很简单:

  • 通过接口 eventHandingList() 获取数据;

  • convertToTreeData 转成 el-tree 可识别结构;

  • 根据状态设置勾选项 checkKeys

  • 渲染到页面。

但问题是:
👉 第一次打开弹框,树形结构显示旧数据;第二次打开才正常。


🔍 原因分析

Vue3 的响应式机制 + Element Plus 的渲染机制在这里“撞车”了。

Vue 的响应式更新是 异步的,也就是说:

  • treeData.valuecheckKeys.value 的变化会在下一个“渲染周期”才同步到 DOM;

  • 而这时 el-tree 已经开始渲染,拿到的是 旧的 props 值

  • 所以你第一次看到的还是上一次的树结构;

  • 第二次因为组件重新渲染了,所以才正确。

本质上,这是一个 DOM 更新时机不一致 的问题。


✅ 解决方案一:nextTick() 等待 DOM 更新(推荐)

只要在数据更新后,等 Vue 把响应式变化同步到 DOM,再让树渲染即可:

import { nextTick } from 'vue'

async function getEventHandingList() {
  const res = await eventHandingList();
  checkKeys.value = []
  treeData.value = convertToTreeData(res.data, eventTypeArr1, eventTypeMap3)
  await nextTick() // ✅ 等待 DOM 更新完成
}

这样 el-tree 能拿到 最新的 treeDatacheckKeys
第一次打开弹框时就能正确显示最新状态。


✅ 解决方案二:强制重挂载组件(更彻底)

如果弹框的内容较复杂、缓存较多,也可以选择强制销毁再挂载一次:

<template>
  <el-tree
    v-if="treeVisible"
    :data="treeData"
    show-checkbox
    node-key="id"
    :default-checked-keys="checkKeys"
  />
</template>

<script setup>
import { ref, nextTick } from 'vue'

const treeData = ref([])
const checkKeys = ref([])
const treeVisible = ref(true)

async function getEventHandingList() {
  const res = await eventHandingList();
  checkKeys.value = []
  treeVisible.value = false // 🚫 临时卸载 el-tree
  treeData.value = convertToTreeData(res.data, eventTypeArr1, eventTypeMap3)
  await nextTick()
  treeVisible.value = true // ✅ 重新挂载,强制刷新
}
</script>

这种方式更保险,适用于:

  • 弹框复用;

  • 多次切换数据源;

  • el-tree 内部状态缓存太多的情况。


✅ 解决方案三:利用弹框事件 @open

在 Element Plus 中,el-dialog 提供了 @open 事件,
我们可以在弹框打开时自动拉取新数据:

<el-dialog v-model="visible" title="事件管理" @open="getEventHandingList">
  <el-tree
    :data="treeData"
    show-checkbox
    node-key="id"
    :default-checked-keys="checkKeys"
  />
</el-dialog>

这样每次打开弹框,树都会自动加载并刷新最新状态。


⚙️ 最终推荐组合方案

async function getEventHandingList() {
  const res = await eventHandingList();
  checkKeys.value = []
  treeVisible.value = false
  treeData.value = convertToTreeData(res.data, eventTypeArr1, eventTypeMap3)
  await nextTick()
  treeVisible.value = true
}

✅ 简洁、安全、100% 解决首次加载不刷新的问题。


🎯 总结

问题 原因 解决方式
第一次打开树显示旧数据 DOM 更新延迟 使用 nextTick()v-if 重新挂载
弹框数据未刷新 弹框未重新加载数据 @open="getEventHandingList"
勾选状态未同步 default-checked-keys 绑定太早 等待数据加载完再赋值

🧠 技术思考

这个问题本质上揭示了两个前端基础知识点:

  1. Vue3 的响应式是异步的 —— 数据变更不会立即反映到 DOM;

  2. Element Plus 的组件渲染是一次性初始化的 —— 首次挂载后不会主动重新取值。

遇到这类“第一次显示旧数据、第二次才对”的问题,
你可以优先想到:

“是不是数据更新太快,DOM 还没跟上?”

然后大胆用 await nextTick() 试试看。


✍️ 写在最后

调试这类问题时,最重要的不是“背方案”,
而是理解 Vue 的 渲染时序
等你明白这一点,很多“第一次不更新”的问题都会迎刃而解。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐