Agent Developer Roadmap
前端框架

Vue:运行时依赖追踪与编译优化协作

最后核验:2026-06-30|适用版本:Vue 2.7、3.4–3.5(示例锁定 3.5.39)|前置知识:JavaScript、TypeScript、Browser / Web APIs

Vue 的渐进采用边界与固定成本

Vue 把两类机制组合在一起:运行时响应式系统追踪哪些 effect 读取了哪些值,模板编译器标记哪些节点和属性可能变化。前者让状态组合直观,后者减少 Virtual DOM 更新时要检查的范围。理解这条链,才能解释 reactive 解构为何失去连接、computedwatch 为什么不是同一种工具、模板为何比随意手写 render function 更容易优化。

Vue 适合渐进增强、单页应用和需要清晰模板/逻辑组织的团队;同一核心可以从 CDN 小组件扩展到构建型应用。若需求是静态内容加一个原生表单,Vue 仍是额外运行时;若需求已经确定为文件路由、SSR、服务端 API 和混合渲染,应直接评估 Nuxt,而不是在客户端 Vue 上重造应用框架。Ways of Using Vue

不要因模板“像 HTML”就忽略平台语义。v-ifv-for 和组件最终仍生成 DOM;合法内容模型、焦点、表格语义与浏览器性能不会被 Vue 自动保证。

单文件组件、模板与组件契约

Single-File Component(SFC)把 <script><template><style> 放在 .vue 文件中,由 @vue/compiler-sfc 拆分并交给 script/template/style 编译器。<script setup> 是编译语法:顶层绑定自动暴露给模板,definePropsdefineEmitsdefineModel 等宏在编译时处理,不是运行时全局函数。

<script setup lang="ts">
type Entry = { id: string; name: string };

const props = defineProps<{
  entry: Entry;
  selected?: boolean;
}>();
const emit = defineEmits<{
  toggle: [id: string];
}>();
</script>

<template>
  <article>
    <h3>{{ props.entry.name }}</h3>
    <button type="button"
      :aria-pressed="props.selected ?? false"
      @click="emit('toggle', props.entry.id)">
      对比
    </button>
  </article>
</template>

props 是父到子的只读输入,子组件不直接修改;emit 是子向父报告事件,不产生浏览器 DOM 冒泡。父组件需要监听祖孙事件时应逐层明确转发或提升状态,不使用隐式事件总线。attribute fallthrough 会把未声明属性落到单根节点;多根组件需用 $attrs 明确放置,避免 class/aria/事件丢失。

slot 让父级提供内容,slot 内容在父级作用域求值;scoped slot 由子级提供 slot props。只需固定标题/正文时直接 props/普通 slot,不为每个文本创建动态组件。

v-model 是 prop + update event 约定。Vue 3.4+ 的 defineModel() 可声明模型,但双向绑定意味着父子共同协议,不用于任意对象深层共享。表单控件仍遵循原生 value/checked/selected 和输入法合成事件语义。

从 SFC 到 Render Function 与 DOM

构建时,SFC parser 读取 descriptor;script 编译处理 setup/宏/TypeScript;template 编译经历 parse、transform、codegen,输出 render function;style 编译处理 scoped/module/preprocessor。生产运行时通常不带模板编译器,浏览器执行预编译 render function。

模板表达式运行在组件 render 上下文,只做同步表达式,不写声明/流程语句。v-if 改变子树是否存在并触发生命周期;v-show 保留节点只切 display,适合频繁切换;v-for 需要稳定 key。v-ifv-for 不放同一元素猜优先级,先用 computed 过滤或外层 template。

scoped CSS 通过编译器为元素和选择器附加 scope attribute,不是 Shadow DOM:全局样式、继承、自定义属性和脚本仍能跨边界。深度选择器会增加父组件对后代实现的耦合,只用于第三方/子组件明确样式接口。

Proxy、ref 与依赖追踪

