snabbdom
Snabbdom 包含一个非常简单、高性能、可扩展的核心, 只有 ≈200 行。为了可以通过自定义模块进行扩展它提供了具有功能丰富的模块化体系结构。为了保持核心的简单,所有非必要的功能都委托给了模块。你可以把Snabbdom 塑造成你想要的样子! 选择并定制您想要的功能。或者,您也可以只使用默认的扩展就可以获得一个具有高性能、小尺寸和下面列出的所有功能的虚拟DOM 库。
基础使用
import {
init ,
classModule ,
propsModule ,
styleModule ,
eventListenersModule ,
h ,
} from "snabbdom" ;
const patch = init ([
classModule ,
propsModule ,
styleModule ,
eventListenersModule ,
]);
const container = document . getElementById ( "container" );
const vnode = h ( "div#container.two.classes" , { on : { click : someFn } }, [
h ( "span" , { style : { fontWeight : "bold" } }, "This is bold" ),
" and this is just normal text" ,
h ( "a" , { props : { href : "/foo" } }, "I'll take you places!" ),
]);
patch ( container , vnode );
const newVnode = h (
"div#container.two.classes" ,
{ on : { click : anotherEventHandler } },
[
h (
"span" ,
{ style : { fontWeight : "normal" , fontStyle : "italic" } },
"This is now italic type"
),
" and this is still just normal text" ,
h ( "a" , { props : { href : "/bar" } }, "I'll take you places!" ),
]
);
patch ( vnode , newVnode );
源码分析
init
export function init ( module s : Array < Partial < Module > > , domApi? : DOMAPI ) {
let i : number ;
let j : number ;
const cbs : ModuleHooks = {
create : [],
update : [],
remove : [],
destroy : [],
pre : [],
post : [],
};
const api : DOMAPI = domApi !== undefined ? domApi : htmlDomApi ;
for ( i = 0 ; i < hooks . length ; ++ i ) {
}
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 ! ;
parent = api . parentNode ( elm ) as Node ;
createElm ( vnode , insertedVnodeQueue );
if ( parent !== null ) {
api . insertBefore ( parent , vnode . elm ! , api . nextSibling ( elm ));
removeVnodes ( parent , [ oldVnode ], 0 , 0 );
}
}
for ( i = 0 ; i < insertedVnodeQueue . length ; ++ i ) {
insertedVnodeQueue [ i ]. data ! . hook ! . insert ! ( insertedVnodeQueue [ i ]);
}
for ( i = 0 ; i < cbs . post . length ; ++ i ) cbs . post [ i ]();
return vnode ;
};
}
function patchVnode (
oldVnode : VNode ,
vnode : VNode ,
insertedVnodeQueue : VNodeQueue
) {
const hook = vnode . data ? . hook ;
hook ? . prepatch ? .( oldVnode , vnode );
const elm = ( vnode . elm = oldVnode . elm ) ! ;
const oldCh = oldVnode . children as VNode [];
const ch = vnode . children as VNode [];
if ( oldVnode === vnode ) return ;
if ( vnode . data !== undefined ) {
for ( let i = 0 ; i < cbs . update . length ; ++ i ) cbs . update [ i ]( oldVnode , vnode );
vnode . data . hook ? . update ? .( oldVnode , vnode );
}
if ( isUndef ( vnode . text )) {
if ( isDef ( oldCh ) && isDef ( ch )) {
if ( oldCh !== ch ) updateChildren ( elm , oldCh , ch , insertedVnodeQueue );
} else if ( isDef ( ch )) {
if ( isDef ( oldVnode . text )) api . setTextContent ( elm , "" );
addVnodes ( elm , null , ch , 0 , ch . length - 1 , insertedVnodeQueue );
} else if ( isDef ( oldCh )) {
removeVnodes ( elm , oldCh , 0 , oldCh . length - 1 );
} else if ( isDef ( oldVnode . text )) {
api . setTextContent ( elm , "" );
}
} else if ( oldVnode . text !== vnode . text ) {
if ( isDef ( oldCh )) {
removeVnodes ( elm , oldCh , 0 , oldCh . length - 1 );
}
api . setTextContent ( elm , vnode . text ! );
}
hook ? . postpatch ? .( oldVnode , vnode );
}
function emptyNodeAt ( elm : Element ) {
}
function createRmCb ( childElm : Node , listeners : number ) {
return function rmCb ( ) {
if ( -- listeners === 0 ) {
const parent = api . parentNode ( childElm ) as Node ;
api . removeChild ( parent , childElm );
}
};
}
function createElm ( vnode : VNode , insertedVnodeQueue : VNodeQueue ) : Node {
let i : any ;
let data = vnode . data ;
if ( data !== undefined ) {
}
const children = vnode . children ;
const sel = vnode . sel ;
if ( sel === "!" ) {
} else if ( sel !== undefined ) {
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 . ns ))
? api . createElementNS ( i , tag , data )
: api . createElement ( tag , data ));
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 ));
}
const hook = vnode . data ! . hook ;
if ( isDef ( hook )) {
hook . create ? .( emptyNode , vnode );
if ( hook . insert ) {
insertedVnodeQueue . push ( vnode );
}
}
} else {
vnode . elm = api . createTextNode ( vnode . text ! );
}
return vnode . elm ;
}
function addVnodes (
parentElm : Node ,
before : Node | null ,
vnodes : 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 ) {
}
function removeVnodes (
parentElm : Node ,
vnodes : VNode [],
startIdx : number ,
endIdx : number
) : void {
for (; startIdx <= endIdx ; ++ startIdx ) {
let listeners : number ;
let rm : () => void ;
const ch = vnodes [ startIdx ];
if ( ch != null ) {
if ( isDef ( ch . sel )) {
invokeDestroyHook ( ch );
listeners = cbs . remove . length + 1 ;
rm = createRmCb ( ch . elm ! , listeners );
for ( let i = 0 ; i < cbs . remove . length ; ++ i ) cbs . remove [ i ]( ch , rm );
const removeHook = ch ? . data ? . hook ? . remove ;
if ( isDef ( removeHook )) {
removeHook ( ch , rm );
} else {
rm ();
}
} else {
api . removeChild ( parentElm , ch . elm ! );
}
}
}
}
function updateChildren (
parentElm : Node ,
oldCh : VNode [],
newCh : VNode [],
insertedVnodeQueue : VNodeQueue
) {
let oldStartIdx = 0 ;
let 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 : KeyToIndexMap | undefined ;
let idxInOld : number ;
let elmToMove : VNode ;
let before : any ;
while ( oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx ) {
if ( oldStartVnode == null ) {
oldStartVnode = oldCh [ ++ oldStartIdx ];
} 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 )) {
patchVnode ( oldStartVnode , newEndVnode , insertedVnodeQueue );
api . insertBefore (
parentElm ,
oldStartVnode . elm ! ,
api . nextSibling ( oldEndVnode . elm ! )
);
oldStartVnode = oldCh [ ++ oldStartIdx ];
newEndVnode = newCh [ -- newEndIdx ];
} else if ( sameVnode ( oldEndVnode , newStartVnode )) {
patchVnode ( oldEndVnode , newStartVnode , insertedVnodeQueue );
api . insertBefore ( parentElm , oldEndVnode . elm ! , oldStartVnode . elm ! );
oldEndVnode = oldCh [ -- oldEndIdx ];
newStartVnode = newCh [ ++ newStartIdx ];
} else {
if ( oldKeyToIdx === undefined ) {
oldKeyToIdx = createKeyToOldIdx ( oldCh , oldStartIdx , oldEndIdx );
}
idxInOld = oldKeyToIdx [ newStartVnode . key as string ];
if ( isUndef ( idxInOld )) {
api . insertBefore (
parentElm ,
createElm ( newStartVnode , insertedVnodeQueue ),
oldStartVnode . elm !
);
} else {
elmToMove = oldCh [ idxInOld ];
if ( elmToMove . sel !== newStartVnode . sel ) {
api . insertBefore (
parentElm ,
createElm ( newStartVnode , insertedVnodeQueue ),
oldStartVnode . elm !
);
} else {
patchVnode ( elmToMove , newStartVnode , insertedVnodeQueue );
oldCh [ idxInOld ] = undefined as any ;
api . insertBefore ( parentElm , elmToMove . elm ! , oldStartVnode . elm ! );
}
}
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 );
}
}
}