运行时,简单理解,就是把vnode渲染到页面中
<div id='app'></div>
<script>
const { render, h } = Vue
const vnode = h('div', {
class: 'test',
}, 'hello render')
const container = document.querySelector('#app')
render(vnode, container)
</script>
整个runtime包含两个环节
所以,我们的目标是
在理解这些之前, 我们需要了解
1 )两个概念
<div>
<h1> hello h1</h1>
<!-- 哈哈 -->
hello div
</div>
2 )关于虚拟DOM
3 ) 区别示例
拿文本节点来说
html dom 节点树表示
<div>text</div>
虚拟dom表示
const vnode = {
type: 'div',
children: 'text'
}
简化版的demo
<div id='app'></div>
<script>
// <div>hello render</div>
const vnode = {
type: 'div',
children: 'hello render'
}
const vnode2 = {
type: 'div',
children: 'hello patch'
}
function render(oldVNode, newVNode, container) {
// 第一次属于挂载,old不存在
if(!oldVNode) {
mount(newVNode, container)
} else {
patch(oldVNode, newVNode, container)
}
}
// 挂载方法
function mount(vnode, container) {
const ele = document.createElement(vnode.type) // 1. 创建当前节点
ele.innerText = vnode.children // 2.插入具体节点
container.appendChild(ele) // 3. 将创建的节点存放到容器中
}
// 卸载操作
function unmount(container) {
container.innerHTML = ''
}
// 更新操作
function patch(oldVNode, newVNode, container) {
// 1. 卸载
unmount(container)
// 2. 重新渲染
const ele = document.createElement(newVNode.type)
ele.innerText = newVNode.children
container.appendChild(ele)
}
// 初始化时去挂载
render(null, vnode, document.querySelector('#app'))
// 延迟两秒进行更新
setTimeout(() => {
render(vnode, vnode2, document.querySelector('#app'))
}, 2000)
</script>
在vue中的vnode对象实际上属性很多,我们精简一下
{
// 是否是一个vnode对象
"__v_isVNode": true,
// 当前节点类型
"type": "div",
// 当前节点的属性
"props": {"class": "test"}
// 它的子节点
"children": "hello render"
}
官方示例
import { h } from 'vue'
// 除了 type 外,其他参数都是可选的
h('div')
h('div', { id: 'foo' })
// attribute 和 property 都可以用于 prop
// Vue 会自动选择正确的方式来分配它
h('div', { class: 'bar', innerHTML: 'hello' })
// class 与 style 可以像在模板中一样
// 用数组或对象的形式书写
h('div', { class: [foo, { bar }], style: { color: 'red' } })
// 事件监听器应以 onXxx 的形式书写
h('div', { onClick: () => {} })
// children 可以是一个字符串
h('div', { id: 'foo' }, 'hello')
// 没有 prop 时可以省略不写
h('div', 'hello')
h('div', [h('span', 'hello')])
// children 数组可以同时包含 vnode 和字符串
h('div', ['hello', h('span', 'hello')])
2 ) render 函数
https://cn.vuejs.org/api/options-rendering.html#render
render(vnode, container)
vnode 虚拟dom树
container: 承载的容器,真实节点的渲染节点位置
通过render函数,我们可以通过编程形式来把虚拟dom转换成真实dom挂载到指定的容器盒子上
1 ) 概述
runtime-core
runtime-dom
2 ) vue为何分开设计
runtime-core 是运行时核心代码
runtime-dom 是浏览器渲染的核心逻辑,多是一个浏览器相关基本操作
runtime-core
包中使用所以
挂载和更新的逻辑处理
baseCreateRenderer
最终返回了一个对象 {render, hydrate, createApp}
,这个对象里有 render 函数
render函数有三个参数: vnode
, container
, isSVG
这第三个参数不用管
const render: RootRenderFunction = (vnode, container, isSVG) => {
// 不存在vnode
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
// 存在vnode更新
patch(container._vnode || null, vnode, container, null, null, null, isSVG)
}
flushPostFlushCbs()
container._vnode = vnode
}
进入patch函数
const patch: PatchFn = (
n1,
n2,
container,
anchor = null,
parentComponent = null,
parentSuspense = null,
isSVG = false,
slotScopeIds = null,
optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren
) => {
if (n1 === n2) {
return
}
// patching & not same type, unmount old tree
if (n1 && !isSameVNodeType(n1, n2)) {
anchor = getNextHostNode(n1)
unmount(n1, parentComponent, parentSuspense, true)
n1 = null
}
if (n2.patchFlag === PatchFlags.BAIL) {
optimized = false
n2.dynamicChildren = null
}
const { type, ref, shapeFlag } = n2
switch (type) {
case Text:
processText(n1, n2, container, anchor)
break
case Comment:
processCommentNode(n1, n2, container, anchor)
break
case Static:
if (n1 == null) {
mountStaticNode(n2, container, anchor, isSVG)
} else if (__DEV__) {
patchStaticNode(n1, n2, container, isSVG)
}
break
case Fragment:
processFragment(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
break
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else if (shapeFlag & ShapeFlags.COMPONENT) {
processComponent(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else if (shapeFlag & ShapeFlags.TELEPORT) {
;(type as typeof TeleportImpl).process(
n1 as TeleportVNode,
n2 as TeleportVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized,
internals
)
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
;(type as typeof SuspenseImpl).process(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized,
internals
)
} else if (__DEV__) {
warn('Invalid VNode type:', type, `(${typeof type})`)
}
}
// set ref
if (ref != null && parentComponent) {
setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2)
}
}
里面有一个switch,根据当前vnode的type来划分vnode的类型进行各自的处理
挂载的操作,本质上都是依赖patch函数来执行的,内部根据type来匹配各类挂载流程
所以,整个挂载的操作,本质上是依赖于patch函数来执行的,内部基于type来执行不同类型节点的挂载
整个render的大致逻辑如下