Vue 3 对对象使用 Proxy,对 ref 使用 getter/setter。effect 执行期间读取响应式属性时,track(target, key) 把当前 effect 放进订阅集合;写入触发 trigger(target, key),相关 effect 被调度。官方用 WeakMap<target, Map<key, Set<effect>>> 解释核心关系。Reactivity in Depth

组件渲染本身是 Reactive Effect(响应式副作用)。首次渲染读取 queryentriesselectedIds,这些依赖变化后组件更新被排入队列;同一 tick 的多次同步写入会批量处理。需要等待 DOM 更新完成时使用 nextTick(),不要用固定 setTimeout 猜提交时机。

ref 适合可替换的任意值;模板会自动解包顶层 ref,脚本中仍显式 .valuereactive 深度代理对象且返回值与原对象身份不同,不能用另一个普通对象整体替换原代理引用。把响应式属性解构成普通局部变量会绕过 Proxy 访问;使用 toReftoRefs,或保持对象访问。Reactivity Fundamentals

API读取与失效适用问题常见误用
ref.value getter/setter标量、可替换对象、模板状态在模板中到处手写 .value
reactiveProxy 属性聚合且原地更新的对象解构后期待原始值仍响应
computed惰性缓存纯派生搜索结果、总计、格式化发请求或修改其他状态
watch显式 source某值变化后同步外部系统复制可以 computed 的状态
watchEffect执行时自动收集依赖与副作用高度一致依赖隐蔽、异步后读取不被追踪

Composition API 按业务能力组织状态、派生和副作用;Options API 则按 datacomputedmethods 等选项组织。Vue 3 的 Options API 建立在 Composition API 之上,不是旧运行时。小组件两者都清楚时无需强制迁移;跨组件复用带状态逻辑时,composable 比 mixin 的隐式成员来源更可追踪。

computed、watch 与调度队列

以下单文件组件展示 refcomputed 和只同步 URL 的 watch。环境是 Vue 3.5、TypeScript 6、Vite 8。

<script setup lang="ts">
import { computed, ref, watch } from 'vue';

type Entry = { id: string; name: string; tags: string[] };
const props = defineProps<{ entries: Entry[] }>();
const query = ref('');
const selectedIds = ref<string[]>([]);

const filtered = computed(() => {
  const term = query.value.trim().toLowerCase();
  return props.entries.filter(({ name, tags }) =>
    [name, ...tags].some((value) => value.toLowerCase().includes(term)));
});

watch([query, selectedIds], () => {
  const params = new URLSearchParams();
  if (query.value) params.set('q', query.value);
  if (selectedIds.value.length) params.set('compare', selectedIds.value.join(','));
  history.replaceState(null, '', `?${params}`);
}, { deep: true });

function toggle(id: string) {
  selectedIds.value = selectedIds.value.includes(id)
    ? selectedIds.value.filter((item) => item !== id)
    : selectedIds.value.length < 4 ? [...selectedIds.value, id] : selectedIds.value;
}
</script>

<template>
  <label for="q">搜索</label>
  <input id="q" v-model="query" type="search">
  <ul>
    <li v-for="entry in filtered" :key="entry.id">
      {{ entry.name }}
      <button type="button" :aria-pressed="selectedIds.includes(entry.id)"
        @click="toggle(entry.id)">对比</button>
    </li>
  </ul>
</template>

运行本仓库示例:

pnpm --filter @roadmap/vue-slice test
pnpm --filter @roadmap/vue-slice preview --port 4202

输入:访问 http://127.0.0.1:4202/,搜索 proxy,选择四项后尝试第五项,再刷新。

预期输出:2 项测试、vue-tsc 与生产构建通过;搜索匹配 Vue,选择保持四项,URL 写入并恢复查询与选择。八项数据无需防抖、虚拟列表或异步 Store。

computed getter 应保持纯。它的缓存按响应式依赖失效,而不是按函数参数;返回新对象会让引用每次变化,可能继续触发下游 effect。Vue 3.4+ 对 computed 结果稳定性做了优化,但复杂对象仍应先减少不必要派生,而不是手写深比较。Vue Performance

