关于Virtual Dom的那些事(四)

前言

上一篇《关于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只会与那些需要化很长的计算时间的复杂的视图相关。

 

作者: 博主

Talk is cheap, show me the code!

发表评论

邮箱地址不会被公开。

Captcha Code