Fragment
回顾创建slot的时候的代码
import { h } from '../h'
export function renderSlots(slots, name = 'default', props) {
/** 返回一个vnode
* 1. 其本质和 h 是一样的
* 2. 通过name取到对应的slots
*/
const slot = slots[name];
if (slot) {
if (typeof slot === "function") {
// 是一个函数,需要调用函数,并把当前作用域插槽的数据传过去,把调用结果渲染处理
return createdVNode('div', {}, slot(props));
}
// 不是函数,是h对象,或者h对象数组集合
return createdVNode('div', {}, slot);
} else {
return console.warn("没有找到对应的插槽");
}
}
问题:每次都是通过createdVNode去重新创建一个新的div节点,这样会导致一个问题。每个slot都会包一层div
很显然,这不优雅~ 可以通过Fragment节点来优化创建slot
修改slot创建节点
/*
* @Author: Lin zefan
* @Date: 2022-03-27 12:03:47
* @LastEditTime: 2022-03-30 21:45:42
* @LastEditors: Lin zefan
* @Description:
* @FilePath: \mini-vue3\src\runtime-core\helpers\renderSlots.ts
*
*/
import { createdVNode, Fragment } from "../vnode";
export function renderSlots(slots, name = "default", props = {}) {
/** 返回一个vnode
* 1. 其本质和 h 是一样的
* 2. 通过name取到对应的slots
*/
const slot = slots[name];
if (slot) {
if (typeof slot === "function") {
// 是一个函数,需要调用函数,并把当前作用域插槽的数据传过去,把调用结果渲染处理
return createdVNode(Fragment, {}, slot(props));
}
// 不是函数,是h对象,或者h对象数组集合
return createdVNode(Fragment, {}, slot);
} else {
return console.warn("没有找到对应的插槽");
}
}
新增Fragment节点类型
vnode.ts
export const Fragment = Symbol("Fragment");
render.ts
export function patch(vnode, container) {
if (!vnode) return;
const { type } = vnode;
switch (type) {
case Fragment:
processFragment(vnode, container);
break;
default:
if (isObject(type)) {
// 是一个Component
processComponent(vnode, container);
} else if (typeof type === "string") {
// 是一个element
processElement(vnode, container);
}
break;
}
}
element.ts
// 创建一个Fragment节点
export function processFragment(vnode: any, container: any) {
mountChildren(vnode.children, container);
}
优化后
Text 节点
假如我们想实现纯文字的slot
text: ()=> ("我是个text node"),
但是目前的逻辑是不支持textNode的,因为创建VNode的时候要么是H函数包裹的节点或者 Fragment 节点,所以要再加一个Text 节点
通过 createTextVNode 创建 TextVNode
vnode.ts
export const TextNode = Symbol("TextNode");
// 创建一个textNode节点
export function createTextVNode(text) {
return createdVNode(TextNode, {}, text);
}
patch 时候新增TextNode判断
render.ts
export function patch(vnode, container) {
if (!vnode) return;
const { type } = vnode;
switch (type) {
case Fragment:
processFragment(vnode, container);
break;
case TextNode:
processTextNode(vnode, container);
break;
default:
if (isObject(type)) {
// 是一个Component
processComponent(vnode, container);
} else if (typeof type === "string") {
// 是一个element
processElement(vnode, container);
}
break;
}
}
element.ts
// 创建一个TextNode节点
export function processTextNode(vnode: any, container: any) {
const textNode = document.createTextNode(vnode.children);
container.append(textNode);
}
实现
App.js
import { h, createTextVNode } from "../../lib/mini-vue.esm.js";
import Child from "./Child.js";
export default {
name: "App",
setup() {},
render() {
return h("div", {}, [
h("div", {}, "你好"),
h(
Child,
{},
{
header: [h("div", {}, "header")],
default: ({ age }) => [
h("p", {}, "我是通过 slot 渲染出来的第一个元素 "),
h("p", {}, "我是通过 slot 渲染出来的第二个元素"),
h("p", {}, `我可以接收到 age: ${age}`),
],
main: h("div", {}, "main"),
footer: ({ name }) =>
h("p", {}, "我是通过footer插槽 ,名字是:" + name),
text: createTextVNode("我是个text node"),
}
),
]);
},
};