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
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
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
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
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
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
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
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
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
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
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
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
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
2
3
4
5
6
7
8
9
10
11
创建出vnode,交给render函数进行渲染