普通插槽
happy path
// App.js
export const App = {
render() {
const app = h("div", {}, "App");
const foo = h(Foo, {}, h("p", {}, "123"));
return h("div", {}, [app, foo]);
},
setup() {
return {};
},
};
// Foo.js
export const Foo = {
setup() {
return {};
},
render() {
const foo = h("p", {}, "foo");
return h("div", {}, [
renderSlots(this.$slots),
foo,
]);
},
};
实现目标:把App.js中h("p", {}, "123")添加到foo组件内
代码实现
通过this获取到$slots
// componentPublicInstance.ts
const publicProertiesMap = {
$el: (i) => i.vnode.el,
$slots: (i) => i.slots,
}
export const PublicInstanceProxyhandlers = {
...
}
接下来只需要构建instance.slots即可
初始化slots
// component.ts
export function setupComponent(instance) {
initSlots(instance, instance.vnode.children);
}
// componentSlots
export function initSlots(instance, children) {
instance.slots = children
}
通过renderSlots,在Foo.js中渲染插槽
// renderSlots
export function renderSlots(slots) {
return createVNode("div", {}, slots);
}
这样就实现了一个简答的插槽功能,但是插槽有可能是个数组
// App.js
export const App = {
render() {
const app = h("div", {}, "App");
const foo = h(Foo, {}, [h("span", {}, "footer "), h("span", {}, "123")]);
return h("div", {}, [app, foo]);
},
setup() {
return {};
},
};
在componentSlots中下文章
// componentSlots
export function initSlots(instance, children) {
instance.slots = Array.isArray(children) ? children : [children]
}
具名插槽
实现目标:1. 获取到要渲染的元素 2. 获取到要渲染的位置
happy path
// App.js
export const App = {
name: "App",
render() {
const app = h("div", {}, "App");
const foo = h(
Foo,
{},
{
header: h("p", {}, "header"),
footer: h("p", {}, "footer"),
}
);
return h("div", {}, [app, foo]);
},
setup() {
return {};
},
};
// Foo.js
export const Foo = {
name: "Foo",
setup() {
return {};
},
render() {
const foo = h("p", {}, "foo");
const age = 18;
return h("div", {}, [
renderSlots(this.$slots, "header"),
foo,
renderSlots(this.$slots, "footer"),
]);
},
};
代码实现
// renderSlots.ts
export function renderSlots(slots, name) {
const slot = slots[name];
if (slot) {
return createVNode("div", {}, slot);
}
}
// componentSlots.ts
export function initSlots(instance, children) {
normalizeObjectSlots(instance.slots, children);
}
function normalizeObjectSlots(slots, children) {
for (let key in children) {
let value = children[key];
slots[key] = normalizeSlotValue(value);
}
}
function normalizeSlotValue(value) {
return Array.isArray(value) ? value : [value];
}
作用域插槽
实现目标:可以获取插槽组件内部的变量
happy path
// Foo.js
export const Foo = {
name: "Foo",
setup() {
return {};
},
render() {
const foo = h("p", {}, "foo");
const age = 18;
return h("div", {}, [
renderSlots(this.$slots, "header", {
age,
}),
foo,
renderSlots(this.$slots, "footer"),
]);
},
};
// App.js
export const App = {
name: "App",
render() {
const app = h("div", {}, "App");
const foo = h(
Foo,
{},
{
header: ({ age }) => h("p", {}, "header" + age),
footer: () =>
h("p", {}, [h("span", {}, "footer "), h("span", {}, "123")]),
}
);
// const foo = h(Foo, {}, h("p", {}, "123"));
return h("div", {}, [app, foo]);
},
setup() {
return {};
},
};
代码实现
在renderSlots函数中slot变成了一个function
// renderSlots
export function renderSlots(slots, name, props) {
const slot = slots[name];
if (slot) {
if (typeof slot === "function") {
return createVNode("div", {}, slot(props));
}
}
}
initSlots的时候也需要做出修改
// componentSlots
function normalizeObjectSlots(slots, children) {
for (let key in children) {
let value = children[key];
slots[key] = (props) => normalizeSlotValue(value(props));
}
}
优化
因为不是所有的组件实例都有slots,所以在initSlots判断下是否需要做slots的处理
// vnode.ts
export function createVNode(type, props?, children?) {
...
if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
if (typeof children === "object") {
vnode.shapeFlag |= ShapeFlags.SLOT_CHILDREN;
}
}
...
}
// ShapeFlags.ts
export const enum ShapeFlags {
ELEMENT = 1, // 0001
STATEFUL_COMPONENT = 1 << 1, // 0010
TEXT_CHILDREN = 1 << 2, // 0100
ARRAY_CHILDREN = 1 << 3, // 1000
SLOT_CHILDREN = 1 << 4,
}
// componentSlots.ts
export function initSlots(instance, children) {
const { vnode } = instance;
if (vnode.shapeFlag & ShapeFlags.SLOT_CHILDREN) {
normalizeObjectSlots(instance.slots, children);
}
}