从零手写Vue3初始化流程

一.介绍 VueRuntimeDOM

Vue中将runtime模块分为 runtime-core 核心代码 及 其他平台对应的运行时,那么VueRuntimeDOM无疑就是解决浏览器运行时的问题,此包中提供了DOM 属性操作和节点操作一系列接口。

一.patchProp实现

此方法主要针对不同的属性提供不同的patch操作

import { patchClass } from "./modules/class"; // 类名处理
import { patchStyle } from "./modules/style"; // 样式处理
import { patchEvent } from "./modules/events"; // 事件处理
import { patchAttr } from "./modules/attrs"; // 属性处理
import {isOn} from '@vue/shared';
export const patchProp = (el,key,prevValue,nextValue) => {
    switch (key) {
        // 先处理特殊逻辑
        case 'class':
            patchClass(el,nextValue)
            break
        case 'style':
            patchStyle(el,prevValue,nextValue)
            break;
        default:
            if(isOn(key)){
                // 如果是事件
                patchEvent(el,key,nextValue)
            }else{
                patchAttr(el,key,nextValue);
            }
            break;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

patchClass

export const patchClass = (el,value)=>{
    if(value == null){
        value = '';
    }
    el.className = value;// 设置样式名
}
1
2
3
4
5
6

patchStyle

export const patchStyle = (el, prev, next) => {
    const style = el.style;
    if (!next) {
        el.removeAttribute('style');
    } else {

        for (const key in next) {
            style[key] = next[key];
        }
        if (prev) {
            for (const key in prev) {
                if (next[key] == null) {
                    style[key] = '';
                }
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

patchEvent

export const patchEvent = (el, rawName, nextValue) => {
    const invokers = el._vei || (el._vei = {});
    const exisitingInvoker = invokers[rawName];
    if (nextValue && exisitingInvoker) { // 如果绑定过,则替换为新的
        exisitingInvoker.value = nextValue;
    } else {
        const name = rawName.slice(2).toLowerCase();
        if (nextValue) {  // 绑定新值
            const invoker = (invokers[rawName] = createInvoker(nextValue));
            el.addEventListener(name, invoker);
        } else if (exisitingInvoker) {
            el.removeEventListener(name, exisitingInvoker);
            invokers[rawName] = undefined;
        }
    }
}
function createInvoker(initialValue) {
    const invoker = (e) => {
        invoker.value(e);
    }
    invoker.value = initialValue;
    return invoker;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

patchAttr

export const patchAttr = (el, key, value) => {
    if (value == null) {
        el.removeAttribute(key);
    } else {
        el.setAttribute(key, value);
    }
}
1
2
3
4
5
6
7

二.nodeOps实现

这里存放着所有的节点操作的方法

export const nodeOps = {
    insert: (child, parent, anchor) => { // 增加
        parent.insertBefore(child, anchor || null)
    },
    remove: child => { // 删除
        const parent = child.parentNode
        if (parent) {
            parent.removeChild(child);
        }
    },
    // 创建元素
    createElement: tag => document.createElement(tag),
    // 创建文本
    createText: text => document.createTextNode(text),
    // 设置元素内容
    setElementText: (el, text) => {
        el.textContent = text
    },
    // 设置文本内容
    setText: (node, text) => {
        node.nodeValue = text
    },
    parentNode: node => node.parentNode, // 获取父节点
    nextSibling: node => node.nextSibling, // 获取下个兄弟
    querySelector: selector => document.querySelector(selector)
}
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

三 .runtimeDom实现

用户调用的createApp函数就在这里被声明

// runtime-dom主要提供dom操作的方法
import { extend } from "@vue/shared"
import { patchProp } from './patchProp'
import { nodeOps } from './nodeOps'


// runtimeDom中对dom操作的所有选项
const rendererOptions = extend({ patchProp }, nodeOps);

// 用户调用的createApp方法,此时才会创建渲染器
export const createApp = (rootComponent, rootProps = null) => {
    const app = createRenderer(rendererOptions).createApp(rootComponent, rootProps);
    const { mount } = app;
    app.mount = function (container) {
        container = document.querySelector(container);
        container.innerHTML = ''; // 清空容器内容
        const proxy = mount(container); // 执行挂载逻辑
        return proxy;
    }
    return app
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// -----------这些逻辑移动到core中与平台代码无关--------------
function createRenderer(rendererOptions) {
    return {
        createApp(rootComponent, rootProps) { // 用户创建app的参数
            const app = {
                mount(container) { // 挂载的容器

                }
            }
            return app;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

四.runtimeCore实现

renderer.ts

import { createAppAPI } from "./apiCreateApp"

export function createRenderer(rendererOptions) { // 渲染时所到的api
    const render = (vnode,container) =>{ // 核心渲染方法
		// 将虚拟节点转化成真实节点插入到容器中
    }
    return {
        createApp:createAppAPI(render)
    }
}
1
2
3
4
5
6
7
8
9
10

apiCreateApp.ts

export function createAppAPI(render){
    return function createApp(rootComponent,rootProps = null){
        const app = {
            _props:rootProps, // 属性
            _component:rootComponent, // 组件
            _container:null,
            mount(rootContainer){
                // 1.通过rootComponent 创建vnode
                // 2.调用render方法将vnode渲染到rootContainer中
            }
        }
        return app;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

五.VNode实现

import { createVNode } from "./vnode";

export function createAppAPI(render){
    return function createApp(rootComponent,rootProps = null){
        const app = {
            _props:rootProps, // 属性
            _component:rootComponent, // 组件
            _container:null,
            mount(rootContainer){
                // 1.通过rootComponent 创建vnode
                // 2.调用render方法将vnode渲染到rootContainer中
                const vnode = createVNode(rootComponent,rootProps);
                render(vnode,rootContainer);
                app._container = rootContainer
            }
        }
        return app;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

vnode.ts

import { isObject, isString, ShapeFlags } from "@vue/shared/src"
export const createVNode = (type, props, children = null) => {
    const shapeFlag = isString(type) ?
        ShapeFlags.ELEMENT
        : isObject(type) ?
            ShapeFlags.STATEFUL_COMPONENT
            : 0
    const vnode = {
        type,
        props,
        children,
        key: props && props.key, // 用于diff算法
        el: null, // 虚拟节点对应的真实节点
        shapeFlag // 自己是什么类型
    }
    normalizeChildren(vnode, children); // 根据子节点计算孩子类型
    return vnode
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function normalizeChildren(vnode, children) {
    let type = 0;
    if(children == null){

    }else if(isArray(children)){ 
        type = ShapeFlags.ARRAY_CHILDREN; // 数组
    }else{
        type = ShapeFlags.TEXT_CHILDREN;  // 文本
    }
    vnode.shapeFlag |= type
}
1
2
3
4
5
6
7
8
9
10
11

创建出vnode,交给render函数进行渲染