Skip to content

Latest commit

 

History

History
185 lines (172 loc) · 4.39 KB

3.HFUNCTION.md

File metadata and controls

185 lines (172 loc) · 4.39 KB

生成 VNode 的 h 函数

基本的 h 函数

首先我们实现一个最简单的 h 函数,可以是这样的,接收三个参数:

  • tag 标签名
  • props DOM 上的属性
  • children 子节点

我们新建一个文件 h.ts,内容如下:

function h(tag, props, children){
  return {
    tag,
    props,
    children
  }
}

我们用如下的 VNode 来表示 <div class="red"><span>hello</span></div>

import { h } from './h'
const vdom = h('div', {
  class: 'red'
}, [
  h('span', null, 'hello')
])

看一下实际输出内容:

const vdom = {
  "tag": "div",
  "props": {
    "class": "red"
  },
  "children": [
    {
      "tag": "span",
      "props": null,
      "children": "hello"
    }
  ]
}

基本符合预期,但是这里有同学可能又要问了:“这个 vdom 和写的 h 函数没什么不同,为什么不直接写 VNode?”

这是因为我们现在的 h 函数所做的仅仅就是返回传入的参数,实际上根据我们对 VNode 的定义,还缺少一些字段,不过你也可以直接写 VNode,但这样会增加大量的额外工作。

完整的 h 函数

现在我们补全 h 函数,添加 _isVNodeelshapeFlag 字段。

function h(tag, props = null, children = null) {
  return {
    _isVNode: true,
    el: null,
    shapeFlag: null,
    tag,
    props,
    children
  }
}

这里的 _isVNode 永远为 trueel 不是在创建 VNode 的时候赋值,所以不用处理,我们主要处理 shapeFlag,实际上 shapeFlag 有 10 种类型,我们这里只实现一个最简单的判断:

function h(tag, props = null, children = null) {
  let shapeFlag = null
  // 这里为了简化,直接这样判断
  if (typeof tag === 'string') {
    shapeFlag = ShapeFlags.ELEMENT
  } else if(typeof tag === 'object'){
    shapeFlag = ShapeFlags.STATEFUL_COMPONENT
  } else if(typeof tag === 'function'){
    shapeFlag = ShapeFlags.FUNCTIONAL_COMPONENT
  }

  return {
    _isVNode: true,
    el: null,
    shapeFlag,
    tag,
    props,
    children
  }
}

现在我们需要处理一下 children 的类型了,VNode 章节中我们讲过其判断逻辑,那么 h 函数现在完整逻辑如下:

function h(tag, props = null, children = null) {
  let shapeFlag = null
  // 这里为了简化,直接这样判断
  if (typeof tag === 'string') {
    shapeFlag = ShapeFlags.ELEMENT
  } else if(typeof tag === 'object'){
    shapeFlag = ShapeFlags.STATEFUL_COMPONENT
  } else if(typeof tag === 'function'){
    shapeFlag = ShapeFlags.FUNCTIONAL_COMPONENT
  }

  const vnode = {
    _isVNode: true,
    el: null,
    shapeFlag,
    tag,
    props,
    children
  }
  normalizeChildren(vnode, vnode.children)
  return vnode
}

function normalizeChildren(vnode, children) {
  let type = 0
  if (children == null) {
    children = null
  } else if (Array.isArray(children)) {
    type = ShapeFlags.ARRAY_CHILDREN
  } else if (typeof children === 'object') {
    type = ShapeFlags.SLOTS_CHILDREN
  } else if (typeof children === 'string') {
    children = String(children)
    type = ShapeFlags.TEXT_CHILDREN
  }
  vnode.shapeFlag |= type
}

现在我们重新写一个测试代码看一下 h 函数输入结果:

import { h } from './h'
const MyComponent = {
  render() {}
}
const vdom = h('div', {
  class: 'red'
}, [
  h('p', null, 'hello'),
  h('p', null, null),
  h(MyComponent)
])

console.log(vdom);
// vdom:
// {
//   _isVNode: true,
//   el: null,
//   shapeFlag: 17,
//   tag: 'div',
//   props: { class: 'red' },
//   children: [
//     {
//       _isVNode: true,
//       el: null,
//       shapeFlag: 9,
//       tag: 'p',
//       props: null,
//       children: 'hello'
//     },
//     {
//       _isVNode: true,
//       el: null,
//       shapeFlag: 1,
//       tag: 'p',
//       props: null,
//       children: null
//     },
//     {
//       _isVNode: true,
//       el: null,
//       shapeFlag: 4,
//       tag: [Object],
//       props: null,
//       children: null
//     }
//   ]
// }

至此已经完成了 h 函数的基本设计,可以得到想要的 VNode 了,下一步就是把 VNode 渲染到页面上。

消化一下,下回继续~

下一课 - 渲染 VNode 的 mount 函数