实现一个普通的slots
demo
App.js
import { h } from "../../lib/mini-vue.esm.js";
import Child from "./Child.js";
export default {
name: "App",
setup() {},
render() {
return h("div", {}, [
h("div", {}, "你好"),
h(
Child,
{
msg: "your name is child",
},
[h("div", {}, "123"), h("div", {}, "456")]
),
]);
},
};
Child.js
import { h, renderSlots } from "../../lib/mini-vue.esm.js";
export default {
name: "Child",
setup(props, context) {},
render() {
console.log("this.$slots", this.$slots);
return h("div", {}, [
h("div", {}, "child"),
h("div", {}, this.$slots),
]);
},
};
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script type="module">
import { createApp } from "../../lib/mini-vue.esm.js";
import App from "./App.js";
const rootContainer = document.querySelector("#app");
createApp(App).mount(rootContainer);
</script>
</body>
</html>
在实例挂载$slots
component.ts
function createComponentInstance(initVNode) {
const component = {
vnode: initVNode,
type: initVNode.type,
proxy: null,
setupState: {},
props: {},
+ slots: {},
emit: () => {},
};
// other code
}
// 初始化setup数据
function setupComponent(instance, container) {
// 初始化props
initProps(instance, instance.vnode.props);
+ // 初始化slots
+ initSlots(instance, instance.vnode.children);
// 初始化setup函数返回值
setupStatefulComponent(instance, container);
}
componentPublicInstanceProxyHandlers.ts
const PublicInstanceMap = {
$el: (i) => i.vnode.el,
+ $slots: (i) => i.slots,
};
componentSlot.ts
/*
* @Author: Lin zefan
* @Date: 2022-03-27 11:18:29
* @LastEditTime: 2022-03-27 12:23:50
* @LastEditors: Lin zefan
* @Description: 初始化slot
* @FilePath: \mini-vue3\src\runtime-core\componentSlot.ts
*
*/
export function initSlots(instance, children) {
instance.slots = Array.isArray(children) ? children : [children];
}
具名插槽
App.js
import { h } from "../../lib/mini-vue.esm.js";
import Child from "./Child.js";
export default {
name: "App",
setup() {},
render() {
return h("div", {}, [
h("div", {}, "你好"),
h(
Child,
{
msg: "your name is child",
},
{
default: [h("div", {}, "default")],
header: [h("div", {}, "header")],
footer: [h("div", {}, "footer")],
}
// [h("div", {}, "123"), h("div", {}, "456")]
),
]);
},
};
Child.js
import { h, renderSlots } from "../../lib/mini-vue.esm.js";
export default {
name: "Child",
setup(props, context) {},
render() {
console.log("this.$slots", this.$slots);
return h("div", {}, [
h("div", {}, "child"),
h("div", {}, [
renderSlots(this.$slots, "header"),
renderSlots(this.$slots),
renderSlots(this.$slots, "footer"),
]),
]);
},
};
componentSlot.ts
/*
* @Author: Lin zefan
* @Date: 2022-03-27 11:18:29
* @LastEditTime: 2022-03-27 12:19:35
* @LastEditors: Lin zefan
* @Description: 初始化slot
* @FilePath: \mini-vue3\src\runtime-core\componentSlot.ts
*
*/
/*
* @Author: Lin zefan
* @Date: 2022-03-27 11:18:29
* @LastEditTime: 2022-03-27 12:23:50
* @LastEditors: Lin zefan
* @Description: 初始化slot
* @FilePath: \mini-vue3\src\runtime-core\componentSlot.ts
*
*/
export function initSlots(instance, children) {
normalizeSlotObject(children, instance.slots);
}
function normalizeSlotObject(children, slots) {
for (const key in children) {
if (Object.prototype.hasOwnProperty.call(children, key)) {
const value = children[key];
// 直接把children对应key的slots给到instance.slots对应的key
slots[key] = normalizeSlotValue(value);
}
}
}
function normalizeSlotValue(slots: any): any {
// 统一转换为数组,children接收的是一个数组
return Array.isArray(slots) ? slots : [slots];
}
renderSlots.ts
/*
* @Author: Lin zefan
* @Date: 2022-03-27 12:03:47
* @LastEditTime: 2022-03-27 12:35:22
* @LastEditors: Lin zefan
* @Description:
* @FilePath: \mini-vue3\src\runtime-core\helpers\renderSlots.ts
*
*/
import { createdVNode } from "../vnode";
export function renderSlots(slots, name = "default") {
/** 返回一个vnode
* 1. 其本质和 h 是一样的
* 2. 通过name取到对应的slots
*/
return createdVNode("div", {}, slots[name]);
}
作用域插槽
App.js
import { h } 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),
}
),
]);
},
};
Child.js
import { h, renderSlots } from "../../lib/mini-vue.esm.js";
export default {
name: "Child",
setup(props, context) {},
render() {
console.log("this.$slots", this.$slots);
return h("div", {}, [
h("div", {}, "child"),
h("div", {}, [
renderSlots(this.$slots, "header"),
renderSlots(this.$slots, "default", {
age: 18,
}),
renderSlots(this.$slots, "main", {
content: "main的内容",
}),
renderSlots(this.$slots, "footer", {
name: "foo",
}),
]),
]);
},
};
支持函数形式渲染
componentSlot.ts
function normalizeSlotObject(children, slots) {
for (const key in children) {
if (Object.prototype.hasOwnProperty.call(children, key)) {
const value = children[key];
+ if (typeof value === "function") {
+ /**
+ * 1. 如果是一个函数,那初始化的时候就返回一个函数
+ * 2. props为作用域插槽的值,在renderSlots函数中会传递过来
+ */
+ const handler = (props) => normalizeSlotValue(value(props));
+ slots[key] = handler;
} else {
// 不是函数,是一个是h对象,或者h对象数组集合
slots[key] = normalizeSlotValue(value);
}
}
}
}
renderSlots.ts
/*
* @Author: Lin zefan
* @Date: 2022-03-27 12:03:47
* @LastEditTime: 2022-03-27 14:26:00
* @LastEditors: Lin zefan
* @Description:
* @FilePath: \mini-vue3\src\runtime-core\helpers\renderSlots.ts
*
*/
import { createdVNode } 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("div", {}, slot(props));
+ }
// 不是函数,是h对象,或者h对象数组集合
return createdVNode("div", {}, slot);
}
}
兼容没有slots的情况
在Child新增了一个main的插槽
render() {
console.log("this.$slots", this.$slots);
return h("div", {}, [
h("div", {}, "child"),
h("div", {}, [
renderSlots(this.$slots, "header"),
renderSlots(this.$slots, "default", {
age: 18,
}),
+ renderSlots(this.$slots, "main", {
+ content: "main的内容",
+ }),
renderSlots(this.$slots, "footer", {
name: "foo",
}),
]),
]);
},
但是App.js没有去创建对应的插槽,会报错
// App.js
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),
}
),
]);
},
基于这种情况,要加个flag判断,在初始化Slots的时候判断是否需要初始化
// render.ts
export function patch(vnode, container) {
if (!vnode) return;
if (isObject(vnode.type)) {
// 是一个Component
processComponent(vnode, container);
} else if (typeof vnode.type === "string") {
// 是一个element
processElement(vnode, container);
}
// if (root === "component") {
// // 是一个Component
// processComponent(vnode, container);
// } else if (root === "element") {
// // 是一个element
// processElement(vnode, container);
// }
}