vue3源码分析——实现slots vue3.0 slot
ztj100 2024-12-22 22:03 20 浏览 0 评论
引言
<<往期回顾>>
- vue3源码分析——rollup打包monorepo
- vue3源码分析——实现组件的挂载流程
- vue3源码分析——实现props,emit,事件处理等
本期来实现, 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>'
)
})
复制代码
需求分析
通过上面的测试用例,可以分析以下内容:
- 父组件使用子组件传入插槽的方式是在h的第三个参数,并且传入的是一个对象,value的值可以是对象,或者是数组
- 子组件中使用插槽的时候,是在this.$slots中获取的
- 并且还实现了一个renderSlot的方法,renderSlot是将this.$slots调用h转变为vnode
问题解决:
- 需要在绑定在this上面,那就在setupStatefulComponent函数代理中加入判断,传入的$slots ;
- 判断$slot是否在组件的代理中,然后代理需要把slots绑定在instance上面并且绑定值的时候需要把传入的对象统一转成数组;
- renderSlot方法调用了h函数,把一个数据转成vnode
编码实现
// 需要把$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传入第二个参数,然后可以获取对于的slots
问题解决
直接在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>')
})
复制代码
需求分析
通过上面的测试用例,分析出以下内容:
- 传入插槽的时候,传入一个函数,函数可以拿到子组件传过来的参数
- renderSlots可以传入第三个参数props, 用于接收子组件往父组件传入的参数
问题解决:
- 问题1: 只需要在传入插槽的时候进行一下判断,如果是函数的话,需要进行函数执行,并且传入参数
- 问题2: 也是对传入的内容进行判断,函数做传入参数处理
编码
// 在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
复制代码
到此,整个测试用例就可以完美通过啦!
相关推荐
- 30天学会Python编程:16. Python常用标准库使用教程
-
16.1collections模块16.1.1高级数据结构16.1.2示例...
- 强烈推荐!Python 这个宝藏库 re 正则匹配
-
Python的re模块(RegularExpression正则表达式)提供各种正则表达式的匹配操作。...
- Python爬虫中正则表达式的用法,只讲如何应用,不讲原理
-
Python爬虫:正则的用法(非原理)。大家好,这节课给大家讲正则的实际用法,不讲原理,通俗易懂的讲如何用正则抓取内容。·导入re库,这里是需要从html这段字符串中提取出中间的那几个文字。实例一个对...
- Python数据分析实战-正则提取文本的URL网址和邮箱(源码和效果)
-
实现功能:Python数据分析实战-利用正则表达式提取文本中的URL网址和邮箱...
- python爬虫教程之爬取当当网 Top 500 本五星好评书籍
-
我们使用requests和re来写一个爬虫作为一个爱看书的你(说的跟真的似的)怎么能发现好书呢?所以我们爬取当当网的前500本好五星评书籍怎么样?ok接下来就是学习python的正确姿...
- 深入理解re模块:Python中的正则表达式神器解析
-
在Python中,"re"是一个强大的模块,用于处理正则表达式(regularexpressions)。正则表达式是一种强大的文本模式匹配工具,用于在字符串中查找、替换或提取特定模式...
- 如何使用正则表达式和 Python 匹配不以模式开头的字符串
-
需要在Python中使用正则表达式来匹配不以给定模式开头的字符串吗?如果是这样,你可以使用下面的语法来查找所有的字符串,除了那些不以https开始的字符串。r"^(?!https).*&...
- 先Mark后用!8分钟读懂 Python 性能优化
-
从本文总结了Python开发时,遇到的性能优化问题的定位和解决。概述:性能优化的原则——优化需要优化的部分。性能优化的一般步骤:首先,让你的程序跑起来结果一切正常。然后,运行这个结果正常的代码,看看它...
- Python“三步”即可爬取,毋庸置疑
-
声明:本实例仅供学习,切忌遵守robots协议,请不要使用多线程等方式频繁访问网站。#第一步导入模块importreimportrequests#第二步获取你想爬取的网页地址,发送请求,获取网页内...
- 简单学Python——re库(正则表达式)2(split、findall、和sub)
-
1、split():分割字符串,返回列表语法:re.split('分隔符','目标字符串')例如:importrere.split(',','...
- Lavazza拉瓦萨再度牵手上海大师赛
-
阅读此文前,麻烦您点击一下“关注”,方便您进行讨论和分享。Lavazza拉瓦萨再度牵手上海大师赛标题:2024上海大师赛:网球与咖啡的浪漫邂逅在2024年的上海劳力士大师赛上,拉瓦萨咖啡再次成为官...
- ArkUI-X构建Android平台AAR及使用
-
本教程主要讲述如何利用ArkUI-XSDK完成AndroidAAR开发,实现基于ArkTS的声明式开发范式在android平台显示。包括:1.跨平台Library工程开发介绍...
- Deepseek写歌详细教程(怎样用deepseek写歌功能)
-
以下为结合DeepSeek及相关工具实现AI写歌的详细教程,涵盖作词、作曲、演唱全流程:一、核心流程三步法1.AI生成歌词-打开DeepSeek(网页/APP/API),使用结构化提示词生成歌词:...
- “AI说唱解说影视”走红,“零基础入行”靠谱吗?本报记者实测
-
“手里翻找冻鱼,精心的布局;老漠却不言语,脸上带笑意……”《狂飙》剧情被写成歌词,再配上“科目三”背景音乐的演唱,这段1分钟30秒的视频受到了无数网友的点赞。最近一段时间随着AI技术的发展,说唱解说影...
- AI音乐制作神器揭秘!3款工具让你秒变高手
-
在音乐创作的领域里,每个人都有一颗想要成为大师的心。但是面对复杂的乐理知识和繁复的制作过程,许多人的热情被一点点消磨。...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 30天学会Python编程:16. Python常用标准库使用教程
- 强烈推荐!Python 这个宝藏库 re 正则匹配
- Python爬虫中正则表达式的用法,只讲如何应用,不讲原理
- Python数据分析实战-正则提取文本的URL网址和邮箱(源码和效果)
- python爬虫教程之爬取当当网 Top 500 本五星好评书籍
- 深入理解re模块:Python中的正则表达式神器解析
- 如何使用正则表达式和 Python 匹配不以模式开头的字符串
- 先Mark后用!8分钟读懂 Python 性能优化
- Python“三步”即可爬取,毋庸置疑
- 简单学Python——re库(正则表达式)2(split、findall、和sub)
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)
- vmware17pro最新密钥 (34)
- mysql单表最大数据量 (35)