watch 的 source 决定依赖:直接传 state.count 只传入当前 number,应写 getter () => state.count;reactive 对象作为 source 默认深层追踪,深遍历大图成本高。flush: 'pre' 默认在组件 DOM 更新前执行,post 在更新后,sync 每次写入同步触发且不批处理,只用于极小同步需求。异步 watcher 在第一个 await 后读取的值不会自动成为 watchEffect 依赖。

Vue 3.5 的 onWatcherCleanup 必须在 watcher 同步执行阶段注册;传统 onCleanup 参数也可绑定当前 watcher。请求 watcher 应取消陈旧结果:

watch(query, async (value, _oldValue, onCleanup) => {
  const controller = new AbortController();
  onCleanup(() => controller.abort());
  entries.value = await loadEntries(value, controller.signal);
}, { flush: 'post' });

VNode、Block Tree 与 keyed Diff

Render function 返回 VNode。首次挂载递归创建 DOM;更新时 patch(oldVNode, newVNode) 先比较 type/key:不同则卸载替换,相同则更新 props/children。文本、数组、组件、Fragment、Teleport、Suspense 各有处理分支。

模板编译器把稳定静态节点提升到 render 外,给动态 VNode 标注 Patch Flags(如 TEXT、CLASS、PROPS),并把动态后代收集进 Block Tree。运行时因此可以跳过静态子树,只访问编译器已知的动态节点;随意手写 render function 可能失去这些提示。

keyed children diff 会同步处理头尾相同节点,再为未知区间建立 key → new index 映射,卸载消失项、patch 存在项,并用最长递增子序列减少 DOM 移动。这个算法不是任意树最优 diff;稳定 key 才能表达业务身份。

<li v-for="entry in entries" :key="entry.id">
  <input v-model="notes[entry.id]" :aria-label="`${entry.name} 备注`">
</li>

使用 index key 或缺 key 时,插入/排序会按位置复用 DOM/组件实例,输入值、焦点、transition 和本地 state 可能对应错条目。key 只需兄弟中唯一,不能每次 render 用随机数,否则所有节点被强制重建。

组件更新由渲染 effect 调度。同一 tick 多次状态写入进入去重队列;父组件通常先于子组件更新,pre/post flush callback 在相应阶段运行。nextTick 等待当前批次 DOM flush,不保证图片加载、CSS transition 或浏览器绘制完成。

SSR、水合、Teleport 与异步边界

SSR 为每个请求创建 app,把 VNode 渲染为 HTML;浏览器解析后,hydrate 复用现有 DOM 并关联组件/事件。服务端与客户端首次 render 必须使用一致数据、时区/locale 和随机结果。非法 HTML 被浏览器纠错也会造成结构 mismatch。

Vue 3.5 支持 hydration strategies 让异步组件在 idle、visible、media 或 interaction 时水合;延迟只适合不需立即交互的区域,且服务端 HTML 要可读。useId 为 SSR/客户端生成稳定 id。data-allow-mismatch 只声明已理解的特定差异,不修复权限、内容或结构错误。

Teleport 把子内容渲染到组件树外的 DOM 目标,但逻辑父子、provide/inject 和事件仍按组件树;目标必须在挂载时存在,SSR 还需框架正确注入 teleport 内容。Suspense 协调 async setup/异步组件的 pending/fallback,目前使用前应核对稳定性和 SSR 行为,不把它当通用错误边界。

Composition API 中的状态与清理边界

Vue 示例ref 保存目录、查询、选择、详情和通知;computed 生成过滤结果与已选条目;watch 只把查询和选择同步到 History。数据加载在 onMounted 中执行,onUnmounted 取消 Fetch。

运行效果Vue 知识目录运行效果
新窗口打开

正在加载示例……

