这里,我们通过Snabbdom
学习VirtualDom基本思想
使用parcel创建项目
初始化
mkdir snabbdom-demo
cd snabbdom-demo
npm install -g parcel-bundler
npm init -y
# package.json
...
"scripts": {
"dev": "parcel index.html --open",
"build": "parcel build index.html"
}
...
导入Snabbdom
const snabbdom = require('snabbdom'); // commonjs
const { h, thunk, init } from 'snabbdom'; // es6
扩展内容:CommonJS和ES6模块的区别
简单使用
var { h, init } = require('snabbdom');
// 1. hello world
const patch = init([])
// 2. div 设置
// const vnode = h('div#container.cls', 'Hello World');
let vnode = h('div#container.cls', [
h('h1', 'Hello'),
h('p', '我是P')
])
let app = document.getElementById('app');
// 3. patch
let oldVnode = patch(app, vnode);
// 4. 更新
setTimeout(() => {
vnode = h('div#container.cls', [
h('h1', 'Hi'),
h('p', '我还是P')
]);
oldVnode = patch(oldVnode, vnode);
}, 1500);
清空节点:
- 创建注释节点
oldVnode = patch(oldVnode, h('!'));
模块
Snabbdom
核心库不能处理元素属性、样式、事件,需要使用模块进行处理- 常用模块
- attributes
- 设置
DOM
元素的属性,设置setAttribute()
- 处理布尔类型的属性
- 设置
- props
- 与
attributes
类似,设置DOM
元素的属性element[attr]=value
- 不处理布尔类型的属性
- 与
- class
- 切换类样式
- 给元素设置样式使用
sel
选择器
- dataset
- 设置
data-*
自定义属性
- 设置
- eventlisteners
- 注册和移除事件
- style
- 设置行内样式,支持动画
- 增加
delayed/remove/destroy
属性
- attributes
import { h, init } from 'snabbdom';
// 导入模块
import style from 'snabbdom/modules/style';
import eventlisteners from 'snabbdom/modules/eventlisteners';
// 注册模块
const patch = init([style, eventlisteners]);
// 使用
let vnode = h('div', {
style: {
backgroundColor: 'red'
},
on: {
click: eventHandler
}
}, [
h('h1', 'Hello~'),
h('p', '我是p')
])
function eventHandler() {
console.log('click me');
}
let app = document.getElementById('app');
patch(app, vnode);
Snabbdom 源码解析
目录结构
├── src
│ ├── h.ts
│ ├── helpers
│ │ └── attachto.ts
│ ├── hooks.ts ## 钩子函数
│ ├── htmldomapi.ts ## dom操作相关
│ ├── is.ts
│ ├── modules ## 模块
│ │ ├── attributes.ts
│ │ ├── class.ts
│ │ ├── dataset.ts
│ │ ├── eventlisteners.ts
│ │ ├── hero.ts
│ │ ├── module.ts
│ │ ├── props.ts
│ │ └── style.ts
│ ├── snabbdom.bundle.ts ## 入口文件
│ ├── snabbdom.ts ## 主要文件,导出init,h,thunk函数
│ ├── thunk.ts
│ ├── tovnode.ts ## Dom转化成vnode
│ └── vnode.ts
Snabbdom核心
- 使用
h
函数创建javascript
对象(VNode
)描述真是DOM
init()
设置模块,创建patch()
patch()
比较新旧Vnode
- 把变化的内容更新到真实DOM树上
h函数
Vue中的h函数
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app');
h()
函数用来创建VNode
h函数的重载
h
函数导出重载
// 定义重载形式
export function h(sel: string): VNode;
export function h(sel: string, data: VNodeData): VNode;
export function h(sel: string, children: VNodeChildren): VNode;
export function h(sel: string, data: VNodeData, children: VNodeChildren): VNode;
// 具体重载实现
export function h(sel: any, b?: any, c?: any): VNode {
var data: VNodeData = {}, children: any, text: any, i: number;
// 处理参数,实现重载机制
if (c !== undefined) {
// 处理3个参数的情况
// sel、data、children/text
data = b;
// c为数组,存储到children中
if (is.array(c)) { children = c; }
// 这里c为字符串或数字
else if (is.primitive(c)) { text = c; }
// c为VNode
else if (c && c.sel) { children = [c]; }
} else if (b !== undefined) {
// 处理2个参数的情况
// b为数组,存储到children中
if (is.array(b)) { children = b; }
// b为数字或字符串
else if (is.primitive(b)) { text = b; }
// b为vnode
else if (b && b.sel) { children = [b]; }
// b为VNodeData
else { data = b; }
}
// 判断children是否有值
if (children !== undefined) {
// 遍历
for (i = 0; i < children.length; ++i) {
// 判断是字符串或者数字,则使用vnode函数创建文本节点
if (is.primitive(children[i])) children[i] = vnode(undefined, undefined, undefined, children[i], undefined);
}
}
// 判断如果是svg,则使用addNS添加命名空间
if (
sel[0] === 's' && sel[1] === 'v' && sel[2] === 'g' &&
(sel.length === 3 || sel[3] === '.' || sel[3] === '#')
) {
addNS(data, children, sel);
}
// 循环调用vnode函数创建虚拟节点
return vnode(sel, data, children, text, undefined);
};
export default h;
vode函数
import {Hooks} from './hooks';
import {AttachData} from './helpers/attachto'
import {VNodeStyle} from './modules/style'
import {On} from './modules/eventlisteners'
import {Attrs} from './modules/attributes'
import {Classes} from './modules/class'
import {Props} from './modules/props'
import {Dataset} from './modules/dataset'
import {Hero} from './modules/hero'
export type Key = string | number;
// VNode接口,约束VNode对象需要包含的属性
export interface VNode {
// 选择器
sel: string | undefined;
// 节点所需数据
data: VNodeData | undefined;
// 子节点,和text互斥
children: Array<VNode | string> | undefined;
// 记录vnode对应的真是dom
elm: Node | undefined;
// 标签内容,和children互斥
text: string | undefined;
// 节点key,优化用
key: Key | undefined;
}
export interface VNodeData {
props?: Props;
attrs?: Attrs;
class?: Classes;
style?: VNodeStyle;
dataset?: Dataset;
on?: On;
hero?: Hero;
attachData?: AttachData;
hook?: Hooks;
key?: Key;
ns?: string; // for SVGs
fn?: () => VNode; // for thunks
args?: Array<any>; // for thunks
[key: string]: any; // for any other 3rd party module
}
// 返回VNode类型的对象,描述节点
export function vnode(sel: string | undefined,
data: any | undefined,
children: Array<VNode | string> | undefined,
text: string | undefined,
elm: Element | Text | undefined): VNode {
let key = data === undefined ? undefined : data.key;
return {sel, data, children, text, elm, key};
}
export default vnode;
VNode渲染真是Dom
patch(oldVNode, newVNode)
- 打补丁,把新节点中变化的内容渲染到真实
DOM
,返回新节点作为下次处理的旧节点 - 对比新旧
VNode
是否是相同节点(sel
和key
相同) - 如果不是相同节点,删除之前的内容,重新渲染
- 如果是相同节点,再判断新的
VNode
是否有text
,如果有且和之前text
不同,则直接更新文本内容 - 如果新的
VNode
有children
,使用diff
算法判断子节点是否有变化 diff
过程只进行同层比较
init函数
export function init(modules: Array<Partial<Module>>, domApi?: DOMAPI) {
let i: number, j: number, cbs = ({} as ModuleHooks);
const api: DOMAPI = domApi !== undefined ? domApi : htmlDomApi;
for (i = 0; i < hooks.length; ++i) {
cbs[hooks[i]] = [];
for (j = 0; j < modules.length; ++j) {
const hook = modules[j][hooks[i]];
if (hook !== undefined) {
(cbs[hooks[i]] as Array<any>).push(hook);
}
}
}
function emptyNodeAt(elm: Element) {
const id = elm.id ? '#' + elm.id : '';
const c = elm.className ? '.' + elm.className.split(' ').join('.') : '';
return vnode(api.tagName(elm).toLowerCase() + id + c, {}, [], undefined, elm);
}
function createRmCb(childElm: Node, listeners: number) {
return function rmCb() {
if (--listeners === 0) {
const parent = api.parentNode(childElm);
api.removeChild(parent, childElm);
}
};
}
function createElm(vnode: VNode, insertedVnodeQueue: VNodeQueue): Node {
let i: any, data = vnode.data;
if (data !== undefined) {
if (isDef(i = data.hook) && isDef(i = i.init)) {
i(vnode);
data = vnode.data;
}
}
let children = vnode.children, sel = vnode.sel;
if (sel === '!') {
if (isUndef(vnode.text)) {
vnode.text = '';
}
vnode.elm = api.createComment(vnode.text as string);
} else if (sel !== undefined) {
// Parse selector
const hashIdx = sel.indexOf('#');
const dotIdx = sel.indexOf('.', hashIdx);
const hash = hashIdx > 0 ? hashIdx : sel.length;
const dot = dotIdx > 0 ? dotIdx : sel.length;
const tag = hashIdx !== -1 || dotIdx !== -1 ? sel.slice(0, Math.min(hash, dot)) : sel;
const elm = vnode.elm = isDef(data) && isDef(i = (data as VNodeData).ns) ? api.createElementNS(i, tag)
: api.createElement(tag);
if (hash < dot) elm.setAttribute('id', sel.slice(hash + 1, dot));
if (dotIdx > 0) elm.setAttribute('class', sel.slice(dot + 1).replace(/\./g, ' '));
for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode);
if (is.array(children)) {
for (i = 0; i < children.length; ++i) {
const ch = children[i];
if (ch != null) {
api.appendChild(elm, createElm(ch as VNode, insertedVnodeQueue));
}
}
} else if (is.primitive(vnode.text)) {
api.appendChild(elm, api.createTextNode(vnode.text));
}
i = (vnode.data as VNodeData).hook; // Reuse variable
if (isDef(i)) {
if (i.create) i.create(emptyNode, vnode);
if (i.insert) insertedVnodeQueue.push(vnode);
}
} else {
vnode.elm = api.createTextNode(vnode.text as string);
}
return vnode.elm;
}
function addVnodes(parentElm: Node,
before: Node | null,
vnodes: Array<VNode>,
startIdx: number,
endIdx: number,
insertedVnodeQueue: VNodeQueue) {
for (; startIdx <= endIdx; ++startIdx) {
const ch = vnodes[startIdx];
if (ch != null) {
api.insertBefore(parentElm, createElm(ch, insertedVnodeQueue), before);
}
}
}
function invokeDestroyHook(vnode: VNode) {
let i: any, j: number, data = vnode.data;
if (data !== undefined) {
if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode);
for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode);
if (vnode.children !== undefined) {
for (j = 0; j < vnode.children.length; ++j) {
i = vnode.children[j];
if (i != null && typeof i !== "string") {
invokeDestroyHook(i);
}
}
}
}
}
function removeVnodes(parentElm: Node,
vnodes: Array<VNode>,
startIdx: number,
endIdx: number): void {
for (; startIdx <= endIdx; ++startIdx) {
let i: any, listeners: number, rm: () => void, ch = vnodes[startIdx];
if (ch != null) {
if (isDef(ch.sel)) {
invokeDestroyHook(ch);
listeners = cbs.remove.length + 1;
rm = createRmCb(ch.elm as Node, listeners);
for (i = 0; i < cbs.remove.length; ++i) cbs.remove[i](ch, rm);
if (isDef(i = ch.data) && isDef(i = i.hook) && isDef(i = i.remove)) {
i(ch, rm);
} else {
rm();
}
} else { // Text node
api.removeChild(parentElm, ch.elm as Node);
}
}
}
}
function updateChildren(parentElm: Node,
oldCh: Array<VNode>,
newCh: Array<VNode>,
insertedVnodeQueue: VNodeQueue) {
let oldStartIdx = 0, newStartIdx = 0;
let oldEndIdx = oldCh.length - 1;
let oldStartVnode = oldCh[0];
let oldEndVnode = oldCh[oldEndIdx];
let newEndIdx = newCh.length - 1;
let newStartVnode = newCh[0];
let newEndVnode = newCh[newEndIdx];
let oldKeyToIdx: any;
let idxInOld: number;
let elmToMove: VNode;
let before: any;
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (oldStartVnode == null) {
oldStartVnode = oldCh[++oldStartIdx]; // Vnode might have been moved left
} else if (oldEndVnode == null) {
oldEndVnode = oldCh[--oldEndIdx];
} else if (newStartVnode == null) {
newStartVnode = newCh[++newStartIdx];
} else if (newEndVnode == null) {
newEndVnode = newCh[--newEndIdx];
} else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);
oldStartVnode = oldCh[++oldStartIdx];
newStartVnode = newCh[++newStartIdx];
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);
oldEndVnode = oldCh[--oldEndIdx];
newEndVnode = newCh[--newEndIdx];
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);
api.insertBefore(parentElm, oldStartVnode.elm as Node, api.nextSibling(oldEndVnode.elm as Node));
oldStartVnode = oldCh[++oldStartIdx];
newEndVnode = newCh[--newEndIdx];
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
api.insertBefore(parentElm, oldEndVnode.elm as Node, oldStartVnode.elm as Node);
oldEndVnode = oldCh[--oldEndIdx];
newStartVnode = newCh[++newStartIdx];
} else {
if (oldKeyToIdx === undefined) {
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
}
idxInOld = oldKeyToIdx[newStartVnode.key as string];
if (isUndef(idxInOld)) { // New element
api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm as Node);
newStartVnode = newCh[++newStartIdx];
} else {
elmToMove = oldCh[idxInOld];
if (elmToMove.sel !== newStartVnode.sel) {
api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm as Node);
} else {
patchVnode(elmToMove, newStartVnode, insertedVnodeQueue);
oldCh[idxInOld] = undefined as any;
api.insertBefore(parentElm, (elmToMove.elm as Node), oldStartVnode.elm as Node);
}
newStartVnode = newCh[++newStartIdx];
}
}
}
if (oldStartIdx <= oldEndIdx || newStartIdx <= newEndIdx) {
if (oldStartIdx > oldEndIdx) {
before = newCh[newEndIdx+1] == null ? null : newCh[newEndIdx+1].elm;
addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);
} else {
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
}
}
}
function patchVnode(oldVnode: VNode, vnode: VNode, insertedVnodeQueue: VNodeQueue) {
let i: any, hook: any;
if (isDef(i = vnode.data) && isDef(hook = i.hook) && isDef(i = hook.prepatch)) {
i(oldVnode, vnode);
}
const elm = vnode.elm = (oldVnode.elm as Node);
let oldCh = oldVnode.children;
let ch = vnode.children;
if (oldVnode === vnode) return;
if (vnode.data !== undefined) {
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode);
i = vnode.data.hook;
if (isDef(i) && isDef(i = i.update)) i(oldVnode, vnode);
}
if (isUndef(vnode.text)) {
if (isDef(oldCh) && isDef(ch)) {
if (oldCh !== ch) updateChildren(elm, oldCh as Array<VNode>, ch as Array<VNode>, insertedVnodeQueue);
} else if (isDef(ch)) {
if (isDef(oldVnode.text)) api.setTextContent(elm, '');
addVnodes(elm, null, ch as Array<VNode>, 0, (ch as Array<VNode>).length - 1, insertedVnodeQueue);
} else if (isDef(oldCh)) {
removeVnodes(elm, oldCh as Array<VNode>, 0, (oldCh as Array<VNode>).length - 1);
} else if (isDef(oldVnode.text)) {
api.setTextContent(elm, '');
}
} else if (oldVnode.text !== vnode.text) {
if (isDef(oldCh)) {
removeVnodes(elm, oldCh as Array<VNode>, 0, (oldCh as Array<VNode>).length - 1);
}
api.setTextContent(elm, vnode.text as string);
}
if (isDef(hook) && isDef(i = hook.postpatch)) {
i(oldVnode, vnode);
}
}
return function patch(oldVnode: VNode | Element, vnode: VNode): VNode {
let i: number, elm: Node, parent: Node;
const insertedVnodeQueue: VNodeQueue = [];
for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]();
if (!isVnode(oldVnode)) {
oldVnode = emptyNodeAt(oldVnode);
}
if (sameVnode(oldVnode, vnode)) {
patchVnode(oldVnode, vnode, insertedVnodeQueue);
} else {
elm = oldVnode.elm as Node;
parent = api.parentNode(elm);
createElm(vnode, insertedVnodeQueue);
if (parent !== null) {
api.insertBefore(parent, vnode.elm as Node, api.nextSibling(elm));
removeVnodes(parent, [oldVnode], 0, 0);
}
}
for (i = 0; i < insertedVnodeQueue.length; ++i) {
(((insertedVnodeQueue[i].data as VNodeData).hook as Hooks).insert as any)(insertedVnodeQueue[i]);
}
for (i = 0; i < cbs.post.length; ++i) cbs.post[i]();
return vnode;
};
}
- 入参:
modules
为模块数组,domApi
为封装的DOM操作对象 - 返回
patch
函数,一个函数内部返回另外一个函数为高阶函数,形成闭包,可以访问到外部函数的成员,patch函数可以访问到modules
和domApi
- 初始化钩子函数,遍历
modules
,保存到module
的钩子函数到cbs
中
path函数
- 首先遍历执行
cbs.pre
钩子函数 - 判断,如果
oldVnode
不是VNode
,则使用emptyNodeAt
转成VNode
- 如果有
sel
属性,则是VNode
emptyNodeAt
方法
function emptyNodeAt(elm: Element) { const id = elm.id ? '#' + elm.id : ''; const c = elm.className ? '.' + elm.className.split(' ').join('.') : ''; return vnode(api.tagName(elm).toLowerCase() + id + c, {}, [], undefined, elm); }
- 如果有
- 判断两个虚拟节点是否相同
sameVnode
方法
function sameVnode(vnode1: VNode, vnode2: VNode): boolean { return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel; }
- 如果相同,则获取差异,更新到
DOM
上 - 如果不同,则使用
vnode
创建相应的DOM
- 获取旧节点
DOM
元素 - 获取旧节点的父节点
- 使用新的
vnode
创建元素 - 如果父节点存在,则插入新
DOM
节点,移出就DOM
节点
- 获取旧节点
- 对于新插入的节点,执行
insert
钩子函数 - 遍历,执行
post
钩子函数 - 返回新的
vnode
createElm函数
function createElm(vnode: VNode, insertedVnodeQueue: VNodeQueue): Node {
let i: any, data = vnode.data;
if (data !== undefined) {
if (isDef(i = data.hook) && isDef(i = i.init)) {
i(vnode);
data = vnode.data;
}
}
let children = vnode.children, sel = vnode.sel;
if (sel === '!') {
if (isUndef(vnode.text)) {
vnode.text = '';
}
vnode.elm = api.createComment(vnode.text as string);
} else if (sel !== undefined) {
// Parse selector
const hashIdx = sel.indexOf('#');
const dotIdx = sel.indexOf('.', hashIdx);
const hash = hashIdx > 0 ? hashIdx : sel.length;
const dot = dotIdx > 0 ? dotIdx : sel.length;
const tag = hashIdx !== -1 || dotIdx !== -1 ? sel.slice(0, Math.min(hash, dot)) : sel;
const elm = vnode.elm = isDef(data) && isDef(i = (data as VNodeData).ns) ? api.createElementNS(i, tag)
: api.createElement(tag);
if (hash < dot) elm.setAttribute('id', sel.slice(hash + 1, dot));
if (dotIdx > 0) elm.setAttribute('class', sel.slice(dot + 1).replace(/\./g, ' '));
for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode);
if (is.array(children)) {
for (i = 0; i < children.length; ++i) {
const ch = children[i];
if (ch != null) {
api.appendChild(elm, createElm(ch as VNode, insertedVnodeQueue));
}
}
} else if (is.primitive(vnode.text)) {
api.appendChild(elm, api.createTextNode(vnode.text));
}
i = (vnode.data as VNodeData).hook; // Reuse variable
if (isDef(i)) {
if (i.create) i.create(emptyNode, vnode);
if (i.insert) insertedVnodeQueue.push(vnode);
}
} else {
vnode.elm = api.createTextNode(vnode.text as string);
}
return vnode.elm;
}
- 判断
vnode.data
不为空,且定义了init
钩子函数,则执行init
钩子函数 - 判断
vnode.sel
是否为!
- 如果是,则创建注释节点
- 不是且不为空,创建
DOM
元素。- 使用
createElement
或者createElementNS
创建真实DOM
元素 - 设置
id
,class
样式等 - 遍历
children
,递归调用createElm
,并添加到DOM
上
- 使用
- 否则,
vnode.sel
为空,则使用createTextNode
创建文本节点
addNodes、removeNodes函数
- 批量添加、删除节点
function removeVnodes(parentElm: Node,
vnodes: Array<VNode>,
startIdx: number,
endIdx: number): void {
for (; startIdx <= endIdx; ++startIdx) {
let i: any, listeners: number, rm: () => void, ch = vnodes[startIdx];
if (ch != null) {
if (isDef(ch.sel)) {
invokeDestroyHook(ch);
listeners = cbs.remove.length + 1;
rm = createRmCb(ch.elm as Node, listeners);
for (i = 0; i < cbs.remove.length; ++i) cbs.remove[i](ch, rm);
if (isDef(i = ch.data) && isDef(i = i.hook) && isDef(i = i.remove)) {
i(ch, rm);
} else {
rm();
}
} else { // Text node
api.removeChild(parentElm, ch.elm as Node);
}
}
}
}
- 遍历
vnodes
,判断节点不为null- 如果节点不存在
sel
,则证明是文本节点,直接执行removeChild
移除 - 否则,执行
destroy
钩子函数,- 利用
createRmCb
生成删除节点的函数rm
function createRmCb(childElm: Node, listeners: number) { return function rmCb() { if (--listeners === 0) { const parent = api.parentNode(childElm); api.removeChild(parent, childElm); } }; }
- 当
listeners
为0时,才执行移出节点,防止多次调用
- 利用
- 如果节点不存在
patchVnode
function patchVnode(oldVnode: VNode, vnode: VNode, insertedVnodeQueue: VNodeQueue) {
let i: any, hook: any;
if (isDef(i = vnode.data) && isDef(hook = i.hook) && isDef(i = hook.prepatch)) {
i(oldVnode, vnode);
}
const elm = vnode.elm = (oldVnode.elm as Node);
let oldCh = oldVnode.children;
let ch = vnode.children;
if (oldVnode === vnode) return;
if (vnode.data !== undefined) {
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode);
i = vnode.data.hook;
if (isDef(i) && isDef(i = i.update)) i(oldVnode, vnode);
}
if (isUndef(vnode.text)) {
if (isDef(oldCh) && isDef(ch)) {
if (oldCh !== ch) updateChildren(elm, oldCh as Array<VNode>, ch as Array<VNode>, insertedVnodeQueue);
} else if (isDef(ch)) {
if (isDef(oldVnode.text)) api.setTextContent(elm, '');
addVnodes(elm, null, ch as Array<VNode>, 0, (ch as Array<VNode>).length - 1, insertedVnodeQueue);
} else if (isDef(oldCh)) {
removeVnodes(elm, oldCh as Array<VNode>, 0, (oldCh as Array<VNode>).length - 1);
} else if (isDef(oldVnode.text)) {
api.setTextContent(elm, '');
}
} else if (oldVnode.text !== vnode.text) {
if (isDef(oldCh)) {
removeVnodes(elm, oldCh as Array<VNode>, 0, (oldCh as Array<VNode>).length - 1);
}
api.setTextContent(elm, vnode.text as string);
}
if (isDef(hook) && isDef(i = hook.postpatch)) {
i(oldVnode, vnode);
}
}