第一次听slot 是从 vue 的文档里面。其实vue 也是参考webcomponents 的草案的。
今天来用react 的jsx 来实现一下这个slot 吧。
1. 使用<slot> 作为内容分发的出口
vue 的使用例子
// demo.vue
<navigation-link> Your Profile </navigation-link>
// navigation-link.vue
<a>
<slot>
// Your Profile 将会出现在这里
</slot>
</a>
react 的实现
// demo.js
export default function Demo() {
return (
<Template>
<div slot>demo content</div>
</Template>
);
}
// hook.js
export function useSlots(children) {
let slots = { default: [] }
// 读取Children 分发内容,如果是设置slot=true,说明是<div slot>abc</div> 的格式,存入slots.default 数组里面备用
Children.toArray(children).forEach(child => {
let slotName = child.props.slot
if (slotName === true) {
slotName = 'default'
}
slots[slotName].push(child)
})
const Slot = () => {
return slots.default.length ? slots.default : null;
};
return [Slot]
}
// Template.js
export default function Template(props) {
const [Slot] = useSlots(props.children)
return (
<div>
<Slot>content</Slot>
</div>
);
}
2. 后备内容
// hook.js
export function useSlots(children) {
// ...
const Slot = ({ name, children: slotChildren }) => {
// 如果slots.default 是空数组,就渲染slotChildren
return slots.default.length ? slots.default : (slotChildren || null);
};
return [Slot]
}
3.具名插槽
// hook.js
export function useSlots(children) {
let slots = { default: [] }
Children.toArray(children).forEach(child => {
let slotName = child.props.slot
if (slotName === true) {
slotName = 'default'
}
// 如果是<div slot='head'> 这样的格式,把内容存在slots.head 数组里面备用
if (!Array.isArray(slots[slotName])) {
slots[slotName] = []
}
slots[slotName].push(child)
})
const Slot = ({ name, children: slotChildren }) => {
if (!name) {
return slots.default.length ? slots.default : (slotChildren || null);
}
// 从slots.head 数组里面拿出来
return slots[name] || slotChildren || null;
};
return [Slot]
}
// Template.js
export default function Template(props) {
const [Slot] = useSlots(props.children)
return (
<div>
<div slot="head">head from top</div>
<Slot>content</Slot>
</div>
);
}
4. vue 实例中可以通过this.$slots 获取插槽内容
// hook.js
export function useSlots(children) {
let slots = { default: [] }
// ...
return [Slot, slots]
}
// Template.js
export default function Template(props) {
const [Slot, slots] = useSlots(props.children)
console.log('slots.head', slots.head)
return (
<div>
<Slot>content</Slot>
</div>
);
}
5. react 的类组件用不了hook,需要提供SlotWrap 高阶函数处理一下
// hook.js
export function _useSlots(children) {
// ...
return [Slot, slots]
}
export const useSlots = _useSlots
export function SlotWrap(Component){
return function(props){
// 直接调用useSlots 会触发React Hook "useSlots" cannot be called inside a callback.
// React Hooks must be called in a React function component or a custom React Hook function.
// 所以把useSlots重命名为_useSlots
const [Slot, slots] = _useSlots(props.children)
const _props = {...props}
_props.$Slot = Slot
_props.$slots = slots
return <Component {..._props}/>
}
}
// ClassTemplate.js
export default class ClassTemplate {
render(){
const Slot = this.props.$Slot
return (
<div>
<Slot>content</Slot>
</div>
);
}
}
export default SlotWrap(Template)
ok,今天先到这里