理解Vue底层内核:生命周期运行逻辑与响应式本质

0 阅读13分钟

Vue3 底层内核全集|生命周期 + 响应式原理(地基篇)

在之前学习 Vue3 的过程中,我一直处于知识点碎片化的状态。 零散 API 看得多、底层逻辑懂得少,只会调用语法,却说不清每一个概念存在的意义,常常学完就忘、越学越混乱,知其然不知其所以然。

很多原理单独看都能看懂,但知识点之间彼此孤立,没有完整的思考脉络,既没办法串联理解框架运行逻辑,也很难往源码、工程化方向深入学习。

于是我顺着自己的思考习惯,从框架初心出发,一步步追问缘由、梳理痛点、串联因果,把生命周期、响应式原理、各类API设计逻辑完整打通。 这篇文章没有晦涩难懂的源码堆砌,也没有生硬的概念灌输,只按照普通人认知顺序,讲清 Vue3 底层整套地基体系。

比起抠细枝末节的知识点,我更在意知识之间的关联与由来。 先建立整体框架思维,弄懂每一项设计的底层初衷,往后不管是业务开发、原理深挖、阅读源码,都会顺畅很多。

一、先搞懂:Vue 到底是个什么样的框架?

我们先从最基础的问题开始:我们为什么要用 Vue?原生 JS 不好用吗?

大家平时写原生 JS 应该都有体会,开发过程非常繁琐:

想要页面变,你必须手动找 DOM、手动赋值、手动修改内容;用户在页面输入内容,你又要手动获取 DOM 值、同步到数据里。

简单说:原生 JS 是你指挥浏览器一步一步干活,你不写,页面就不动,这种写法有个专业名字,叫命令式编程。

命令式编程最大的问题就是:数据和页面死死绑在一起,代码冗余、维护起来极其痛苦。

那 Vue 是怎么解决这个问题的?这里就引出了 Vue 的核心定位:渐进式 JavaScript 框架。

“渐进式”三个字听起来好高级啊,其实人话特别简单:

Vue 的内核只保留最核心的视图渲染、数据驱动能力。 (如果你写小项目,只用基础语法就够了;如果要开发大型项目,再按需搭配路由、状态管理、工程化工具,能力可以一点点往上叠加,这就是「渐进式」。)

同时 Vue 换了一套全新的写法:声明式编程

不用你一步步指挥浏览器操作 DOM,你只需要定义好数据和页面结构,剩下的视图更新、数据同步,全部由框架自动完成。(太牛了)

你只管改数据,页面自动跟着变,这就是 Vue 最核心的魅力。

二、MVVM 到底是什么?为什么所有 Vue 项目都遵循它?

知道了 Vue 是数据驱动之后,新的问题又来了:Vue 是怎么实现数据自动驱动视图的?

这里就诞生了前端通用的架构设计思想:MVVM

它不是复杂代码,只是一套规范数据、视图、框架三者关系的通用规则,目的就是彻底解开「数据」和「页面」的强耦合关系

我们拆开三个部分,通俗讲明白:

M(Model 数据层)

就是我们代码里所有的变量、状态、接口返回的数据。 它只安安静静存在内存里,只管存数据,从来不碰页面。

V(View 视图层)

就是我们写的 template 模板、浏览器渲染出来的 DOM 页面。 它只负责展示内容,只负责给用户看,不管理任何数据逻辑。

VM(ViewModel 视图调度层)

这就是 Vue 内核本身,也是整个 MVVM 的核心桥梁。

它默默做了两件超级重要的事:

1. 监听数据变化,自动更新页面视图

2. 监听页面用户操作,自动同步最新数据

这下逻辑就通了:

数据和页面再也不用直接沟通,全部交给 Vue 内核中转。我们开发者只需要专注修改数据,彻底告别手动操作 DOM 的痛苦。

三、组合式API 凭什么取代选项式API?

搞懂了框架核心思想,我们再来看日常开发的代码写法。

用过 Vue2 都知道,Vue2 用的是选项式API

什么是选项式API?

就是框架提前给你分好了固定“区块”数据写在  data 、方法写在  methods 、监听写在  watch 、逻辑写在生命周期

刚学的时候觉得很规整,但是项目一变大,问题直接爆炸:

同一个完整的业务逻辑,比如「用户搜索功能」:

  • 搜索关键词数据在 data
  • 搜索触发方法在 methods
  • 页面加载默认请求在 mounted
  • 关键词变化监听在 watch

