1.依赖关系
runtime-dom
依赖于runtime-core
,runtime-core
依赖于reactivity
和shared
runtime-core
提供跨平台的渲染方法createRenderer
,用户可以自己传递节点渲染的渲染方法renderOptions
,本身不关心用户使用什么APIruntime-dom
提供了为浏览器而生的渲染方法render
,render
方法调用runtime-core
的createRenderer
方法传递的renderOptions
是runtime-dom
封装好的一系列关于渲染浏览器dom
节点的操作
const renderOptions = Object.assign({ patchProp }, nodeOps);
export const render = (vnode,container)=>{
return createRenderer(renderOptions).render(vnode,container)
}
2.init
2.1 package init
runtime-core/package.json
{
"name": "@vue/runtime-core",
"version": "1.0.0",
"main": "index.js",
"module": "dist/runtime-core.esm-bundler.js",
"unpkg": "dist/runtime-core.global.js",
"buildOptions": {
"name": "RuntimeCore",
"formats": [
"esm-bundler",
"esm-browser",
"cjs",
"global"
]
},
"dependencies": {
"@vue/reactivity": "^3.4.30",
"@vue/shared": "*"
}
}
2.2 调整runtime-dom/index
依赖
import { nodeOps } from "./nodeOps";
import patchProp from "./patchProp";
import {createRenderer} from '@vue/runtime-core'
const renderOptions = Object.assign({ patchProp }, nodeOps);
export { renderOptions };
// 如果我们采用的是runtime-dom中的render方法,我们不需要传递renderOptions,因为会把runtime-dom 这一层的dom处理方法传递进去,主要为浏览器而生的
// 如果我们用的是runtime-core 中的createRenderer,需要用户自己传递renderOptions 并不关心采用什么api
// runtime-dom 是内置的dom api 会去调用createRenderer,传入渲染选项,返回的渲染器有一个render方法
// 采用dom api 进行渲染
export const render = (vnode,container)=>{
return createRenderer(renderOptions).render(vnode,container)
}
export * from "@vue/runtime-core"
3.实现
3.1 init
createRenderer
接受一个参数dom
渲染相关配置,提供一个render
方法,参数为虚拟节点和真实的dom
元素
export function createRenderer(renderOptions) {
const {
insert: hostInsert,
remove: hostRemove,
patchProp: hostPatchProp,
createElement: hostCreateElement,
createText: hostCreateText,
setText: hostSetText,
setElementText: hostSetElementText,
parentNode: hostParentNode,
nextSibling: hostNextSibling,
} = renderOptions;
const render = (vnode, container) => {
// 将虚拟节点变成真实节点进行渲染
};
return {
render,
};
}
3.2 render实现
const mountElement = (vnode, container) => {
console.log(vnode);
const { type, children, props } = vnode;
let el = hostCreateElement(type);
if (props) {
for (let key in props) {
hostPatchProp(el, key, null, props[key]);
}
}
hostSetElementText(el, children);
hostInsert(el, container);
};
const patch = (n1, n2, container) => {
if (n1 == n2) {
return;
}
if (n1 == null) {
mountElement(n2, container);
}
};
// core 中不关心如何渲染
const render = (vnode, container) => {
// 将虚拟节点变成真实节点进行渲染
patch(container._vnode || null, vnode, container);
container._vnode = vnode;
};
vnode如图:
const ele1 = h(
"h1",
{ style: { color: "red" }},
"hello world"
);
const ele2 = h(
"h1",
{ style: { color: "green" } },
"hello world"
);
render(ele1, document.getElementById("app"));
setTimeout(()=>{
render(ele2, document.getElementById("app"));
},3000)
此时可以实现基础渲染,由于我们知道节点children
是文本,可以直接使用文本进行渲染,那如果dom
里面又嵌套一个dom
呢?
3.3 shapeFlag
为了能够判断子节点的类型,定义一个枚举
export const enum ShapeFlags { // vue3提供的形状标识
ELEMENT = 1,
FUNCTIONAL_COMPONENT = 1 << 1,
STATEFUL_COMPONENT = 1 << 2,
TEXT_CHILDREN = 1 << 3,
ARRAY_CHILDREN = 1 << 4,
SLOTS_CHILDREN = 1 << 5,
TELEPORT = 1 << 6,
SUSPENSE = 1 << 7,
COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,
COMPONENT_KEPT_ALIVE = 1 << 9,
COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT
}
比如const ele1 = h("h1", { style: { color: "red" } }, "hello world");
是节点和文本的组合,节点为1,文本为8,采用或运算,得出节点类型数据9,可以看到上图中节点的shapeFlag
为9,采用&运算得出节点具体类型 8&9=1000&1001=1000>0 则证明包含这个类型
const mountChildren = (children, container) => {
for(let i=0;i<children.length;i++) {
// 数组可能为字符串而不是节点
patch(null, children[i], container)
}
};
const { type, children, props, shapeFlag } = vnode;
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
hostSetElementText(el, children);
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
mountChildren(children, el);
}
此处判断了TEXT_CHILDREN
是文本,ARRAY_CHILDREN
是数组
const ele3 = h("h1", { style: { color: "red" } }, [
h("p", "hello"),
h("p", "world"),
]);
可以正确渲染