markRaw 让 Vue 超大树渲染性能从 184ms 降到 17ms!

3,566 阅读2分钟

背景

在 Vue 项目中,渲染一个包含 10000+ 节点 的树形结构(树形选择器)时,如果直接将接口返回的数据交给响应式系统,性能会明显下降。
当数据层级很深、字段很多时,Vue 会对整棵树进行深度代理,这一步的开销非常大。即便节点是点击后才展开并渲染,在响应式代理阶段依然会消耗大量时间,导致页面卡顿、交互延迟。

本文将通过 数据清洗markRaw 跳过响应式代理 两种方式,把渲染耗时从 184ms 降到 17ms,性能提升超过 10 倍

树形选择器

image.png

原始数据,不做处理,直接赋值到ref

数据格式示例如下:

{
  id:1,
  name:'根节点',
  level:1,
  children:[
    id:12,
    name:'子节点',
    level:2,
    children:[]
  ]
  ... // 其他字段
}

请求接口获取节点

const treeData = ref({})
// 获取树数据
const getTreeData = async () => {
  try {
    const res = await getData()
    const now = Date.now()
    console.log('更新前', now)
    treeData.value = res
    treeDataReset = cloneDeep(treeData.value)
    nextTick(() => {
      console.log('更新后 耗时 ms', Date.now() - now)
    })
  } catch (err) {
    console.warn(err)
  }
}

image.png

可以看到,Vue 会对整个树对象进行代理,导致页面渲染耗时高达 184ms。

优化点 1:数据清洗,去除无用字段

当数据中存在大量无关字段时,会显著影响性能。

// 格式化树形数据
const formatTreeData = ({ id, name, level, parentName, children }: any): TreeNode => {
  // 提取需要的字段
  const formattedNode: TreeNode = {
    name,
    id,
    level,
    parentName
  }

  // 递归处理子节点
  if (children && children.length > 0) {
    formattedNode.children = children.map((child: any) => formatTreeData(child))
  }
  return formattedNode
}
treeData.value = formatTreeData(res)

image.png

结果:去除多余字段后,渲染耗时降至 82ms

优化点 2:使用 markRaw

markRaw 的作用是为对象添加 __v_skip 标记,使 Vue 响应式系统跳过该对象的深度观测,数据变化将不会触发视图更新,从而显著提升性能。

treeData.value = markRaw(formatTreeData(res))

image.png

结果:使用 markRaw 后,让 Vue 完全跳过数据代理,渲染耗时直接降到 17ms。

markRaw怎么停止响应式监测?

markRaw 只是给对象加上 __v_skip = true 标记。
依赖收集时,traverse 发现该标记会直接跳过,避免递归和响应式处理。
traverseWatcher.get 中执行,而 Watchereffect 创建。

graph TD
A[markRaw 设置 __v_skip=true] --> B[traverse 检测到标记跳过对象]
B --> C[Watcher.get 调用 traverse]
C --> D[effect 创建 Watcher]
观察下面的vue源码

image.png

image.png

image.png image.png

image.png

image.png

性能对比表

优化方式渲染耗时
原始数据(包含所有字段,Vue 深度代理)184ms
数据清洗(去除无关字段)82ms
数据清洗 + markRaw(跳过响应式代理)17ms

总结

  1. 数据清洗——只保留必要字段,减少代理对象体积。
  2. markRaw——直接跳过响应式,让 Vue 不再对数据做深度追踪。

适合静态且只读的大型数据结构