前言
上一篇《关于Virtual Dom的那些事(三)》主要是介绍了生成VNode的几种方式。翻一翻snabbdom的源码,我看还可以看到一个概念-Thunk,Thunk是什么?在react全家桶中经常会看到Thunk这概念,在计算机语言中是传名调用的意思,但如果按我个人的理解就是中间件的概念,更通俗一点就是在某函数的每次输入与输出之间,预加一个自定义的拦截处理函数(thunk函数),先对输入的参数进行预处理再作为输入,这样当然输出的结果就依赖于thunk函数了。react-thunk中间件的作用也类似,让redux可以支持dispatch一个异步函数(不使用的话,只能支持dispatch同步的action)。而在snabbdom里,Thunk主要用于优化性能,在每次patch之前先拦截一下判断VNode状态数据有没有变化,如果没有变化就不需要执行patch操作了。本节主要是分析一下snabbdom中Thunk的源码。
Thunk(thunk.ts)的源码分析
import {VNode, VNodeData} from './vnode'; import {h} from './h'; export interface ThunkData extends VNodeData { // ThunkData是承自VNodeData,增加了fn属性函数返回是VNode,增加了args参数数组属性 fn: () => VNode; args: Array<any>; } export interface Thunk extends VNode { // Thunk继承自VNode,增加了data属性类型为ThunkData data: ThunkData; } export interface ThunkFn { // ThunkFn接口定义,左边两种参数方式都可以生成Thunk (sel: string, fn: Function, args: Array<any>): Thunk; (sel: string, key: any, fn: Function, args: Array<any>): Thunk; } function copyToThunk(vnode: VNode, thunk: VNode): void { // 把VNode复制成Thunk thunk.elm = vnode.elm; (vnode.data as VNodeData).fn = (thunk.data as VNodeData).fn; (vnode.data as VNodeData).args = (thunk.data as VNodeData).args; thunk.data = vnode.data; thunk.children = vnode.children; thunk.text = vnode.text; thunk.elm = vnode.elm; } function init(thunk: VNode): void { // 初始化生成Thunk const cur = thunk.data as VNodeData; const vnode = (cur.fn as any).apply(undefined, cur.args); copyToThunk(vnode, thunk); } function prepatch(oldVnode: VNode, thunk: VNode): void { // 提供patch之前比较一下old与cur数据及属性有没有变化的能力,如果有变化要先用执行fn处理函数返回的VNode来copy到thunk生成thunk,如果没变直接用oldVNode来生成thunk let i: number, old = oldVnode.data as VNodeData, cur = thunk.data as VNodeData; const oldArgs = old.args, args = cur.args; if (old.fn !== cur.fn || (oldArgs as any).length !== (args as any).length) { copyToThunk((cur.fn as any).apply(undefined, args), thunk); return; } for (i = 0; i < (args as any).length; ++i) { if ((oldArgs as any)[i] !== (args as any)[i]) { copyToThunk((cur.fn as any).apply(undefined, args), thunk); return; } } copyToThunk(oldVnode, thunk); } export const thunk = function thunk(sel: string, key?: any, fn?: any, args?: any): VNode { // thunk的构造函数,就一中间件函数(函数中返回函数) if (args === undefined) { args = fn; fn = key; key = undefined; } return h(sel, { key: key, hook: {init: init, prepatch: prepatch}, fn: fn, args: args }); } as ThunkFn; export default thunk;
官网的定义
thunk
函数包含一个selector,一个key 用来标识一个thunk, 还有一个返回vnode的函数及一系列的状态变量。如果被触发,render 函数就会接收这些参数变量。
thunk(selector, key, renderFn, [stateArguments])
key
是可选的。 如果 selector
在thunk兄弟中是不唯一的,就需要提供。来保证diff时能顺利进行。
Thunk是一些用于处理 immutable 数据的优化策略。
如下面这函数是用于创建基于一个number类型变量的virtual node。
function numberView(n) {
return h('div', 'Number is: ' + n);
}
视图取决于 n
. 这样如果 n
不变, 那么创建virtual DOM node 和用于与old vnode的patch就变量浪费了。 为了避免这种情况,我们可以用thunk
工具函数。
function render(state) {
return thunk('num', numberView, [state.number]);
}
与其真的要去触发 numberView
,还不如丢一个仿制的vnode到virtual dom。当Snabbdom patch这个仿制的vnode与先前的vnode,它将对 n
进行比较,如果 n
不变,它将直接使用旧的vnode。这将不用重新创建number view 和进行整个diff过程。
此视图函数只是一个例子。在实际中thunk只会与那些需要化很长的计算时间的复杂的视图相关。