一段完整的业务,被硬生生拆得四分五裂,代码极其零散,阅读、维护、复用都特别麻烦。

为了解决这个开发痛点,Vue3 推出了全新的 组合式API。

它的设计思路完全反过来:不再按代码类型分类,而是按业务逻辑聚合。

同一个业务的所有数据、方法、监听、生命周期逻辑,全部写在同一个区域。代码高度集中、逻辑清晰,随便抽离复用,完美适配大型项目开发。

简单总结:

- 选项式API:适合小项目,代码规整但业务碎片化

- 组合式API:适合所有项目,逻辑聚合、可复用、易维护

这也是 Vue3 全面主推组合式API的根本原因。

四、生命周期:组件的一生,你真的看懂了吗?

写完代码、写完业务,新的问题又来了:我们的代码到底该在什么时候执行?
接口什么时候发?DOM 什么时候拿?定时器什么时候清?

如果代码执行时机乱了,就会出现各种莫名其妙的 bug:DOM 获取不到、接口重复请求、页面关闭还在跑代码造成内存泄漏。

为了解决「代码执行时机混乱」的问题,Vue 给每一个组件规划了完整的时间运行流程,这就是我们常说的组件生命周期

大家可以把一个 Vue 组件,想象成一朵花的一生,特别好理解:

1. 创建阶段(setup)—— 花的种子期

组件刚在内存里创建完成,只初始化数据、定义方法。 此时没有 DOM、页面还没渲染,只能写逻辑,不能操作页面

2. 挂载阶段(onBeforeMount / onMounted)—— 花破土开花

- onBeforeMount:模板编译完成,马上要渲染页面,依旧拿不到真实 DOM

- onMounted:页面彻底渲染完成,DOM 完全挂载成功

这是我们业务最常用的阶段,接口请求、DOM 操作、第三方插件初始化,全部写在这里。

3. 更新阶段(onBeforeUpdate / onUpdated)—— 花微调状态

当我们修改响应式数据,页面会重新渲染更新。

- onBeforeUpdate:数据变更,页面即将刷新 - onUpdated:页面刷新完成,可操作最新的 DOM 结构

4. 销毁阶段(onBeforeUnmount / onUnmounted)—— 花枯萎凋零

组件关闭、页面切换时触发。

我们在这里清空定时器、移除事件监听、终止异步请求,从根源杜绝内存泄漏

另外生命周期钩子,本质就是:Vue 把组件一生的关键时间节点开放出来,让我们自定义执行业务代码。 懂了生命周期,你就永远不会写错代码的执行时机。

五、响应式原理:Vue 自动更新的核心魔法

前面所有内容,都是 Vue 的「代码写法、执行规则」。而 Vue 最核心、最精髓、支撑整个框架运行的底层能力,就是响应式

我们先抛出最核心的问题:

凭什么我们改一行数据,页面就能自动刷新?Vue 是怎么精准感知数据变化的?

原生 JS 是完全做不到这一点的,你修改变量,浏览器毫无感知。

所以 Vue 需要一套机制:监听数据的读取、监听数据的修改,这套机制有个专业名字,叫数据劫持。

Vue2 的短板

Vue2 用的是  Object.defineProperty只能劫持对象里单独的某一个属性。 新增属性、删除属性、数组修改,全部监听不到,漏洞非常多。

Vue3 直接升级浏览器原生 API —— Proxy。

它不再监听单个属性,而是直接给整个对象套一层监控外壳。 不管你是修改、新增、删除、修改数组,所有操作全能被拦截监听。

基于 Proxy,Vue3 完整跑通了响应式的核心三步,也是整个 Vue 最底层的运行逻辑:
1. 数据劫持

用 Proxy 包裹我们定义的所有数据,从此以后,所有读写操作都要经过这层监控。Vue 从此拥有了感知数据变化的能力。

2. 依赖收集

页面渲染的时候,会读取我们的响应式数据。 Vue 会默默记录:当前这个页面渲染逻辑,依赖了哪些数据。 这一步在底层源码中叫做 track。 (简单说:谁用了我,我就把谁记下来。

3. 派发更新

当我们修改数据时,Proxy 监听到变化,会立刻找到所有依赖当前数据的页面副作用函数 effect,统一重新执行、刷新视图。数据发生变动后触发视图更新的行为,底层源码命名为 trigger。

人话总结全程:读数据的时候记一下谁在用,改数据的时候通知所有人更新。) 这就是 Vue 自动视图更新的底层魔法。

