从零手写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

二.组件渲染流程

1.为组件创造实例

const mountComponent = (initialVNode, container) => {
    const instance = (initialVNode.component = createComponentInstance(
        initialVNode
    ))
}
1
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.扩展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

组件的启动,核心就是调用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

提供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
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
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

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
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

render函数中返回的是虚拟节点,例如

const App = {
    render : (r) =>h('div', {}, 'hello zf')
}
1
2
3

三.元素创建流程

1.h方法的实现

// 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.创建真实节点

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

对子节点进行处理

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
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
const processText = (n1,n2,container) =>{
    if(n1 == null){ // 创建文本插入到容器中
        hostInsert(n2.el = hostCreateText(n2.children),container)
    }
}
1
2
3
4
5