详情使用原生 dialogwatch(active) 等待 nextTick() 后调用 showModal(),因为这是与 DOM 小部件的外部协调;关闭事件清空详情并恢复触发按钮焦点。模板保留真实 label、button、table 和 caption,不用组件封装隐藏语义。

示例能力Vue 原语选择原因
异步目录ref<Entry[]>成功后整体替换一次
搜索结果computed纯派生且依赖明确
URL 同步watchHistory 是外部系统
详情 DOMtemplate ref + nextTickCommit 后调用原生方法
请求取消生命周期 + AbortController防止卸载后陈旧更新
本地对比ref<string[]>小型可序列化状态

这里不使用 Pinia。一个页面只有一个状态拥有者,composable 也没有第二个调用点;提前提取 Store 只会增加间接层。出现跨路由共享、开发工具时间线或独立领域所有权后,再评估 Pinia。

Vue Devtools、编译优化与原始 HTML 风险

Vue DevTools 查看组件、props、响应式状态和性能;开发环境的 onRenderTrackedonRenderTriggered 以及 computed/watch 的 onTrackonTrigger 能定位具体依赖。生产性能仍要用 Chrome Performance 与真实产物测量,开启 app.config.performance 可加入 Vue 标记。Reactivity Debugging

Vue 模板先编译成 render function。编译器做静态提升、缓存事件处理器并生成 Patch Flags,运行时只遍历动态节点形成的 Block Tree;模板因此提供比任意手写 VNode 更强的优化信息。Rendering Mechanism

性能先区分加载与更新:内容敏感页面不应默认纯 SPA;生产构建预编译模板,不向浏览器发送编译器;路由组件按需分割;大列表经测量后虚拟化;访问十万级深层属性出现成本时,才用 shallowRef 并把内部数据当不可变。官方同样提醒避免无意义组件抽象,因为每个组件实例都有成本。Performance Guide

安全上,文本插值和普通绑定默认转义,但 v-html 直接写入 HTML,不能接收用户内容。URL、样式和服务端模板仍需按上下文验证;前端路由守卫不能替代服务端授权。SSR 必须为每个请求创建独立应用和状态,防止跨请求状态污染。SecuritySSR

常见误区:解构 reactive 后期待标量响应;用 watch 保存过滤结果;在 computed 中修改依赖;滥用 deep watch 扫描大对象;列表缺稳定 key;把 nextTick 当任意延迟;用 v-ifv-for 在同一元素制造不清晰优先级;用 v-html 渲染不可信 Markdown。

重大重构、关键 bug 与 Vue 2.7 到 3.5 迁移

节点重大重构行为风险升级证据
Vue 2 → 3defineProperty 改为 Proxy,Composition API、Fragments、Teleport、新编译器与应用实例全局 API、过滤器、事件 API、数组/属性观察语义和第三方插件变化migration build 告警、组件/插件清单、SSR 水合
Vue 3.4模板 parser 重写并提升解析速度;响应式触发重构,defineModel 稳定依赖旧 parser 容错或 effect 重入顺序的代码需复测SFC 编译快照、effect/watch 调度测试
Vue 3.5响应式内部再次重构,降低内存;稳定响应式 props 解构,加入懒水合、useIdonWatcherCleanup解构依赖、清理时机和 SSR 首屏输入变化heap snapshot、watch cleanup、SSR/CSR HTML 对照

