createRenderer()
创建一个自定义渲染器。通过提供平台特定的节点创建以及更改 API,你可以在非 DOM 环境中也享受到 Vue 核心运行时的特性。
render
用于编程式地创建组件虚拟 DOM 树的函数。
h
创建虚拟 DOM 节点 (vnode)。h用法大全
VNode标识
类型比对 packages/shared/src/shapeFlags.ts
export const enum ShapeFlags {
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
}
后续可以使用 | 运算符进行多种类型存储,使用 & 运算符进行判断是否包含某一种类型( a & b > 0 )
创建虚拟节点
packages/runtime-core/src/vnode.ts
import { isArray, isString, ShapeFlags } from '@vue/shared';
export const Text = Symbol('Text');
export function isVNode(value) {
return !!(value && value.__v_isVnode);
}
export function createVNode(type, props, children = null) {
let shapeFlag = isString(type) ? ShapeFlags.ELEMENT : 0;
const vnode = {
__v_isVnode: true,
shapeFlag,
type,
props,
children,
key: props?.key,
el: null,
};
if (children) {
let type = 0;
if (isArray(children)) {
type = ShapeFlags.ARRAY_CHILDREN;
} else {
children = String(children);
type = ShapeFlags.TEXT_CHILDREN;
}
vnode.shapeFlag |= type;
}
return vnode;
}
虚拟节点不用考虑平台兼容,并且可以将虚拟节点利用js存储并进行比对后再渲染真实dom,不用频繁操作dom元素,性能更好
h方法
packages/runtime-core/src/h.ts
import { isArray, isObject } from '@vue/shared';
import { createVNode, isVNode } from './vnode';
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);
}
}
h方法对创建虚拟节点操作进行了二次封装,使用法变得多种多样
挂载及卸载DOM节点
packages/runtime-core/src/renderer.ts
import { isString, ShapeFlags } from '@vue/shared';
import { createVNode, isSameVNode, Text } from './vnode';
export function createRenderer(renderOptions) {
let {
insert: hostInsert,
createElement: hostCreateElement,
createText: hostCreateText,
remove: hostRemove,
setElementText: hostSetElementText,
setText: hostSetText,
querySelector: hostQuerySelector,
parentNode: hostParentNode,
nextSibling: hostNextSibling,
patchProp: hostPatchProp,
} = renderOptions;
const normalize = (child, i) => {
if (isString(child[i])) {
let vnode = createVNode(Text, null, child[i]);
child[i] = vnode;
return child[i];
}
return child[i];
};
const mountChildren = (children, container) => {
for (let i = 0; i < children.length; i++) {
let child = normalize(children, i);
patch(null, child, container);
}
};
const mountElement = (vnode, container) => {
let { type, props, children, shapeFlag } = vnode;
let el = (vnode.el = hostCreateElement(type));
if (props) {
for (const key in props) {
hostPatchProp(el, key, null, props[key]);
}
}
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
hostSetElementText(el, children);
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
mountChildren(children, el);
}
hostInsert(el, container);
};
const processText = (n1, n2, container) => {
if (n1 === null) {
hostInsert((n2.el = hostCreateText(n2.children)), container);
} else {
const el = (n2.el = n1.el);
if (n1.children !== n2.children) {
hostSetText(el, n2.children);
}
}
};
const patchProps = (oldProps, newProps, el) => {
for (let key in newProps) {
hostPatchProp(el, key, oldProps[key], newProps[key]);
}
for (let key in oldProps) {
if (!newProps[key]) {
hostPatchProp(el, key, oldProps[key], undefined);
}
}
};
const unmountChildren = (children) => {
for (let i = 0; i < children.length; i++) {
unmount(children[i]);
}
};
const patchKeyChildren = (c1, c2, el) => {
};
const patchChildren = (n1, n2, el) => {
const c1 = n1.children;
const c2 = n2.children;
const prevShapeFlag = n1.shapeFlag;
const shapeFlag = n2.shapeFlag;
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
unmountChildren(c1);
}
if (c1 !== c2) {
hostSetElementText(el, c2);
}
} else {
if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
patchKeyChildren(c1, c2, el);
} else {
unmountChildren(c1);
}
} else {
if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
hostSetElementText(el, '');
}
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
mountChildren(c2, el);
}
}
}
};
const patchElement = (n1, n2) => {
let el = (n2.el = n1.el);
let oldProps = n1.props || {};
let newProps = n2.props || {};
patchProps(oldProps, newProps, el);
patchChildren(n1, n2, el);
};
const processElement = (n1, n2, container) => {
if (n1 === null) {
mountElement(n2, container);
} else {
patchElement(n1, n2);
}
};
const patch = (n1, n2, container) => {
if (n1 === n2) {
return;
}
if (n1 && !isSameVNode(n1, n2)) {
unmount(n1);
n1 = null;
}
const { type, shapeFlag } = n2;
switch (type) {
case Text:
processText(n1, n2, container);
break;
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement(n1, n2, container);
}
break;
}
};
const unmount = (vnode) => {
hostRemove(vnode.el);
};
const render = (vnode, container) => {
if (vnode === null) {
if (container._vnode) {
unmount(container._vnode);
}
} else {
patch(container._vnode || null, vnode, container);
}
container._vnode = vnode;
};
return { render };
}
|