源码探究 runtime-core模块

源码探究 runtime-core模块
Breezli源码探究 runtime-core 模块
接下来我们将对这行核心代码的源码进行全流程追踪
1 | createApp(App).mount('#root') |
注:在此之前我们已经通过
1 const rootContainer = document.querySelector('#app')拿到 rootContainer :
拆分成两个步骤
createApp(App)
.mount(“#root”)
createApp(App)
关于 App
我们先来看用户这边的操作
App.js
1 | export const App = { |
这里给 h 辅助函数传入三个参数
type:要创建的 HTML 标签名或组件选项对象(div)
props:标签属性,可以是一个 对象 或 数组
children:子节点,可以是 字符串、数字、数组、其他虚拟 DOM 节点
来看看 h 辅助函数
1 | function h(type, props, children) { |
接着 createVNode
1 | function createVNode(type, props, children) { |
现在 App 的结构就变成了这样
render()
1
2
3
4
5
6 type, // 类型
props, // 属性
children, // 孩子
el: null, // 对应的真实dom
component: null, // 组件实例
key: props === null || props === void 0 ? void 0 : props.key, // 唯一标识setup()
关于 createApp
传入结构 App->rootComponent
1 | function createApp(rootComponent) { |
由此进入.mount(“#root”)部分
.mount(“#root”)
接着看 mount 方法的内部
// 挂载回根容器
// 先把根组件转换成虚拟节点 vnode
// 之后所有的操作都会基于 vnode 做处理
1 | function createApp(rootComponent) { |
createVNode
rootComponent 传进来了 {render: ƒ, setup: ƒ}
1 | function createVNode(type, props, children) { |
现在 {render: ƒ, setup: ƒ} 被挂到了虚拟节点的第一个属性 type 上
现在 vnode 的结构就变成了这样
type
render()
1
2
3
4
5
6 type, // 类型
props, // 属性
children, // 孩子
el: null, // 对应的真实dom
component: null, // 组件实例
key: props === null || props === void 0 ? void 0 : props.key, // 唯一标识setup()
props
children
el
component
key
render
1 | render(vnode, rootContainer) // 将 vnode 渲染到 container 中 |
vnode : 刚才写的结构 👆
rootContainer : div#app
1 | function render(vnode, container) { |
patch 拆箱回调
拆箱函数,深层回调的起点
1 | function patch(vnode, container) { |
processComponent 组件分支(首次进入)
1 | function processComponent(vnode, container) { |
mountComponent 组件初始化分支
1 | function mountComponent(vnode, container) { |
createComponentInstance
1 | function createComponentInstance(vnode) { |
返回的 instance 结构
vnode(新增) :继承当前传过来的整个 vnode 结构
type
render()
1
2
3
4
5
6 type, // 类型
props, // 属性
children, // 孩子
el: null, // 对应的真实dom
component: null, // 组件实例
key: props === null || props === void 0 ? void 0 : props.key, // 唯一标识setup()
props
slots(新增)
proxy(新增)
children
el
component
key
setupComponent
instance 的结构见上 👆
1 | function setupComponent(instance) { |
setupStatefulComponent
按这个例子来说,解构出来的 setup 结构为
1
2
3
4
5 ƒ setup() {
return {
msg: 'mini-vue',
}
}
1 | function setupStatefulComponent(instance) { |
如果 setup 存在
handleSetupResult (处理组件的 setup)
传入 instance & {msg: ‘mini-vue’}
1 | function handleSetupResult(instance: any, setupResult: any) { |
finishComponentSetup (处理组件的 render)
1 | function finishComponentSetup(instance) { |
这里 instance 又新增了 render 属性
如果 setup 不存在
直接处理组件的 render
1 | finishComponentSetup(instance) |
到这里就是组件初始化的最底层了,接下来我们回到 mountComponent 完成 setupRenderEffect 部分
setupRenderEffect
要传入三个参数 instance, vnode, container
已知 container 作为容器为
到这里我们先梳理一下目前 instance & vnode 内部的参数
instance vnode 属性 ✅ ✅ children ✅ ✅ component ✅ ✅ el ✅ ✅ key ✅ ✅ props ✅ 内部是右边 vnode 的前五个属性 ⛔ vnode ✅ { render: ƒ, setup: ƒ } ✅ { render: ƒ, setup: ƒ } type ✅ ⛔ proxy ✅ ⛔ slots ✅ { msg: ‘mini-vue’ } ⛔ setupState
1 | export function setupRenderEffect(instance: any, vnode: any, container: any) { |
回顾 h 函数
1 | function h(type, props, children) { |
再看这时候的 type, props, children 也就是用户样例
type:’div’
props:{ id: ‘root’, class: [‘red’, ‘hard’] }
children:’hi, mini-vue’
1 | function createVNode(type, props, children) { |
那么接下来的 subTree 结构就是
type = ‘div’
props = { id: ‘root’, class: [‘red’, ‘hard’] }
children = ‘hi, mini-vue’
el
component
key
以及 container 结构
1
2
3 <div id="app">
<div id="root" class="red, hard">hi, mini-vue</div>
</div>
返回 setupRenderEffect 函数,继续看下面的逻辑
1 | export function setupRenderEffect(instance: any, vnode: any, container: any) { |
此时调用 patch 回调拆箱
1 | function patch(vnode, container) { |
快进到下面的 processElement 元素分支
updateComponent 更新分支
!!! 第一次看先掠过这一部分,先看后文 ‘processElement 元素分支’ 实现拆箱 !!!
processElement 元素分支
1 | function processElement(vnode, container) { |
mountElement 元素初始化分支
若以首次样例传入为例,进入文本节点
若以二次样例传入为例,进入数组节点
1 | function mountElement(vnode, container) { |
(首次样例) for (const key in props) 的过程
1 <div id="root">...</div>
1 <div id="root" class="red,hard">...</div>
(首次样例) container.append(el) 的结果
1
2
3
4
5 <!-- container -->
<div id="app">...</div>
> 0 =
<div id="root" class="red, hard">...</div>
> > 0 = hi, mini-vue
此时返回 setupRenderEffect 的最后一行
1 | vnode.el = subTree.el |
此时打开 html 网页,就可以看到屏幕上的 hi, mini-vue 字样了
!!! 二次复盘 !!!
现在我们将 h 的 children 参数改成数组
1
2
3
4
5
6
7 return h(
// 创建虚拟 DOM 节点,接收三个参数
'div',
{ id: 'root', class: ['red', 'hard'] },
// 'hi, mini-vue' //string类型
[h('p', { class: 'red' }, 'hi'), h('p', { class: 'blue' }, 'mini-vue')] // array类型
)并且在 index.html 中添加 css 样式
1
2
3
4
5
6
7
8
9
10
11 ...
<title>Document</title>
<style>
.red {
color: red;
}
.blue {
color: blue;
}
</style>
...
还记得 children 判断类型的分支吗,就在刚写的 mountElement 函数里,返回去再看看,看完再回到这里
mountChildren
所以现在二次样例中的数据如下
vnode.children 子节点标签数组
1 ;[h('p', { class: 'red' }, 'hi'), h('p', { class: 'blue' }, 'mini-vue')]container 根容器
1 <div id="app">...</div>
1 | function mountChildren(vnode: any, container: any) { |
取出来的 v 依此是
第一次 (调用 h) 第二次 (调用 h) type p p props { class: ‘ red ‘ } { class: ‘ blue ‘ } children ‘ hi ‘ ‘ mini-vue ‘