Vue 3.5.6 出现响应式依赖清理相关内存泄漏。官方 3.5.7 changelog 修复了开发态 subsHead 未更新,以及垃圾回收对象对应 dep 未从依赖表释放的问题(#11956、#11971、#11979)。这类严重 bug 的症状是组件已经卸载但 heap 持续增长,不能靠手工把业务变量置空修复;应升级至少 3.5.7,并用重复挂载/卸载场景比较 heap snapshot 与 retaining path。Vue 3.5.7 changelog问题 #11956

补丁版本也必须保持 vue@vue/compiler-sfc@vue/server-renderer 同线,避免编译提示与运行时 patch flag 不一致。升级时先跑响应式单测和生产 SSR,再检查 hydration mismatch、KeepAlive/Transition、异步组件和卸载后的订阅数量。

Vue 2 使用 Object.defineProperty getter/setter,新增/删除属性和数组索引存在限制;Vue 3 改用 Proxy、Composition API 和新的编译/运行时架构。Vue 2 已结束生命周期,2.7 只适合作为迁移桥梁;新项目不应基于兼容构建长期停留。Vue 2 EOL

Vue 3.5 是无破坏性 minor,重构响应式系统以改善内存与深层数组追踪,并稳定响应式 props 解构;SSR 增加懒水合策略、useId 和选择性水合差异标记,还加入 useTemplateRefonWatcherCleanupAnnouncing Vue 3.5

截至核验日,3.5 是稳定线,3.6 仍处于 beta,因此只在未来方向讨论。Vapor Mode 探索利用既有响应式图生成不依赖 Virtual DOM 的更新代码,它代表潜在编译策略,不应作为当前生产契约。稳定文档仍把 Vue 描述为以运行时响应式为主、由编译器辅助优化。Vue Core Releases

升级顺序:先升级 3.5 最新 patch;处理编译器、runtime-dom 与服务端渲染器的同版本要求;运行类型、SSR 和水合测试;最后评估响应式 props 解构或懒水合是否减少现有代码。不要把 beta 行为写进稳定数据层。

Vue、React 与 Nuxt 的职责差异

约束VueReactAngularSvelte
更新依赖Proxy/ref 自动追踪状态触发组件 RenderSignals + 框架调度编译器生成细粒度更新
模板HTML-based SFCJSXAngular templateSvelte template
派生状态computedRender / useMemocomputed Signal$derived
外部同步watch / lifecycleEffecteffect / RxJS$effect
渐进增强可 CDN 局部挂载通常构建型根节点更偏完整应用需要编译步骤
完整 Meta-frameworkNuxtNext.js 等Angular 自带较多工程能力SvelteKit

Vue 的平衡点是运行时依赖追踪、模板编译和渐进采用。若团队希望显式单向 Render 与庞大 React 生态,React 更合适;若需要强约束 DI、表单、Router 和企业级一体化,Angular 更直接;若希望编译期细粒度语法和更少运行时代码,可比较 Svelte。

响应式追踪与组件验收练习

  • 实现一个 reactive 对象并解构属性,解释哪个绑定失去响应以及为何。
  • computed 替换保存筛选结果的 watch,确认删除一份状态。
  • onRenderTracked 中定位查询输入触发的依赖,再移除调试钩子。
  • 快速切换两次查询请求,用 watcher cleanup 或生命周期取消旧请求。
  • 比较模板编译输出的 Patch Flags 与手写 render function。
  • 在大只读数组上比较 refshallowRef,没有测量差异时保留 ref
  • 服务端渲染后故意制造时间差异,定位水合警告而非直接压制。
  • 用键盘完成搜索、详情和四项对比,验证焦点与状态通知。

验收标准:能画出 track/trigger 关系,区分 computed 与 watch,解释编译器如何缩小 Patch 范围,并能根据路由和服务端约束判断 Vue 还是 Nuxt。只会背 refreactive 语法不算通过。

Vue 官方文档、RFC 与 Release

  • Vue Guide:Vue 3 当前稳定概念与使用方式;核验于 2026-06-30。
  • Reactivity in Depth:Proxy、track/trigger、effect 与 Signals 对比;核验于 2026-06-30。
  • Rendering Mechanism:模板编译、Virtual DOM、Patch Flags 与 Block Tree;核验于 2026-06-30。
  • Composition API FAQ:与 Options API、mixins 和类型推断的取舍;核验于 2026-06-30。
  • Announcing Vue 3.5:响应式重构、SSR 和 watcher 清理;核验于 2026-06-30。
  • Vue Core Releases:3.5 patch 与 3.6 beta 状态;核验于 2026-06-30。
在 GitHub 上编辑此页

本页目录