🗒️vue3源码学习11-h方法和createVnode方法实现
2023-4-7
| 2024-10-10
字数 888阅读时长 3 分钟
date
Apr 7, 2023 09:12 AM
type
status
slug
summary
tags
category
updated
Oct 10, 2024 08:04 AM
icon
password

摘要

在实际开发中,经常用到h方法来实现页面,常见的组件中也有render,h这种写法。例如iview的table中
notion image
那么h用法也有很多的多样性。例如
  • h("div",{style:{color: "black"}})
  • h("div",h('span'))
  • h('div', [h('span'),h('span)])
  • h("div","hello")
  • h("div",null,'hello','world')
  • h('div',null, h('span'))
  • h("div",{style:{color: "white"}},'hello')
  • h("div",hello)
  • h("div")
源码中h主要是调用createVnode方法创建虚拟dom,所以主要的东西在createVnode,h就像一个提供方便创造的可能。

编写createVnode

首先要明白为啥用虚拟节点而不是真实的dom。虚拟dom就是一个对象,为了用于diff算法,真实dom的属性比较多。 其次虚拟节点的类型有很多,例如组件、元素、文本等。 那么为了判断虚拟dom的类型,需要有一个判断类型的方法。 在shared编写一个ShapeFlags的函数。
export const enum ShapeFlags { ELEMENT = 1, // HTML 或 SVG 标签 普通 DOM 元素 FUNCTIONAL_COMPONENT = 1 << 1, // 函数式组件 STATEFUL_COMPONENT = 1 << 2, // 普通有状态组件 TEXT_CHILDREN = 1 << 3, // 子节点为纯文本 ARRAY_CHILDREN = 1 << 4, // 子节点是数组 SLOTS_CHILDREN = 1 << 5, // 子节点是插槽 TELEPORT = 1 << 6, // Teleport SUSPENSE = 1 << 7, // Supspense COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8, // 需要被keep-live的有状态组件 COMPONENT_KEPT_ALIVE = 1 << 9, //已经被keep-live的有状态组件 COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT, // 有状态组件和函数组件都是组件,用component表示
这样就有了一个判断传入的孩子节点的类型判断方法了。 为了统一代码的编写,确认孩子的类型,将孩子放在一个数组中。
import { isArray, isString, ShapeFlags } from '@vue/shared' // 虚拟节点有很多: 组件, 元素的、 文本的 // 先写元素 export function createVnode(type, props, children = null) { let shapeFlag = isString(type) ? ShapeFlags.ELEMENT : 0 // 虚拟dom就是一个对象,为了用于diff算法,真实dom的属性比较多 const vnode = { // key 虚拟节点的标识 type, props, children, el: null, // 虚拟节点对应的真实节点。后续diff算法 key: props?.['key'], __v_isVnode: true, shapeFlag, } if (children) { let type = 0 if (isArray(children)) { type = ShapeFlags.ARRAY_CHILDREN } else { children = String(children) type = ShapeFlags.TEXT_CHILDREN } vnode.shapeFlag |= type } return vnode } export function isVnode(value) { return !!(value && value.__v_isVnode) }
有了上面创建虚拟节点的方法之后,h就是一个对写法的支持划分了。 按照上面h可以有的写法。编写自己的h方法
// h 的用法 // h("div") // h("div",hello) // h("div",{style:{color: "white"}},'hello') import { isArray, isObject } from '@vue/shared' import { createVnode, isVnode } from './vnode' // h("div",null,'hello','world') // h('div',null, h('span')) export function h(type, propsChildren?: any, children?: any) { // 其余的除了3个之外的肯定都是孩子 const l = arguments.length // h("div",{style:{color: "black"}}) // h("div",h('span')) // h('div', [h('span'),h('span)]) // h("div","hello") if (l === 2) { // 为什么要将儿子包装成数组,因为元素可以循环创建。 文本不需要包装了 if (isObject(propsChildren) && !isArray(propsChildren)) { // 虚拟节点就包装成数组 if (isVnode(propsChildren)) { return createVnode(type, null, [propsChildren]) } return createVnode(type, propsChildren) // 属性 } else { return createVnode(type, null, propsChildren) // 是数组 } } else { if (l > 3) { children = Array.from(arguments).slice(2) } else if (l === 3 && isVnode(children)) { children = [children] } return createVnode(type, propsChildren, children) // children的情况有2中 文本 / 数组 } }
在上面判断传入参数的个数来区分。同时如果就一个孩子,那么为了写法处理的统一,放入到数组中。 所以最后就2种情况
  • h("h1",{},[])
  • h("h1",null, 文本)
  • Vue
  • TypeScript
  • Axios 上传文件vue3源码学习10-runtime-dom实现
    Loading...