[Vue源码系列-5]vue3初始化流程原理

856 阅读3分钟

1. VueRuntimeDOM

1.1 runtime-dom

Vue3中将模块分为runtime-core其他平台对应的运行时,其中runtime-dom是专门用来解决浏览器运行时的,这个包中提供了DOM属性操作节点操作的一系列接口

1.2 patchProp

patchProp主要针对不同属性提供不同的patch操作

import { patchClass } from "./modules/class"; // 类名处理
import { patchStyle } from "./modules/style"; // 样式处理
import { patchEvent } from "./modules/events"; // 事件处理
import { patchAttr } from "./modules/attrs"; // 属性处理

export const patchProp = (el,key,prevValue,nextValue) => {
    switch (key) {
        // 先处理特殊逻辑
        case 'class':
            patchClass(el,nextValue)
            break
        case 'style':
            patchStyle(el,prevValue,nextValue)
            break;
        default:
            if(/^on[A-Z]/.test(key)){ // 如果是事件
                patchEvent(el,key,nextValue)
            }else{	// 否则是属性
                patchAttr(el,key,nextValue);
            }
            break;
    }
}

1.3 patch实现

runtime-dom下的modules文件夹下就是各种patch的实现原理

  • patchClass
export const patchClass = (el,value)=>{
    if(value == null){
        value = '';
    }
    el.className = value;// 设置样式名
}
  • 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] = '';
                }
            }
        }
    }
}
  • patchEvent
    1. 给元素缓存一个绑定事件的列表
    2. 如果缓存中没有缓存过来的,而且value有值,需要绑定方法,并缓存起来
    3. 以前绑定过,需要删除掉,删除缓存
    4. 如果前后都有,直接改变 invoker 中 value 属性指向新的事件即可
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) {	  // 以前绑了,当前没有 value
            el.removeEventListener(name, exisitingInvoker);
            invokers[rawName] = undefined;
        }
    }
}
function createInvoker(initialValue) {
    const invoker = (e) => {
        invoker.value(e);
    }
    invoker.value = initialValue;  // 为了能随时更改 value 值
    return invoker;
}
  • patchAttr
export const patchAttr = (el, key, value) => {
    if (value == null) {
        el.removeAttribute(key);
    } else {
        el.setAttribute(key, value);
    }
}

2. nodeOps实现

nodePos中存放着所有节点的操作方法

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

3. runtimeDom实现

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

  • 用户调用的是 runtime-dom ,内部再调用 runtime-core
  • runtime-dom 是用来解决平台差异(浏览器)
// runtime-dom/src/index.ts
// runtime-dom 操作节点、操作属性更新
import { createRenderer } from "@vue/runtime-core/src/index";
import { extend } from "@vue/shared/src";
import { nodeOps } from "./nodeOps";        // 对象
import { patchProp } from "./patchProp";    // 方法

// 渲染时用到的所有方法
const rendererOptions = extend({patchProp},nodeOps);

// vue中 runtime-core 提供了核心的方法,用来处理渲染的,他会使用runtime-dom 中的 api 进行渲染
export function createApp(rootComponent,rootProps = null){
    const app = createRenderer(rendererOptions).createApp(rootComponent,rootProps)
    let { mount } = app
    app.mount = function (container){
        // 清空容器
        container = document.querySelector(container);
        container.innerHTML = '';
        mount(container);   //函数劫持
        // 将组件渲染成DOM元素,进行挂载
    }
    return app;
}

// export * from '@vue/runtime-core' // 后续将 runtime-core 中的方法都在这里暴露

createRenderer

// createRenderer 与平台无关所以定义在 runtime-core 中
function createRenderer(rendererOptions) {
    return {
        createApp(rootComponent, rootProps) { // 用户创建app的参数
            const app = {
                mount(container) { // 挂载的容器
                }
            }
            return app;
        }
    }
}

4. runtimeCore

runtime-core中定于与平台无关的通用代码

4.1 renderer

renderer.ts

import { createAppAPI } from "./apiCreateApp"

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

apiCreateApp.ts

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