赞
踩
创建一个自定义渲染器。通过提供平台特定的节点创建以及更改 API,你可以在非 DOM 环境中也享受到 Vue 核心运行时的特性。
用于编程式地创建组件虚拟 DOM 树的函数。
创建虚拟 DOM 节点 (vnode)。h用法大全
类型比对
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); } /** * 创建虚拟节点 * @param type 虚拟节点类型 * @param props 属性 * @param children 子节点 */ export function createVNode(type, props, children = null) { let shapeFlag = isString(type) ? ShapeFlags.ELEMENT : 0; // 虚拟dom,可以跨平台,性能好 const vnode = { __v_isVnode: true, // 是否是虚拟节点 shapeFlag, // 类型标识 type, // 节点类型 props, // 属性 children, // 子节点 key: props?.key, // key /** * 对应的真实节点,后续diff算法比对两个vnode时会替换新的属性值,并更新el */ el: null, }; if (children) { let type = 0; if (isArray(children)) { type = ShapeFlags.ARRAY_CHILDREN; } else { children = String(children); type = ShapeFlags.TEXT_CHILDREN; } // 通过位运算将当前vnode类型及子节点类型存储起来 vnode.shapeFlag |= type; } return vnode; }
虚拟节点不用考虑平台兼容,并且可以将虚拟节点利用js存储并进行比对后再渲染真实dom,不用频繁操作dom元素,性能更好
packages/runtime-core/src/h.ts
/* // type only h('div') // type + props h('div', {}) // type + omit props + children // Omit props does NOT support named slots h('div', []) // array h('div', 'foo') // text h('div', h('br')) // vnode h(Component, () => {}) // default slot // type + props + children h('div', {}, []) // array h('div', {}, 'foo') // text h('div', {}, h('br')) // vnode h(Component, {}, () => {}) // default slot h(Component, {}, {}) // named slots // named slots without props requires explicit `null` to avoid ambiguity h(Component, null, {}) **/ 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方法对创建虚拟节点操作进行了二次封装,使用法变得多种多样
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; // 挂载真实dom到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); } // 插入真实dom到容器中 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) => { }; // 比较两个节点的子节点,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) { // 数组 数组 diff 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) { // 卸载dom if (container._vnode) { unmount(container._vnode); } } else { // 初始化及更新 patch(container._vnode || null, vnode, container); } // 缓存下次直接更新 container._vnode = vnode; }; return { render }; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。