1.定义
h
函数是创建节点, 可实现展示template
如何渲染到html
中的过程,因为vue
渲染到页面上是通过loader
打包成模板字符串拼接渲染的,所以 h
函数同样也是通过字符串渲染到html
中
h函数就是vue
中的createElement
方法,这个函数作用就是创建虚拟dom
,追踪dom
变化的
2.使用
/**
* h()参数数量
* 1个:类型
* 2个:类型 和 属性、节点、儿子
* 多个:第三个及其以后都是子节点
* 不能出现三个参数,2不是属性 只要有三个参数就会认为第二个参数是属性
*/
const ele = h("h1");
const ele1 = h("h1", "hello world");
const ele2 = h("h1", { style: { color: "red" } });
const ele3 = h("h1", ["hello world","goodbye"]);
const ele4 = h("h1", {},"hello world","goodbye");
如果有三个及其以上的参数,第二个参数代表属性,如果没有属性,可以写成ele3
或者ele4
的形式
3.实现
2.1 创建虚拟节点
import { isString, ShapeFlags } from "@vue/shared";
export function isVnode(value) {
return value && value?.__v_isVnode;
}
export function createVnode(type, props, children) {
const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : 0;
const vnode = {
__v_isVnode: true,
type,
props,
children,
key: props?.key, // diff需要的key
el: null, // 虚拟节点对应的真实节点
shapeFlag,
};
if(children){
let type = 0;
if(Array.isArray(children)){
type = ShapeFlags.ARRAY_CHILDREN;
}else{
children = String(children);
type = ShapeFlags.TEXT_CHILDREN
}
vnode.shapeFlag |= type
}
return vnode;
}
2.2 h实现
import { isObject } from "@vue/shared";
import { createVnode, isVnode } from "./createVnode";
export function h(type, propsOrChildren?, children?) {
let l = arguments.length;
if (l === 2) {
// 虚拟节点 | 属性
if (isObject(propsOrChildren) && !Array.isArray(propsOrChildren)) {
if (isVnode(propsOrChildren)) {
return createVnode(type, null, [propsOrChildren]);
} else {
return createVnode(type, propsOrChildren, null);
}
}
// 数组 | 文本
return createVnode(type, null, propsOrChildren);
} else {
if (l > 3) {
children = Array.from(arguments).slice(2);
}
if (l == 3 && isVnode(children)) {
children = [children];
}
return createVnode(type, propsOrChildren, children);
}
}
2.3 卸载DOM
const ele3 = h("h1", 'hello');
render(ele3, app);
setTimeout(()=>{
render(null, app);
},1000)
在挂载元素时纪录真实节点
const mountElement = (vnode, container) => {
const { type, children, props, shapeFlag } = vnode;
// 第一次渲染的时候我们让虚拟节点和真实的dom创建关联
// 第二次渲染新的vnode,可以和上一次的vnode做比对,之后更新对应的el元素,可以复用这个dom元素
let el = (vnode.el = hostCreateElement(type));
...
}
在渲染时判断
const unmount = (vnode) => hostRemove(vnode.el)
// core 中不关心如何渲染
const render = (vnode, container) => {
if (vnode == null) {
// 移除dom元素
if (container._vnode) {
unmount(container._vnode)
}
}
...
}
2.4 对比节点
通过判断两个节点的类型和key
值是否相同判定为是否为同一节点,如果不是,则删除n1
,创建n2
,如果是同一节点,则采用diff
算法对比更新复用节点
export function isSameVnode(n1,n2){
return n1.type === n2.type && n1.key === n2.key
}
更改patch
逻辑
const patch = (n1, n2, container) => {
if (n1 == n2) {
return;
}
if (n1 && !isSameVnode(n1, n2)) {
unmount(n1);
n1 = null; //为了执行后续的n2的初始化
}
if (n1 == null) {
mountElement(n2, container);
}else{
// diff 算法
}
};
diff
算法见下篇啦~~~