vue3源码分析——实现组件通信provide,inject
ztj100 2024-11-12 14:22 12 浏览 0 评论
引言
<<往期回顾>>
- vue3源码分析——rollup打包monorepo
- vue3源码分析——实现组件的挂载流程
- vue3源码分析——实现props,emit,事件处理等
- vue3源码分析——实现slots
本期来实现, vue3组件通信的provide,inject,所有的源码请查看
getCurrentInstance
在实现provide/inject之前,先来实现getCurrentInstance,由于在provide/inject中会使用到这个api,在开发的时候,这个api使用的频率也是挺频繁的。
getCurrentInstance 是获取当前组件的实列,并且只能在setup函数中使用
测试用例
test('test getCurrentInstance', () => {
const Foo = {
name: 'Foo',
setup() {
// 获取子组件的实列,并且期望是子组件的名称是Foo
const instance = getCurrentInstance();
expect(instance.type.name).toBe('Foo');
return {
count: 1
}
},
render() {
return h('div', {}, '122')
}
}
const app = createApp({
name: 'App',
setup() {
// 获取父组件的实例,期待父组件的名称是定义的App
const instance = getCurrentInstance();
expect(instance.type.name).toBe('App');
return {
count: 2
}
},
render() {
return h('div', { class: 'container' }, [h(Foo, {}, {})])
}
})
// 挂载组件
const appDoc = document.querySelector('#app')
app.mount(appDoc);
})
分析
在上面的测试拥立中,可以得到以下内容:
- getCurrentInstance只能在setup函数中使用
- 对外导出的api,获取的是当前组件的实列
问题解决:
对于上面两个问题,只需要导出一个函数,并且在全局定义一个变量,在setup执行的时候,赋值全局变量即可拿到当前组件的实例,然后setup执行之后,清空即可
编码
// setup执行是在setupStatefulComponent函数中执行的,来进行改造
// 定义全局的变量,存储当前实例
let currentInstance = null;
function setupStatefulComponent(instance: any) {
// ……省略其他
// 获取组件的setup
const { setup } = Component;
if (setup) {
currentInstance = instance
const setupResult = setup(shallowReadonly(instance.props), { emit: instance.emit })
// 情况操作
currentInstance = null
}
// ……省略其他
}
// 对外导出函数,提供全局的api
export function getCurrentInstance() {
return currentInstance
}
getCurrentInstance 有没有想到实现方式这么简单哇!
provide/inject
provide和inject需要配套使用才方便用于测试,这里就从功能分析,来逐步完成这两个api.
父子组件传值
父子组件传值可以使用props/emit来实现,还记得是怎么实现的么?
测试用例
test('test provide basic use', () => {
const Foo = {
name: 'Foo',
setup() {
// 子组件接受数据
const count = inject('count')
const str = inject('str')
return {
count,
str
}
},
render() {
return h('div', {}, this.str + this.count)
}
}
const app = createApp({
name: 'App',
setup() {
// 父组件提供数据,
provide('count', 1);
provide('str', 'str');
},
render() {
return h('div', { class: 'container' }, [h(Foo, {})])
}
})
const appDoc = document.querySelector('#app')
app.mount(appDoc);
const container = document.querySelector('.container') as HTMLElement;
expect(container.innerHTML).toBe('<div>str1</div>')
})
分析
从上面的测试用例中进行需求分析,
- provide api是需要有两个参数,一个key,另一个是value, 有点类似与sessionStorage这种set值的方式
- inject api则是只需要一个key,来进行get操作
- provide存的数据,存在哪里呢?
问题解决: 问题1和问题2都很好解决,对外导出函数,传递对应的参数,只是数据存储在哪里的问题,经过仔细的思考,会发现,组件的数据是需要进行共享的,父组件存入的数据,里面的所有子组件和孙子组件都可以共享,那么存储在实例上,是不是一个不错的选择呢? inject 是获取父级组件的数据,那么在实列上还需要传入parent
编码
由于需要在实例上存储provide,首先就在createInstance中的实例,在初始化就赋值
export function createComponentInstance(vnode, parent) {
const instance = {
// ……省略其他属性
// 提供数据
provides: {},
parent,
}
return instance
}
// 有了实例,分别创建provide,inject函数
export function provide(key, val){
// 将数据存在实例上,先进行获取
const instance = getCurrentInstance();
if(instance){
instance.provides[key] = val
}
}
export function inject(key){
// 从实列上取值
const instance = getCurrentInstance();
if(instance){
// 获取父级provides
const provides = instance.parent?.provides;
if(key in provides){
return provides[key]
}
return null
}
}
一个简单的prvide/inject就实现啦,接下来进行需求升级,爷孙组件数据传递
爷孙组件传值
无可厚非,就是孙子组件需要从爷爷组件中获取值,父组件不提供数据
测试用例
test('test provide exit grandfather', () => {
const Child = {
name: 'Foo',
setup() {
// 孙子组件也可以取值
const count = inject('count')
const str = inject('str')
return {
count,
str
}
},
render() {
return h('div', {}, this.str + this.count)
}
}
const Father = {
name: 'Father',
setup() {
// 子组件可以取值
const count = inject('count')
return {
count
}
},
render() {
return h('div', {}, [h('p', {}, this.count), h(Child, {})])
}
}
const app = createApp({
name: 'App',
setup() {
// 爷爷提供数据
provide('count', 1);
provide('str', 'str');
return {}
},
render() {
return h('div', { class: 'container' }, [h(Father, {})])
}
})
const appDoc = document.querySelector('#app');
app.mount(appDoc);
const container = document.querySelector('.container') as HTMLElement;
expect(container.innerHTML).toBe('<div><p>1</p><div>str1</div></div>')
})
分析
上面的测试用例相对于父子组件的测试用例来说,增加了一个孙子组件。
- 孙子(Child组件) 和 父亲(Foo组件) 都可以获取 爷爷(App组件) 的值
- 其他的没啥变化
问题解决: 想要让孙子组件获取爷爷组件的数据,那是否可以让父组件Foo在初始化就获取他父组件App的provides
编码
// 需要在组件初始化的时候,获取父组件的数据,修改下初始化的内容
export function createComponentInstance(vnode, parent) {
const instance = {
// ……省略其他属性
// 存在则用,不存在还是空对象
provides: parent ? parent.provides : {},
parent,
}
return instance
}
是不是感觉非常简单哇,那接下来在升级下,inject获取provide的数据,需要就近原则来进行获取
就近原则获取数据
就近原则的意思是说,如果父组件有就拿父组件的,父组件没有就那爷爷组件的,爷爷组件没有继续往上找,直到找到跟组件App上,如果还没有就为null
测试用例
test('get value by proximity principle(就近原则) ', () => {
// 孙子组件来获取数据
const GrandSon = {
name: 'GrandSon',
setup() {
const count = inject('count')
const str = inject('str')
return {
count,
str
}
},
render() {
return h('div', {}, this.str + this.count)
}
}
// 子组件提供count
const Child = {
name: 'Child',
setup() {
provide('count', 100)
},
render() {
return h(GrandSon)
}
}
// 父亲组件,不提供数据
const Father = {
name: 'Father',
render() {
return h(Child)
}
}
// 跟组件app,提供,count,str
const app = createApp({
name: 'App',
setup() {
provide('count', 1);
provide('str', 'str');
return {}
},
render() {
return h('div', { class: 'container' }, [h(Father, {})])
}
})
// ……省略挂载
const container = document.querySelector('.container') as HTMLElement;
expect(container.innerHTML).toBe('<div>str100</div>')
})
分析
在上面的测试用例中,存在4个组件,只有app组件和Child组件提供数据,其他只是嵌套,不提供数据。存在下面问题:
- inject怎么去查找provides的数据,一层一层的查找
问题解决: 怎么查找呢,在inject里面递归? NO,换一个角度,inject查找数据的时候,是不是有点像原型链的方式来进行查找呢?YES,那就是需要在provide里面来构建一条原型链。
原型链, 啥叫做原型链呢?请查看
编码
// 只需要改造provide函数即可
export function provide(key, val) {
// 数据需要存储在当前的实例上面
const instance = getCurrentInstance();
if (instance) {
let { provides } = instance;
// 正对多层组件,需要把当前组件的__proto__绑定到父级上面,形成原型链,可以访问到最顶层的数据
const parentProvides = instance.parent && instance.parent.provide;
// 只有父级的provides和当前的provides是相同的时候为第一次调用provide,后续调用就不需要绑定原型了
if (parentProvides === provides) {
provides = instance.providers = Object.create(parentProvides || {});
}
provides[key] = val;
}
}
总结
本期主要完成了getCurrentInstance,provide,inject的实现,在getCurrentInstance中只是用了一个中间变量,而provide是把数据存在当前的instance当中,provide里面还用到了原型链的知识,通过原型的方式来查询key是否存在,不存在则往上查找
相关推荐
- 告别手动操作:一键多工作表合并的实用方法
-
通常情况下,我们需要将同一工作簿内不同工作表中的数据进行合并处理。如何快速有效地完成这些数据的整合呢?这主要取决于需要合并的源数据的结构。...
- 【MySQL技术专题】「优化技术系列」常用SQL的优化方案和技术思路
-
概述前面我们介绍了MySQL中怎么样通过索引来优化查询。日常开发中,除了使用查询外,我们还会使用一些其他的常用SQL,比如INSERT、GROUPBY等。对于这些SQL语句,我们该怎么样进行优化呢...
- 9.7寸视网膜屏原道M9i双系统安装教程
-
泡泡网平板电脑频道4月17日原道M9i采用Win8安卓双系统,对于喜欢折腾的朋友来说,刷机成了一件难事,那么原道M9i如何刷机呢?下面通过详细地图文,介绍原道M9i的刷机操作过程,在刷机的过程中,要...
- 如何做好分布式任务调度——Scheduler 的一些探索
-
作者:张宇轩,章逸,曾丹初识Scheduler找准定位:分布式任务调度平台...
- mysqldump备份操作大全及相关参数详解
-
mysqldump简介mysqldump是用于转储MySQL数据库的实用程序,通常我们用来迁移和备份数据库;它自带的功能参数非常多,文中列举出几乎所有常用的导出操作方法,在文章末尾将所有的参数详细说明...
- 大厂面试冲刺,Java“实战”问题三连,你碰到了哪个?
-
推荐学习...
- 亿级分库分表,如何丝滑扩容、如何双写灰度
-
以下是基于亿级分库分表丝滑扩容与双写灰度设计方案,结合架构图与核心流程说明:一、总体设计目标...
- MYSQL表设计规范(mysql表设计原则)
-
日常工作总结,不是通用规范一、表设计库名、表名、字段名必须使用小写字母,“_”分割。...
- 怎么解决MySQL中的Duplicate entry错误?
-
在使用MySQL数据库时,我们经常会遇到Duplicateentry错误,这是由于插入或更新数据时出现了重复的唯一键值。这种错误可能会导致数据的不一致性和完整性问题。为了解决这个问题,我们可以采取以...
- 高并发下如何防重?(高并发如何防止重复)
-
前言最近测试给我提了一个bug,说我之前提供的一个批量复制商品的接口,产生了重复的商品数据。...
- 性能压测数据告诉你MySQL和MariaDB该怎么选
-
1.压测环境为了尽可能的客观公正,本次选择同一物理机上的两台虚拟机,一台用作数据库服务器,一台用作运行压测工具mysqlslap,操作系统均为UbuntuServer22.04LTS。...
- 屠龙之技 --sql注入 不值得浪费超过十天 实战中sqlmap--lv 3通杀全国
-
MySQL小结发表于2020-09-21分类于知识整理阅读次数:本文字数:67k阅读时长≈1:01...
- 破防了,谁懂啊家人们:记一次 mysql 问题排查
-
作者:温粥一、前言谁懂啊家人们,作为一名java开发,原来以为mysql这东西,写写CRUD,不是有手就行吗;你说DDL啊,不就是设计个表结构,搞几个索引吗。...
- MySQL 之 Performance Schema(mysql安装及配置超详细教程)
-
MySQL之PerformanceSchema介绍PerformanceSchema提供了在数据库运行时实时检查MySQL服务器的内部执行情况的方法,通过监视MySQL服务器的事件来实现监视内...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- 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)
- node卸载 (33)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- exceptionininitializererror (33)
- 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)