# VNode 介绍

# vnode 函数生成 Vnode

首先我们要理解 VNode。所谓 VNode,其实就是一个 javascript 对象,它内部有一些属性,可以表示 DOM 元素的一些属性。 我们来看一下这里对 VNode 的定义(src/vnode.ts):

export interface VNode {
  sel: string | undefined // 选择器
  data: VNodeData | undefined // 数据
  children: Array<VNode | string> | undefined // 子节点
  elm: Node | undefined // 对应的 DOM 元素
  text: string | undefined // 对应的文本内容
  key: Key | undefined // key
}

VNode 中的 data 就是用来对应真实 DOM 中属性的,它是 VNodeData 类型的:

export interface VNodeData {
  props?: Props
  attrs?: Attrs
  class?: Classes
  style?: VNodeStyle
  dataset?: Dataset
  on?: On
  hero?: Hero
  attachData?: AttachData
  hook?: Hooks // vnode hook
  key?: Key
  ns?: string // for SVGs
  fn?: () => VNode // for thunks
  args?: any[] // for thunks
  [key: string]: any // for any other 3rd party module
}

还有一个重要的就是 hook,它是 Hooks 类型的(src/hooks.ts):

export type PreHook = () => any;
export type InitHook = (vNode: VNode) => any;
export type CreateHook = (emptyVNode: VNode, vNode: VNode) => any;
export type InsertHook = (vNode: VNode) => any;
export type PrePatchHook = (oldVNode: VNode, vNode: VNode) => any;
export type UpdateHook = (oldVNode: VNode, vNode: VNode) => any;
export type PostPatchHook = (oldVNode: VNode, vNode: VNode) => any;
export type DestroyHook = (vNode: VNode) => any;
export type RemoveHook = (vNode: VNode, removeCallback: () => void) => any;
export type PostHook = () => any;

export interface Hooks {
  pre?: PreHook
  init?: InitHook
  create?: CreateHook
  insert?: InsertHook
  prepatch?: PrePatchHook
  update?: UpdateHook
  postpatch?: PostPatchHook
  destroy?: DestroyHook
  remove?: RemoveHook
  post?: PostHook
}

其实就是定义了对应 hook 的函数形式。最后再介绍一下生成 VNode 的函数 vnode:

export function vnode (sel: string | undefined,
  data: any | undefined,
  children: Array<VNode | string> | undefined,
  text: string | undefined,
  elm: Element | Text | undefined): VNode {
  const key = data === undefined ? undefined : data.key;
  return { sel, data, children, text, elm, key };
}

特别简单,就是根据传入的参数,返回一个对象而已。

# h 函数生成 Vnode

知道了 VNode,我们现在来看一下 h 函数,先来看一下它的定义:


export function h(sel: string): VNode;
export function h(sel: string, data: VNodeData | null): VNode;
export function h(sel: string, children: VNodeChildren): VNode;
export function h(sel: string, data: VNodeData | null, children: VNodeChildren): VNode;

它重载了四个函数,也就是说,我们有四种不同的方式去调用它。比如要创建一个不包含子节点的 VNode,我们可以这么调用:

h('h1', { style: { color: 'red' } })

h 函数的最终实现如下,具体的逻辑写在注释中了:

export function h (sel: any, b?: any, c?: any): VNode {
  var data: VNodeData = {};
  var children: any;
  var text: any;
  var i: number;
  if (c !== undefined) { // 意味着 传递了三个参数,即 sel, data, children
    if (b !== null) { // 意味着 data 不为null,很好,是我们想要的,把 b 赋值给 data 即可
      data = b;
    }
    if (is.array(c)) { // 意味着 children 是一个数组,那太好了,完美的情况,直接把 c 赋值为 children
      children = c;
    } else if (is.primitive(c)) { // c为字符串或者数字的话,把它赋值为 text 好了
      text = c;
    } else if (c && c.sel) { // ok, 这时候 c 可能就是一个 vnode了,把它包装成数组吧
      children = [c];
    }
  } else if (b !== undefined && b !== null) { // 这里说明只传递了两个参数哦
    if (is.array(b)) { // 如果是个数组的话,那就当成 children 处理
      children = b;
    } else if (is.primitive(b)) { // 简单类型的话,当成 text
      text = b;
    } else if (b && b.sel) { // vnode 的话,包装成数组,也当成 children
      children = [b];
    } else { data = b; } // finally,赋值为 data。
  }

  // 经过以上的判断,这时候 data, children 都处理好了

  if (children !== undefined) {
    for (i = 0; i < children.length; ++i) {
      // 这里是把 children 中,出现的字符串或者数字,转成 vnode
      if (is.primitive(children[i])) children[i] = vnode(undefined, undefined, undefined, children[i], undefined);
    }
  }
  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);
};

其实这里的逻辑都是为了满足上面四个重载的实现。对于不同场景传入的参数,做了不同的处理,相当于一层 normalize 的操作,将不同的输入规范化成统一的形式后进行处理,可以方便我们的后续操作。

上次更新: 6/23/2020, 3:00:53 PM