第八章:框架实现:h 函数
大家好,我是作曲家种太阳,这一张来实现一下 h函数
步骤1:创建 ShapeFlags 枚举
在 packages/shared/src/shapeFlags.ts
中定义所有 VNode 类型标识:
ts
export const enum ShapeFlags {
ELEMENT = 1,
FUNCTIONAL_COMPONENT = 1 << 1,
STATEFUL_COMPONENT = 1 << 2,
TEXT_CHILDREN = 1 << 3,
ARRAY_CHILDREN = 1 << 4,
SLOTS_CHILDREN = 1 << 5,
COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT
}
步骤2:实现 h 函数
在 packages/runtime-core/src/h.ts
中构建 h 函数:
ts
import { isArray, isObject } from '@vue/shared'
import { createVNode, isVNode, VNode } from './vnode'
export function h(type: any, propsOrChildren?: any, children?: any): VNode {
// 获取用户传递的参数数量
const l = arguments.length
// 如果用户只传递了两个参数,那么证明第二个参数可能是 props , 也可能是 children
if (l === 2) {
// 如果 第二个参数是对象,但不是数组。则第二个参数只有两种可能性:1. VNode 2.普通的 props
if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
// 如果是 VNode,则 第二个参数代表了 children
if (isVNode(propsOrChildren)) {
return createVNode(type, null, [propsOrChildren])
}
// 如果不是 VNode, 则第二个参数代表了 props
return createVNode(type, propsOrChildren)
}
// 如果第二个参数不是单纯的 object,则 第二个参数代表了 props
else {
return createVNode(type, null, propsOrChildren)
}
}
// 如果用户传递了三个或以上的参数,那么证明第二个参数一定代表了 props
else {
// 如果参数在三个以上,则从第二个参数开始,把后续所有参数都作为 children
if (l > 3) {
children = Array.prototype.slice.call(arguments, 2)
}
// 如果传递的参数只有三个,则 children 是单纯的 children
else if (l === 3 && isVNode(children)) {
children = [children]
}
// 触发 createVNode 方法,创建 VNode 实例
return createVNode(type, propsOrChildren, children)
}
}
步骤3:构建 VNode 类型
在 packages/runtime-core/src/vnode.ts
中:
ts
import { isArray, isFunction, isObject, isString } from '@vue/shared'
import { normalizeClass } from 'packages/shared/src/normalizeProp'
import { ShapeFlags } from 'packages/shared/src/shapeFlags'
export const Fragment = Symbol('Fragment')
export const Text = Symbol('Text')
export const Comment = Symbol('Comment')
/**
* VNode
*/
export interface VNode {
__v_isVNode: true
key: any
type: any
props: any
children: any
shapeFlag: number
}
export function isVNode(value: any): value is VNode {
return value ? value.__v_isVNode === true : false
}
/**
* 生成一个 VNode 对象,并返回
* @param type vnode.type
* @param props 标签属性或自定义属性
* @param children? 子节点
* @returns vnode 对象
*/
export function createVNode(type, props, children?): VNode {
// 通过 bit 位处理 shapeFlag 类型
const shapeFlag = isString(type)
? ShapeFlags.ELEMENT
: isObject(type)
? ShapeFlags.STATEFUL_COMPONENT
: 0
if (props) {
// 处理 class
let { class: klass, style } = props
if (klass && !isString(klass)) {
props.class = normalizeClass(klass)
}
}
return createBaseVNode(type, props, children, shapeFlag)
}
/**
* 构建基础 vnode
*/
function createBaseVNode(type, props, children, shapeFlag) {
const vnode = {
__v_isVNode: true,
type,
props,
shapeFlag,
key: props?.key || null
} as VNode
normalizeChildren(vnode, children)
return vnode
}
export { createVNode as createElementVNode }
/**
* 创建注释节点
*/
export function createCommentVNode(text) {
return createVNode(Comment, null, text)
}
export function normalizeChildren(vnode: VNode, children: unknown) {
let type = 0
const { shapeFlag } = vnode
if (children == null) {
children = null
} else if (isArray(children)) {
type = ShapeFlags.ARRAY_CHILDREN
} else if (typeof children === 'object') {
// TODO: object
} else if (isFunction(children)) {
// TODO: function
} else {
// children 为 string
children = String(children)
// 为 type 指定 Flags
type = ShapeFlags.TEXT_CHILDREN
}
// 修改 vnode 的 chidlren
vnode.children = children
// 按位或赋值
vnode.shapeFlag |= type
}
/**
* 根据 key || type 判断是否为相同类型节点
*/
export function isSameVNodeType(n1: VNode, n2: VNode): boolean {
return n1.type === n2.type && n1.key === n2.key
}
步骤3:isString实现
在 packages/shared/src/index.ts 中:
js
/**
* 判断是否为一个 string
*/
export const isString = (val: unknown): val is string => typeof val === 'string'
步骤4:实现
(后面会讲解 createVNode 细节,这里先留空)
步骤5:导出 h 函数
在 index.ts
导出 h
。
小结
VNode 核心属性:
type
props
children
shapeFlag
__v_isVNode
整个 h
函数和 VNode
的搭建,奠定了 Vue3 渲染系统的基础结构。
下一步,我们将开始真正进入 render
渲染函数的开发!