基于 vue3源码 尝试 mini-vue 的实现
预览:
1. 实现思路
- 渲染系统模块
- 响应式系统
- mini-vue 程序入口
2. 渲染系统模块
2.1 初识 h 函数
以下是 vue 的模版语法代码:
<template>
<div class='container'>hello mini-vue</div>
</template>
它并不是传统的 html 代码,而是通过 h 函数生成的虚拟 dom 节点,h 函数接收 3 个参数:
- 第一个参数是标签名 (tag),此例中为 ‘div’
- 第二个参数是标签的属性 (props), 此例中为 ‘container’
- 第三个参数是子节点,可以是字符串、数组 (children), 此例中为 ‘hello mini-vue’
/**
* h 函数
* 功能:返回vnode
*
* @param {String} tagName - 标签名
* @param {Object | Null} props - 传递过来的参数
* @param {Array | String} children - 子节点
* @return {vnode} 虚拟节点
*/
const h = (tagName, props, children) => {
// 直接返回一个对象,里面包含vnode结构
return {
tagName,
props,
children,
};
};
export default h;
2.2 创建一个 vnode
vnode 就是虚拟 dom 节点,创建方法很简单:
h(
'div', {
class: 'container' }, [
h('h1', {
}, `文本:${
this.data.msg},可变数字:${
this.data.count}`),
h('button', {
onclick: () =>{
this.data.msg = 'hello miniVue',
this.data.count++
}
}, '点击试试'),
]
)
2.3 挂载真实 DOM
该mount
函数的主要功能是将虚拟节点(vnode
)挂载到真实的DOM容器(container
)中:
-
创建真实元素:
- 使用
document.createElement
创建一个具有指定标签名的真实元素。 - 将创建的元素保存在
vnode.el
属性中,以便后续的操作。
- 使用
-
处理属性(props):
- 遍历虚拟节点的
props
属性,分别处理函数类型的事件监听器和其他类型的属性。 - 如果属性名以 “on” 开头,将其作为事件处理函数添加到元素上。
- 否则,使用
setAttribute
方法设置元素的属性。
- 遍历虚拟节点的
-
处理子节点(children):
- 如果虚拟节点有子节点,分两种情况处理:
- 如果子节点是字符串,直接设置元素的文本内容。
- 如果子节点是数组,递归调用
mount
函数挂载每个子节点。
- 如果虚拟节点有子节点,分两种情况处理:
-
挂载到容器中:
- 最后,使用
container.appendChild
将创建的真实元素挂载到指定的DOM容器中。
- 最后,使用
这个mount
函数的重点在于递归调用,通过递归处理子节点,实现了对整个虚拟DOM树的挂载。
/**
* mount 函数
* 功能:挂载 vnode 为 真实dom
* 重点:递归调用处理子节点
*
* @param {Object} vnode -虚拟节点
* @param {elememt} container -需要被挂载节点
*/
const mount = (vnode, container) => {
console.log(vnode);
// 1. 创建出真实元素, 同时给 vnode 添加 el 属性
const el = (vnode.el = document.createElement(vnode.tagName));
// 2. 处理 props
if (vnode.props) {
for (const key in vnode.props) {
const value = vnode.props[key];
// 2.1 prop 是函数
if (key.startsWith("on")) {
el.addEventListener(key.slice(2).toLowerCase(), value);
} else {
// 2.2 prop 是字符串
el.setAttribute(key, value);
}
}
}
// 3. 处理 children
if (vnode.children) {
// 3.1 如果 children 是字符串,直接设置文本内容
if (typeof vnode.children === "string") {
el.textContent = vnode.children;
}
// 3.2 如果 children 是数组,递归挂载每个子节点
else {
console.log(vnode.children);
// 先拿到里面的每一个 vnode
vnode.children.forEach((item) => {
// 再把里面的vnode递归调用
mount(item, el);
});
}
}
// 4. 挂载
container.appendChild(el