vue3源码阅读与实现: runtime运行时-h模块在该专栏第一篇文章框架设计概念中提到: `vue`在运行时主要的工
runtime运行时 -h函数
模块总览
到这一篇文章,响应式系统部分就全部结束了,接下来开始另一个核心模块runtime
运行时.
在该专栏第一篇文章框架设计概念中提到: vue
在运行时主要的工作时将虚拟DOM
渲染为真实DOM
,在这个过程中主要涉及两个函数:
h
:生成虚拟DOM
render
:将虚拟DOM
渲染
什么是虚拟DOM
虚拟DOM
是用来描述真实DOM
的一个js
对象.这个对象中有许多创建真实DOM
的必要属性,这些属性描述了对应真实DOM
的结构.然后就可以根据虚拟DOM
创建出真实DOM
.
那么为什么需要虚拟DOM
呢?
因为操作虚拟DOM
的性能要比操作真实DOM
的性能更快,在复杂更新场景下,这将节省很多性能
h函数
在vue
中虚拟DOM
的生成依赖于h
函数,该函数可以根据参数生成对应的虚拟DOM
,
- 第一个参数: 必填,可以是字符串(原生标签)也可以是
Vue
的组件定义 - 第二个参数: 可选,标签或者组件的各种属性
- 第三个参数: 可选,标签或者组件的子节点
那么h函数是如何将这些参数转换成虚拟DOM
的呢?vue
中的虚拟DOM
到底长什么样子呢?让我们带着问题,到源码中一探究竟吧!
debugger
使用如下测试用例:
<!DOCTYPE html>
<html>
<head>
<script src="../../dist/vue.global.js"></script>
</head>
<body>
<div id="app"></div>
<script>
const { h } = Vue
debugger
const vnode = h('div', { class: { test: true } }, 'hello') // 关注点: h函数如何生成虚拟DOM
console.log('vnode:', vnode)
</script>
</body>
</html>
该测试用例只有一个关注点: h函数如何生成虚拟DOM
关注点1:创建虚拟DOM
-
open with live server
打开测试用例,进入debugger
-
我们知道
h
函数的传参形式很灵活,h
函数首先将参数统一处理,将type,props,children
对应起来,然后传递给createNode
1. 进入`createVNode`,在这里先对`props`中的`class和style`属性进行处理,我们知道vue对这两个属性做了增强,以支持数组,对象的赋值形式,增强就是在这里做的
-
进入
normalizeClass
,通过循环拼接出类名,然后返回
-
处理完
props
中的class
和style
,接下来会根据type
创建一个变量shapeFlag
,这个变量很重要,表示虚拟DOM
的类型,我们这里对应的就是ShapeFlags.Element
类型,只要记得是Element
就好
-
之后进入
createBaseVNode
中,这里就是真正创建虚拟DOM
的地方,首先定义了一个对象,这个对象就是创建的虚拟DOM
-
然后进入
normalizeChildren
函数,对children
进行处理:在这个函数中根据children
的类型,重新赋值了shapFlag
-
这里根据
children
的类型,重新赋值了虚拟DOM
的shapeFlag(一个数字)
,通过按位或操作,使得shapFlag
技能表示vnode
的类型,也能表示vnode
的子元素类型
到此,返回这个虚拟DOM
,h
函数就执行完毕了
总结
h函数主要做了三件事情:
- 根据容器和子元素的类型计算该虚拟
dom
的shapeFlag
,该属性非常重要,在之后的渲染环节会根据shapeFlag
不同而进行不同的渲染逻辑 - 增强
class
,style
- 创建虚拟
DOM
对象
实现h函数
定义类型
ShapeFlags
中定义了vnode
和children
的类型:
一共包括两种vnode,三种children(对源码中类型有删减)
这在之后的渲染中非常重要
在在packages/shared/src/shapeFlags.ts
中
export const enum ShapeFlags {
// vnode类型
ELEMENT = 1, // 普通标签
FUNCTIONAL_COMPONENT = 1 << 1, // 函数式组件
STATEFUL_COMPONENT = 1 << 2, // 有状态组件
COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT, // 有状态 | 函数式组件
//children类型
TEXT_CHILDREN = 1 << 3, // Text类型
ARRAY_CHILDREN = 1 << 4, // Array类型
SLOTS_CHILDREN = 1 << 5, // 插槽类型
}
除了上述的几种vnode类型,还有需定义一些其他的类型,如文本节点,代码片段,注释,这里只额外定义了三种
在packages/runtime-core/src/vnode.ts
// 定义三种虚拟DOM类型
export const Text = Symbol("Text");
export const Comment = Symbol("Comment");
export const Fragment = Symbol("Fragment");
h函数
这个函数中主要处理参数:
在packages/runtime-core/src/h.ts
import { isArray, isObject, isVnode } from "@vue/shared";
import { createVnode } from "./vnode";
export function h(type: any, propsOrChildren: any, children: any) {
// 处理参数
const l = arguments.length;
if (l === 2) {
// 只有两个参数,第二个参数可能是children也可能是props也有可能是vndoe
const isProps = isObject(propsOrChildren) && !isArray(propsOrChildren);
if (isProps) {
if (isVnode(propsOrChildren)) {
return createVnode(type, null, [propsOrChildren]);
}
return createVnode(type, propsOrChildren);
} else {
return createVnode(type, null, propsOrChildren);
}
} else {
if (l > 3) {
children = [...arguments].slice(2);
} else if (l === 3 && isVnode(children)) {
children = [children];
}
return createVnode(type, propsOrChildren, children);
}
}
在packages/shared/src/index.ts
中添加isVnode
方法:
export const isVnode = (v: any): v is Vnode => {
return v && v.__V_isVnode === true;
};
createVnode函数
这个函数主要增强class
和style
同时根据type
确定shapeFlag
的值
在packages/runtime-core/src/vnode.ts
export function createVnode(type: any, props: any, children?: any) {
// 增强class和style
if (props) {
const { class: klass } = props;
if (klass) {
props.class = normalizeClass(klass);
}
}
// 根据type创建shapflag
const shapeFlag = isString(type) // string类型表示是普通标签
? ShapeFlags.ELEMENT
: isObject(type)
? ShapeFlags.STATEFUL_COMPONENT //如果是对象,就是组件
: 0;
return createBaseVnode(type, props, children, shapeFlag);
}
/**
* @message: 增强class处理,以便支持class的多种写法: 字符串,数组,对象
*/
function normalizeClass(klass: any): String {
let res = "";
if (isString(klass)) {
return klass;
}
if (isArray(klass)) {
res = klass.reduce((acc, curr) => {
acc += ` ${normalizeClass(curr)}`;
return acc;
}, "");
} else if (isObject(klass)) {
res = Object.entries(klass).reduce((acc, [key, value]) => {
if (value) {
acc += key;
}
return acc;
}, "");
}
return res.trim();
}
createBaseVnode函数
这个函数主要用来创建vnode
然后根据children
的类型,修改shapFlag
的值
在packages/runtime-core/src/vnode.ts
/**
* @message: 创建vnode
*/
function createBaseVnode(
type: any,
props: any,
children: any,
shapeFlag: number
) {
// 生成vnode
const vnode: Vnode = {
__v_isVnode: true,
type,
props: props,
children,
shapeFlag,
};
normalizeChildren(vnode, children);
return vnode;
}
/**
* @message: 根据children类型,设置shapeFlag的值
*/
function normalizeChildren(vnode: Vnode, children: any) {
let type = 0;
if (children == null) {
children = null;
} else if (isArray(children)) {
type = ShapeFlags.ARRAY_CHILDREN;
} else if (isObject(children)) {
} else if (isFunction(children)) {
} else {
vnode.children = String(children);
// 为 type 指定 Flags
type = ShapeFlags.TEXT_CHILDREN;
}
// 修改 vnode 的 chidlren
vnode.children = children;
// 按位或赋值,赋值前shapeFlag表示的是vnode的类型,通过或运算,shapeFlag就能同时表示其子元素的类型了
vnode.shapeFlag |= type;
}
总结
h函数的整体逻辑并不复杂,主要是一些虚拟DOM
属性的处理,需要关注的就是shapFlag
的处理过程,这个属性将在render
部分重点使用
转载自:https://juejin.cn/post/7401315308005179432