Vue3
渲染流程
从零手写一.初始化渲染逻辑
初次调用render方法时,虚拟节点的类型为组件
const processElement = (n1, n2, container) => {
}
const mountComponent = (initialVNode, container) => {
// 组件初始化
}
const processComponent = (n1, n2, container) => {
if (n1 == null) {
mountComponent(n2, container)
}
}
const patch = (n1, n2, container) => {
const { shapeFlag } = n2;
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement(n1, n2, container); // 处理元素类型
} else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
processComponent(n1, n2, container); // 处理组件类型
}
}
const render = (vnode, container) => {
patch(null, vnode, container); // 初始化逻辑老的虚拟节点为null
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
二.组件渲染流程
1.为组件创造实例
const mountComponent = (initialVNode, container) => {
const instance = (initialVNode.component = createComponentInstance(
initialVNode
))
}
1
2
3
4
5
2
3
4
5
export function createComponentInstance(vnode) {
const type = vnode.type;
const instance = { // 组件实例
__v_isVNode: true,
vnode, // 组件对应的虚拟节点
subTree: null, // 组件要渲染的子元素
type, // 组件对象
ctx: {}, // 组件的上下文
props: {}, // 组件的属性
attrs: {}, // 元素本身的属性
slots: {}, // 组件的插槽
setupState: {}, // 组件setup的返回值
isMounted: false // 组件是否被挂载?
}
instance.ctx = { _: instance };
return instance
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2.扩展instance
需要给instance上的属性进行初始化操作
const mountComponent = (initialVNode, container) => {
// 1.创建组件实例
const instance = (initialVNode.component = createComponentInstance(
initialVNode
))
// 2.给instance赋值
setupComponent(instance)
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
组件的启动,核心就是调用setup方法
export function isStatefulComponent(instance) {
return instance.vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT
}
export function setupComponent(instance) {
// 获取虚拟节点属性和插槽的内容
const { props, children } = instance.vnode;
// 1.初始化属性
// 2.初始化插槽
const isStateful = isStatefulComponent(instance)
// 获取setup函数的返回值
const setupResult = isStateful ? setupStatefulComponent(instance) : undefined;
return setupResult
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
提供instance.proxy, 代理实例上一系列属性
import { hasOwn } from "@vue/shared";
export const PublicInstanceProxyHandlers = {
get({ _: instance }, key) { // 做代理
const { setupState } = instance;
if (key[0] !== '$') {
// 说明访问的时普通属性 不以$开头
if (hasOwn(setupState, key)) {
return setupState[key];
}
}
},
set({ _: instance }, key, value) {
const {setupState} = instance;
if(hasOwn(setupState,key)){
setupState[key] = value;
}
return true;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let currentInstance;
function setupStatefulComponent(instance) {
// 通过instance.proxy 访问实例
instance.proxy = new Proxy(instance.ctx,PublicInstanceProxyHandlers);
const Component = instance.type;
const {setup} = Component;
if(setup){
currentInstance = instance;
const setupContext = createSetupContext(instance); // setup中的上下文
const setupResult = setup && setup(instance.props,setupContext);
handleSetupResult(instance,setupResult); // 处理返回值
currentInstance = null;
}else{
finishComponentSetup(instance)
}
}
function createSetupContext(instance){ // 创建setup上下文
return {
attrs:instance.attrs,
slots:instance.slots,
}
}
function handleSetupResult(instance,setupResult){ // 返回值可以是函数
if(isFunction(setupResult)){
instance.render = setupResult
}else if(isObject(setupResult)){
instance.setupState = setupResult
}
finishComponentSetup(instance)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function finishComponentSetup(instance){
const Component = instance.type;
// 没render函数进行模板编译
if(!instance.render){
if(Component.template && !Component.render){
// 模板编译
}
instance.render = Component.render
}
// applyOptions 2.0API兼容处理
// applyOptions(instance,Component);
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
applyOptions 中兼容vue2.0写法
3.初始化渲染effect
保证组件中数据变化可以重新进行组件的渲染
const mountComponent = (initialVNode, container) => {
// 1.创建组件实例
const instance = (initialVNode.component = createComponentInstance(
initialVNode
))
// 2.给instance赋值
setupComponent(instance)
// 3.给组件增加渲染effect
setupRenderEffect(instance, initialVNode, container);
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
const setupRenderEffect = (instance, initialVNode, container) => {
instance.update = effect(function componentEffect(){
if(!instance.isMounted){
const proxyToUse = instance.proxy; // 实例中的代理属性
const subTree = (instance.subTree = instance.render.call(proxyToUse,proxyToUse));
patch(null,subTree,container); // 渲染子树
initialVNode.el = subTree.el; // 组件的el和子树的el是同一个
instance.isMounted = true; // 组件已经挂载完毕
}else{
console.log('更新逻辑')
}
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
render函数中返回的是虚拟节点,例如
const App = {
render : (r) =>h('div', {}, 'hello zf')
}
1
2
3
2
3
三.元素创建流程
h
方法的实现
1.// 1. 只有两个参数 类型 + 孩子 / 类型 + 属性
// 2. 三个参数 最后一个不是数组
// 3. 超过三个 多个参数
export function h(type,propsOrChildren,children){
const l = arguments.length;
if(l === 2){
// 是对象不是数组, 只有一个节点
if(isObject(propsOrChildren) && !isArray(propsOrChildren)){
if(isVnode(propsOrChildren)){
return createVNode(type,null,[propsOrChildren]);
}
return createVNode(type,propsOrChildren); // 没有孩子
}else{
return createVNode(type, null, propsOrChildren)
}
}else{
if(l > 3){
children = Array.prototype.slice.call(arguments,2);
}else if(l === 3 && isVnode(children)){
children = [children]
}
return createVNode(type,propsOrChildren,children)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2.创建真实节点
const mountElement = (vnode, container) => {
// 创建节点保存到vnode中
const { props, shapeFlag, type, children } = vnode
let el = vnode.el = hostCreateElement(type);
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { // 文本直接插入即可
hostSetElementText(el, children);
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
mountChildren(children, el)
}
if (props) { // 处理属性
for (const key in props) {
hostPatchProp(el, key, null, props[key])
}
}
hostInsert(el, container)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
对子节点进行处理
const mountChildren = (children, container) => {
for (let i = 0; i < children.length; i++) {
const child = normalizeVNode(children[i]);
patch(null, child, container)
}
}
1
2
3
4
5
6
2
3
4
5
6
export const Text = Symbol('Text')
export function normalizeVNode(child) { // 对节点进行标识
if(isObject(child)){
return child
}
return createVNode(Text,null,String(child));
}
1
2
3
4
5
6
7
2
3
4
5
6
7
const processText = (n1,n2,container) =>{
if(n1 == null){ // 创建文本插入到容器中
hostInsert(n2.el = hostCreateText(n2.children),container)
}
}
1
2
3
4
5
2
3
4
5