六、ref / reactive:两套响应式API,为什么缺一不可?

弄懂了 Proxy 原理,你会发现一个新问题:Proxy 只能监听对象,监听不到普通变量!

(为啥一开始不都设计上?:其实 Vue 区分设计 ref 和 reactive,不只是因为 Proxy 仅能劫持对象类型数据。从框架设计角度来说,也并不适合将代码中所有数据无脑转为响应式,会造成不必要的性能开销。 因此 Vue 选择按需区分处理,让我们手动控制哪些数据需要拥有响应式能力。)

数字、字符串、布尔值这些基础数据,不是对象,没有结构可以被 Proxy 劫持。

为了适配所有数据类型,Vue3 设计了两套响应式 API:

reactive

专门服务对象、数组这类引用类型数据。 直接对原生对象做 Proxy 深度劫持,不用额外包装,直接使用。

ref

专门服务数字、字符串、布尔这类基础类型数据。

原理特别聪明:把普通值包装成一个 { value: 原值 } 的对象,强行变成可以被 Proxy 劫持的引用类型。

这也就是为什么 ref 定义的数据,修改和读取需要写  .value  的根本原因。

简单区分:

- 基础类型 → 用 ref

- 对象、数组 → 用 reactive

日常开发直接全用 ref,兼容性最强、不用纠结

七、响应式丢失 & toRef / toRefs:解决90%人的开发bug

大家一定遇到过这个诡异问题:

用 reactive 定义的对象,解构赋值之后,数据改了,页面不更新?

这个现象,就是 Vue 经典的响应式丢失。

会用 toRefs 解决,但不知道为什么会失效,这里我们通俗讲透:

reactive 返回的是一个被 Proxy 代理过的特殊对象。 而 JS 解构赋值,会生成一份普通原始值的拷贝。 (一旦解构,新变量就和原来的 Proxy 代理彻底断开联系,没有了监控,自然就失去了响应式。

为了解决这个问题,Vue 提供了两个 API:

toRef

单独把对象中的某一个属性,保留代理关联,单独转为响应式数据。

toRefs

批量把 reactive 对象的所有属性全部转为 ref,解构之后依旧保留 Proxy 代理,彻底杜绝响应式丢失。

人话总结: 解构会切断代理联系 → 响应式失效用 toRef/toRefs 保留引用关系 → 响应式永久生效

八、computed / watch / watchEffect:响应式的三大衍生能力

基础的响应式,只能实现「数据变、页面变」。但真实业务中,我们还有更多复杂需求,所以 Vue 在响应式底层之上,封装了三个高频 API。

computed 计算属性

核心特点:自带缓存、惰性执行。

它专门用来「根据旧数据推导新数据」。 数据不变化,就不会重复计算,直接复用缓存结果,性能极高。 适合做数值计算、数据筛选、字段拼接。

watch 监听

精准盯死某一个指定数据,数据变化就触发回调

它不负责计算数据、不负责更新视图,专门用来做副作用逻辑:接口请求、弹窗提示、数据校验、复杂异步操作。

watchEffect 自动监听

算是 watch 的懒人升级版,不用手动指定监听数据。

函数内部用到了哪些响应式数据,就自动监听哪些数据,默认初始化执行一次,依赖变化自动重新执行,代码极度简洁。

九、全文总结梳理

读到这里,整套 Vue3 底层地基已经基本打通,我们顺着整篇思考逻辑,简单串成一条完整链路:

原生命令式开发繁琐、DOM与数据强耦合 → 诞生 Vue 渐进式声明式框架

为了解决数据与视图混乱问题 → 确立 MVVM 分层驱动思想

为了解决选项式API业务碎片化问题 → 升级组合式API聚合业务逻辑

为了规范组件代码执行时机 → 设计组件完整生命周期与钩子函数

为了实现数据自动感知更新 → 采用 Proxy 实现数据劫持、依赖收集、派发更新整套响应式机制

为了适配所有数据类型 → 衍生出 ref / reactive 两套响应式方案

为了解构失效问题 → 诞生 toRef / toRefs 修复响应式丢失

为了满足复杂业务场景 → 封装 computed / watch / watchEffect 三大衍生API

这一整套层层递进的设计,就是 Vue3 能够稳定、高效、优雅跑起来前端项目的底层地基。学习框架不必急于上手堆砌代码。理清逻辑、追本溯源,看懂每一个设计的由来,慢慢真正读懂 Vue。知其然,更知其所以然,往后学习与开发都会豁然开朗。