目录
- [一、创建一个 Vue 实例](#一、创建一个 Vue 实例)
-
- [1、Vue 2](#1、Vue 2)
- [2、Vue 3](#2、Vue 3)
- [二、Vue3:createApp 到 mount 发生了什么?](#二、Vue3:createApp 到 mount 发生了什么?)
-
- [1、createApp(App) ------ 创建应用实例](#1、createApp(App) —— 创建应用实例)
- 2、app.mount('#app') ------ 真正启动渲染 —— 真正启动渲染)
- [三、render 阶段发生了什么?](#三、render 阶段发生了什么?)
- 四、组件实例是在什么时候创建的?
-
- [1、mountComponent 做了什么(核心)](#1、mountComponent 做了什么(核心))
-
- [(1)、createComponentInstance ------ 创建"组件实例对象"](#(1)、createComponentInstance —— 创建“组件实例对象”)
- [(2)、setupComponent ------ 初始化组件内容](#(2)、setupComponent —— 初始化组件内容)
- [(3)、setupRenderEffect ------ 创建"自动更新机制"](#(3)、setupRenderEffect —— 创建“自动更新机制”)
- [五、"创建 Vue 实例"本质是创建了什么?](#五、“创建 Vue 实例”本质是创建了什么?)
一、创建一个 Vue 实例
创建 Vue 实例 = 创建一个"组件实例 + 响应式作用域 + 渲染副作用 + 生命周期调度系统"
不是简单 new 个对象,本质上是:
- 启动一个完整的 UI 运行时系统
1、Vue 2
cpp
new Vue({
el: '#app',
data() {
return { count: 0 }
},
template: `<button @click="count++">{{ count }}</button>`
})
2、Vue 3
cpp
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
看起来只是"启动一下",但实际上 Vue 在背后做了 一整套 UI 引擎初始化工作。
二、Vue3:createApp 到 mount 发生了什么?
我们从 Vue3 开始(现代项目为主)。
1、createApp(App) ------ 创建应用实例
cpp
const app = createApp(App)
此时 Vue 做了几件关键事:
| 做了什么 | 本质 |
|---|---|
| 创建 app 对象 | 应用级上下文容器 |
| 保存根组件 App | 作为根 vnode 的来源 |
| 初始化依赖注入容器 | provide/inject 根级作用域 |
| 准备全局配置 | directive、component、plugin 注册入口 |
但注意:这时页面还没开始渲染。
2、app.mount('#app') ------ 真正启动渲染
这一行,才是 Vue 真正"活过来"的地方。
内部核心流程可以简化为:
cpp
mount(rootContainer) {
// 1. 创建根组件的虚拟节点
const vnode = createVNode(App)
// 2. 渲染
render(vnode, rootContainer)
}
三、render 阶段发生了什么?
render() 其实是 Vue 渲染引擎的总入口:
cpp
render(vnode, container) {
patch(null, vnode, container)
}
第一次渲染时 oldVNode 为 null,所以是"挂载"。
四、组件实例是在什么时候创建的?
答案是:在 patch 过程中
当 patch 发现 vnode 是组件:
cpp
processComponent(n1, n2, container) {
mountComponent(n2)
}
1、mountComponent 做了什么(核心)
这是 创建 Vue 实例真正的本质阶段:
cpp
function mountComponent(initialVNode, container) {
// 1.创建组件实例对象
const instance = createComponentInstance(initialVNode)
// 2.初始化组件
setupComponent(instance)
// 3.建立响应式渲染副作用
setupRenderEffect(instance, initialVNode, container)
}
我们逐个拆。
(1)、createComponentInstance ------ 创建"组件实例对象"
这一步创建的不是 DOM,而是内存形态:
cpp
instance = {
vnode, // 当前组件的虚拟节点
type, // 组件定义(App)
props,
attrs,
slots,
ctx, // 代理上下文(this.xxx 来源)
setupState, // setup() 返回的数据
data,
isMounted: false,
subTree: null, // 组件渲染出的子树
update: null // 响应式更新函数
}
👉 这就是 Vue 组件的"内存形态"
(2)、setupComponent ------ 初始化组件内容
这里才真正开始处理你写的代码。
如果是 Options API:
- 解析 props
- 执行 data()
- 绑定 methods
- 创建 computed
- 注册 watch
- 处理生命周期
如果是 Composition API:
cpp
const setupResult = setup(props, ctx)
Vue 会把 setup 返回值:
| 返回类型 | 处理方式 |
|---|---|
| 函数 | 作为 render 函数 |
| 对象 | 作为组件状态挂到 instance.setupState |
此时:
- 响应式数据创建完
- 依赖收集系统就绪
- 生命周期注册完
(3)、setupRenderEffect ------ 创建"自动更新机制"
🔥 这是 Vue 最核心的一步。
Vue 会创建一个响应式副作用:
cpp
instance.update = effect(function componentEffect() {
if (!instance.isMounted) {
// 首次渲染
const subTree = instance.render()
patch(null, subTree, container)
instance.subTree = subTree
instance.isMounted = true
} else {
// 更新渲染
const nextTree = instance.render()
patch(instance.subTree, nextTree, container)
instance.subTree = nextTree
}
})
这段代码的意义是:
- 把组件的 render() 变成一个响应式副作用函数
于是:
| 当数据变化时 | 发生什么 |
|---|---|
| 响应式依赖触发 | effect 重新执行 |
| render() 再次运行 | 生成新虚拟 DOM |
| patch diff | 更新真实 DOM |
这就是 Vue 自动更新的本质。
五、"创建 Vue 实例"本质是创建了什么?
我们总结成 5 个核心系统:
| 系统 | 作用 |
|---|---|
| 组件实例对象 | 组件的运行时载体 |
| 响应式状态系统 | data / setup / props 可追踪 |
| 渲染函数 | render() 负责生成虚拟 DOM |
| 渲染副作用 effect | 自动追踪依赖并触发更新 |
| 生命周期调度器 | beforeMount、mounted 等时机控制 |
当你写:
cpp
createApp(App).mount('#app')
Vue 并不是"帮你把 HTML 画出来"这么简单,而是:
cpp
1. 创建组件实例
2. 把你的数据变成响应式
3. 把模板变成 render 函数
4. 创建一个 effect 包裹 render
5. effect 自动监听数据变化
6. 数据一变,重新 render + diff + 更新 DOM
也就是说:
- Vue 实例 = 一个会自己重新执行 render 的智能 UI 机器人