<<往期回顾>>
本期来实现, slot——插槽,分为普通插槽,具名插槽,作用域插槽,所有的源码请查看
在 模板中使用插槽的方式如下:
<todo-button>
Add todo
</todo-button>
复制代码
在template中的内容最终会被complie成render函数,render函数里面会调用h函数转化成vnode,在vnode的使用方法如下:
render() {
return h(TodoButton, {}, this.$slots.default)
},
复制代码
看完slots的基本用法,一起来实现个slots,方便自己理解slots的原理哦!
使用slots的地方是this.slots,并且调用的属性是default,那么slots,并且调用的属性是default,那么slots则是一个对象,对象里面有插槽的名称,如果使用者没有传递,则可以通过default来进行访问。
attention!!! 由于测试的是dom,需要先写入html等,在这里需要先创建对应的节点
let AppElement: Element;
beforeEach(() => {
appElement = document.createElement('div');
appElement.id = 'app';
document.body.appendChild(appElement);
})
afterEach(() => {
document.body.innerHTML = '';
})
复制代码
本案例的测试正式开始
test('test basic slots', () => {
// 子组件Foo
const Foo = {
name: 'Foo',
render() {
return h('div', { class: 'foo' }, [h('p', {}, this.count), renderSlots(this.$slots)]);
}
}
const app = createApp({
render() {
return h('div', { class: 'contAIner' }, [
h(Foo, { count: 1 }, { default: h('div', { class: 'slot' }, 'slot1') }),
h(Foo, { count: 2 }, { default: [h('p', { class: 'slot' }, 'slot2'), h('p', { class: 'slot' }, 'slot2')] }),
])
}
})
const appDoc = document.querySelector('#app')
app.mount(appDoc);
// 测试挂载的内容是否正确
const container = document.querySelector('.container') as HTMLElement;
expect(container.innerHTML).toBe('<div class="foo"><p>1</p><div><div class="slot">slot1</div></div></div><div class="foo"><p>2</p><div><p class="slot">slot2</p><p class="slot">slot2</p></div></div>'
)
})
复制代码
通过上面的测试用例,可以分析以下内容:
问题解决:
// 需要把$slots绑定在this上面,那就需要在代理里面在加入一个判断即可
function setupStatefulComponent(instance: any) {
// 代理组件的上下文
instance.proxy = new Proxy({ }, {
get(target,key){
// 省略其他
else if(key in instance.slots){
return instance.slots[key]
}
}
})
}
// 接下里在instance上面加上slots属性
export function setupComponent(instance) {
// 获取props和children
const { props, children } = instance.vnode
// 处理props
const slots = {}
for (const key in children) {
slots[key] = Array.isArray(children[key]) ? children[key] : [children[key]]
}
instance.slots = slots
// ……省略其他
}
// 最后还需要使用renderSlot函数
export function renderSlots(slots) {
const slot = slots['default']
if (slot) {
return createVNode('div', {}, slot)
}
}
复制代码
通过上面的编码,测试用例就可以完美通关啦
具名插槽就是,插槽除了可以有多个,并且除了default外,可以加入其他的名字,具体请看测试用例
test('测试具名插槽', () => {
const Foo = {
name: 'Foo',
render() {
return h('div', { class: 'foo' },
[
renderSlots(this.$slots, 'header'),
h('div', { class: 'default' }, 'default'),
renderSlots(this.$slots, 'footer')
]
);
}
}
const app = createApp({
name: 'App',
render() {
return h('div', { class: 'container' }, [h(Foo, {}, {
header: h('h1', {}, 'header'),
footer: h('p', {}, 'footer')
})])
}
})
const appDoc = document.querySelector('#app')
app.mount(appDoc);
const container = document.querySelector('.container') as HTMLElement
expect(container.innerHTML).toBe('<div class="foo"><div><h1>header</h1></div><div class="default">default</div><div><p>footer</p></div></div>')
})
复制代码
通过上面测试用例,发现以下内容:
问题解决
直接在renderSlot里面传入第二个参数即可
// 最后还需要使用renderSlot函数
export function renderSlots(slots, name = 'default') {
const slot = slots[name]
if (slot) {
return createVNode('div', {}, slot)
}
}
复制代码
这一步是不是比较简单,相对起前面来说,正所谓,前面考虑好了,后面就舒服,接下来实现作用域插槽
作用域插槽是,每个slot里面可以传入数据,数据只在当前的slot有效,具体请看测试用例
test('测试作用域插槽', () => {
const Foo = {
name: 'Foo',
render() {
return h('div', { class: 'foo' },
[
renderSlots(this.$slots, 'header', { children: 'foo' }),
h('div', { class: 'default' }, 'default'),
renderSlots(this.$slots, 'footer')
]
);
}
}
const app = createApp({
name: 'App',
render() {
return h('div', { class: 'container' }, [h(Foo, {}, {
header: ({ children }) => h('h1', {}, 'header ' + children),
footer: h('p', {}, 'footer')
})])
}
})
const appDoc = document.querySelector('#app')
app.mount(appDoc);
const container = document.querySelector('.container') as HTMLElement
expect(container.innerHTML).toBe('<div class="foo"><div><h1>header foo</h1></div><div class="default">default</div><div><p>footer</p></div></div>')
})
复制代码
通过上面的测试用例,分析出以下内容:
问题解决:
// 在renderSlot里面传入第三个参数
export function renderSlots(slots, name = 'default', props = {}) {
const slot = slots[name];
if (slot) {
if (isFunction(slot)) {
return createVNode('div', {}, slot(props))
}
return createVNode('div', {}, slot)
}
}
// initSlot时候,需要进行函数判断
const slots = {}
// 遍历children
for (const key in children) {
// 判断传入的是否是函数,如果是函数的话,需要进行执行,并且传入参数
if (isFunction(children[key])) {
slots[key] = (props) => Array.isArray(children[key](props)) ? children[key](props) : [children[key](props)]
} else {
slots[key] = Array.isArray(children[key]) ? children[key] : [children[key]]
}
}
instance.slots = slots
复制代码
到此,整个测试用例就可以完美通过啦!