当前位置: 首页 > news >正文

Vue 原理三大子系统:编译时、响应式与运行时

目录

  • Vue 原理的结构化理解:从三大子系统看“数据驱动视图”
    • 为什么要分三大子系统?
    • 一、编译时:预编译与优化(Compiler-Core)
    • 二、响应式:数据变化的追踪系统(Reactivity)
    • 三、运行时:渲染和调度的执行者(Runtime-Core)
    • 三者如何协同?一条从模板到 DOM 的流水线
    • 常见疑问(FAQ)
    • 实战小抄(Cheat Sheet)
    • 一句话记忆
    • 通俗描述

Vue 原理的结构化理解:从三大子系统看“数据驱动视图”

“要理解 Vue 的原理,可以从它的三大核心子系统入手:编译时、响应式和运行时。三者协同工作,实现了 Vue ‘数据驱动视图’ 的核心目标。”

本文不求铺天盖地,只求讲清主线。你读完将能把 Vue 的工作流一句话讲清:模板先被编译,数据用响应式追踪,最后由运行时把差异精准打到真实 DOM 上。

为什么要分三大子系统?

  • 编译时(Compiler-Core):静态分析与优化,让“渲染时少做事”。
  • 响应式(Reactivity):建立“数据—副作用”的订阅关系,数据一变,副作用精准重跑。
  • 运行时(Runtime-Core):执行渲染与更新,把“想要的界面”变成“真实的 DOM”。

这三个角色像拍电影:编译时是“分镜与预演”,响应式是“场记与通知器”,运行时是“导演带着摄影师开拍”。


一、编译时:预编译与优化(Compiler-Core)

职责:把模板(.vuetemplate 选项)编译成渲染函数(render())。

核心流程:

  1. 解析(Parse)→ 把模板字符串变成 AST(抽象语法树)
  2. 转换(Transform)→ 在 AST 上做静态分析与结构优化
  3. 静态提升(Static Hoisting)→ 把永不变化的节点提升到渲染函数外复用
  4. Patch 标志(Patch Flags)→ 给“动态部分”打标记,运行时可定向更新
  5. 代码生成(Codegen)→ 输出可执行的 JS 渲染函数

一句话:编译时提前“算清楚哪些会变、哪些不变”,并把“会变的地方”打上标签。

示例(思路演示,非实际产物):

<div><h1>静态标题</h1><p>{{ msg }}</p>
</div>

编译后(概念化展示):

function render(ctx) {// h 就是创建 VNode 的辅助函数// 静态的 <h1> 可能被静态提升(只创建一次)const _hoisted_h1 = h('h1', null, '静态标题')// 动态的 <p> 会带上 PatchFlag,例如 TEXTreturn h('div', null, [_hoisted_h1,h('p', /* PatchFlag: TEXT */ null, ctx.msg)])
}

有了 PatchFlag,运行时更新时就能“直奔主题”:只更新 p 的文本,不用糟蹋无辜的 h1


二、响应式:数据变化的追踪系统(Reactivity)

职责:把“数据”和“副作用(例如视图渲染)”绑在一起,数据一改,副作用就被精准通知。

Vue 3 用 Proxy 拦截读写,核心两个动作:

  • 依赖收集(track,发生在 get)→ 谁在读我,我就记下谁
  • 触发更新(trigger,发生在 set)→ 我变了,就通知被我记下的那些副作用

基本用法:

import { reactive, ref, effect, computed } from 'vue'const state = reactive({ count: 0 })
const double = computed(() => state.count * 2)effect(() => {// 读取 state.count,会被 trackconsole.log('副作用执行:', state.count)
})state.count++ // set → trigger → 让依赖它的 effect 重新执行

补充:

  • effect 内读取到哪些响应式数据,就会被这些数据“记住”。
  • scheduler(调度器)可把多次变更合并到微任务里,避免频繁重复更新。
  • computed 会缓存结果,只有依赖变了才重新计算。
  • watch 适合副作用逻辑与渲染解耦的场景(请求、日志、动画等)。

核心价值:精准更新。组件不会“因为父更了我就更”,而是“我依赖的数据真变了才更”。


三、运行时:渲染和调度的执行者(Runtime-Core)

职责:把渲染函数输出的 VNode 树挂到真实 DOM,并在数据更新时进行 Diff & Patch。

关键词:虚拟 DOM(VNode)

  • 渲染函数返回的不是 DOM,而是用 JS 对象描述的“它应该长啥样”。
  • patch(oldVNode, newVNode) 会对比新旧 VNode,计算最小变更并修改真实 DOM。

工作流:

  1. 初次渲染:app.mount() → 执行 render() 产出 VNode → patch(null, vnode) 创建真实 DOM → 挂载
  2. 更新渲染:响应式触发副作用 → 重新执行 render() 得到新 VNode → patch(old, new) diff 后定向更新
  3. 卸载:组件离场,运行时回收并移除对应 DOM 及副作用

运行时的“加速器”:

  • Patch Flags:来自编译时的“路标”,直指动态点,避免全量 Diff。
  • Keyed Diff:同层有序列表的高效对比,尽量复用节点。
  • 调度(Scheduler):批量队列、异步刷新,减少无意义的中间状态。
  • 特性能力:FragmentTeleportSuspense 等,丰富渲染表达力。

三者如何协同?一条从模板到 DOM 的流水线

  1. 开发者写模板与逻辑
  2. 编译时把模板“提前想明白”,产出带 PatchFlag 的 render()
  3. 首次挂载:运行时执行 render(),把 VNode 变成 DOM
  4. 数据变化:响应式追踪到变更,通知相关副作用(通常是组件渲染 effect)
  5. 运行时重跑 render() 得到新 VNode,根据 PatchFlag 和 Diff 精准打补丁

这就是 Vue 的“数据驱动视图”:你改数据,它就改界面。


常见疑问(FAQ)

Q1:有了编译时,为什么还需要虚拟 DOM?

A:因为运行时仍需要从“理想界面”过渡到“真实 DOM”。编译时提供的是“指路牌”,运行时仍要执行更新动作,VNode 是可编程、可对比、可优化的中间层。

Q2:Vue 的响应式和 React 的 setState 有啥不同?

A:Vue 更偏“数据可观察”,通过 track/trigger 精准感知依赖;React 更偏“显式触发更新”,依赖父子 render 流程和 memo 化策略。两者目标一致:少做无用功。

Q3:不用模板、直接写 h() 会更快吗?

A:不一定。模板能让编译器做更多静态优化(静态提升、PatchFlag),手写 h() 需要你自己保证结构稳定与优化。

Q4:为什么有时我修改数据,视图“下一帧”才更新?

A:这源于调度器的批量异步刷新策略(微任务合并),用来避免同一轮事件循环里重复无效的多次渲染。


实战小抄(Cheat Sheet)

  • 写模板时:

    • 尽量保持结构稳定(少改列表 key、少动节点层级)
    • 使用 key 明确列表节点身份
    • 大块不变内容可组件化,利于静态提升与复用
  • 写状态时:

    • 基础状态用 reactive/ref,派生值用 computed
    • 与渲染无关的副作用用 watch(请求、缓存、日志)
    • 注意“只读源、只改源”的数据流,避免循环触发
  • 性能调优:

    • 定位“频繁变动点”和“庞大列表”
    • 利用编译器产物:PatchFlag、v-oncev-memo
    • 善用懒加载与拆分组件,降低一次渲染压力

一句话记忆

编译时“先想清楚”、响应式“盯紧数据”、运行时“动手精准改”。


通俗描述

把 Vue 想成一家“手摇奶茶店”:

  • 编译时是“菜谱设计师”,先把菜单排好,哪些料固定、哪些能加料一目了然;
  • 响应式是“店员的小本子”,你加了珍珠(数据一变),他立刻记下要多摇两下(副作用要重跑);
  • 运行时是“调饮师+出杯口”,根据小本子精准加料、摇一摇、贴上标签端出来(Diff & Patch)。

最终你点的是“少冰三分糖加布丁”,端上来的就不会是“热拿铁加葱段”。这就是 Vue:改了数据,杯子里的世界就跟着变,而且只改对的那一口。

http://www.dtcms.com/a/389060.html

相关文章:

  • 黑马SpringCloud02
  • Windows安装Kafka(kafka_2.12-3.9.1),配置Kafka,以及遇到的问题解决方案
  • Kafka 硬件与操作系统选型与调优实战
  • ActiveMQ面试
  • ActiveMQ 系统知识全解析
  • 智慧园区:科技赋能城市单元,重塑未来运营新生态
  • 2025年9月17日学习笔记——模式识别与机器学习第11章——非监督学习与聚类
  • arcgispro基于森林的分类与回归 (空间统计)
  • npm run serve 和 npm run dev的区别
  • 2025 局域网内多台服务器时间统一,最稳定且无需联网的方案是部署 NTP 离线服务器部署chrony 轻量且兼容性强,支持纯离线环境
  • 机器学习如何改变AI?
  • rook-ceph的dashboard配置覆盖与生效
  • 在 macOS 上安装 Claude Code 的完整指南
  • RocketMQ Dashboard 消息重复问题排查与修复(rocketmq-dashboard-2.0.0-source-release)
  • 卓伊凡的第一款独立游戏-详细介绍游戏开发引擎unity-以及详细介绍windows和mac的安装步骤【01】
  • 多表联合查询
  • Day26_【深度学习(6)_神经网络NN(1中)激活函数_softmax详解篇】
  • 黑盒测试:测试用例设计之等价类设计方法(等价类划分:Equivalence Partitioning)有效等价类、无效等价类、边界值分析
  • 22 C++11 初始化新姿势:{} 统一初始化(省等号)+initializer_list 底层解析
  • 黑马头条_SpringCloud项目阶段二:FreeMarker组件以及MinIO系统集成
  • MySQL 数据库基础操作指南:从创建管理到备份恢复全解析
  • 【Java】-- rjvm 项目分析
  • Markdown 常用语法参考
  • 11.2.3 固定话题聊天实现
  • CAN(控制器局域网)工业协议教学文档(一)
  • PHP基础-变量与常量(第八天)
  • SQ01,SQ02,SQ03,SE93事务码配置
  • AI提示词Excel 表格提取数据准确度处理
  • DeviceNet 转 EtherNet/IP 实现罗克韦尔 PLC 与库卡机器人在汽车白车身焊接的微秒级数据同步协作案例
  • GPT-5 vs Gemini 2.5 Pro:两大AI旗舰模型深度技术对比