共计 5534 个字符,预计需要花费 14 分钟才能阅读完成。
react 组件跨层通信 笔记
组件与组件之间的关系,大抵可分为 4 种。
- 父与子:父组件包裹子组件,父组件向子组件传递数据。
- 子与父:子组件存在于父组件之中,子组件须要向父组件传递数据。
- 兄弟:两个组件并列存在于父组件中,须要金属数据进行互相传递。
- 无间接关系:两个组件并没有间接的关联关系,处在一棵树中相距甚远的地位,但须要共享、传递数据。
通信形式总结如下:
父与子
父与子的通信次要是通过 props。React 开发的每个组件都在应用这样的设计模式。每个组件都会在父级被应用,再传入 Props,实现信息的传递。这样的交互方式只管不起眼,容易让人疏忽,但正是最经典的设计。
子与父
子与父的通信次要依赖 回调函数。
回调函数
回调函数在 JavaScript 中称为 callback。React 在设计中沿用了 JavaScript 的经典设计,容许函数作为参数赋值给子组件。最根底的用法就像上面的例子一样,通过包装传递 text 的值。
class Child extends React.Component {handleChanged = (e) => {
// 调用父组件传进来的回调函数
this.props.onChangeText(e.target.text)
}
render() {return <input onChange={handleTextChanged} />
}
}
class Father extends React.Component {handleTextChanged = (text) => {console.log(text)
}
render() {
return (
// 把函数当做 props 参数传给子组件
<Child onChangeText={this.handleTextChanged} />
)
}
}
实例函数
须要留神的是,实例函数是一种 不被举荐的应用形式。这种通信形式常见于 React 风行初期,那时有很多组件都通过封装 jQuery 插件生成。最常见的一种状况是在 Modal 中应用这种形式。如下代码所示:
import React from 'react'
class HomePage extends React.Component {modalRef = React.createRef()
showModal = () ={this.modalRef.show()
}
hideModal = () => {
// 通过 ref 获取到的实例操作,不过当初个别都不这么用了,当初会给一个参数 show=true 或 show=false 来管制组件显示或者暗藏
this.modalRef.hide();}
render() {
const {text} = this.state
return (
<>
<Button onClick={this.showModal}> 展现 Modal </Button>
<Button onClick={this.hideModal}> 暗藏 Modal </Button>
<Modal ref={modalRef} />
</>
/>
)
}
兄弟
兄弟组件之间的通信,往往 依赖独特的父组件进行直达。也就是状态晋升。
无间接关系
无间接关系就是两个组件的间接关联性并不大,它们身处于多层级的嵌套关系中,既不是父子关系,也不相邻,并且绝对边远。他们之前通信的形式有:
- Context,即 React 的 Context API。
- 全局变量与事件(不太举荐)。全局变量,顾名思义就是放在 Window 上的变量。但值得注意的是批改 Window 上的变量并不会引起 React 组件从新渲染。
- 状态治理框架。状态治理框架提供了十分丰盛的解决方案,常见的有 Flux、Redux 及 Mobx。
- “公布 - 订阅”模式
“公布 - 订阅”模式
“公布 - 订阅”模式堪称是解决通信类问题的“万金油”,应用公布 - 订阅模式的长处在于,监听事件的地位和触发事件的地位是不受限的,就算相隔十万八千里,只有它们在同一个上下文里,就可能彼此感知。这个个性,太适宜用来应答“任意组件通信”这种场景了。
公布 - 订阅模型 API 设计思路
- on():负责注册事件的监听器,指定事件触发时的回调函数。
- emit():负责触发事件,能够通过传参使其在触发的时候携带数据。
- off():负责监听器的删除。
公布 - 订阅模型编码实现(重要,面试考点)
“公布 - 订阅”模式不仅在利用层面非常受欢迎,它更是面试官的心头好。在波及设计模式的面试中,如果只容许出一道题,那么我置信大多数的面试官都会和我一样,会毫不犹豫地抉择考查“公布 - 订阅模式的实现”。
在写代码之前,先要捋分明思路。这里我把“实现 EventEmitter”这个大问题,拆解为 3 个具体的小问题,上面咱们一一来解决。
- 问题一:事件和监听函数的对应关系如何解决?
提到“对应关系”,应该联想到的是“映射”。在 JavaScript 中,解决“映射”咱们大部分状况下都是用对象来做的。所以说在全局咱们须要设置一个对象,来存储事件和监听函数之间的关系:
constructor() {
// eventMap 用来存储事件和监听函数之间的关系
this.eventMap= {}}
- 问题二:如何实现订阅?
所谓“订阅”,也就是注册事件监听函数的过程。这是一个“写”操作,具体来说就是把事件和对应的监听函数写入到 eventMap 外面去:
// type 这里就代表事件的名称
on(type, handler) {
// hanlder 必须是一个函数,如果不是间接报错
if(!(handler instanceof Function)) {throw new Error("哥 你错了 请传一个函数")
}
// 判断 type 事件对应的队列是否存在
if(!this.eventMap[type]) {
// 若不存在,新建该队列
this.eventMap[type] = []}
// 若存在,间接往队列里推入 handler
this.eventMap[type].push(handler)
}
- 问题三:如何实现公布?
订阅操作是一个“写”操作,相应的,公布操作就是一个“读”操作。公布的实质是触发装置在某个事件上的监听函数,咱们须要做的就是找到这个事件对应的监听函数队列,将队列中的 handler 顺次执行出队:
// 别忘了咱们后面说过触发时是能够携带数据的,params 就是数据的载体
emit(type, params) {
// 假如该事件是有订阅的(对应的事件队列存在)if(this.eventMap[type]) {
// 将事件队列里的 handler 顺次执行出队
this.eventMap[type].forEach((handler, index)=> {
// 留神别忘了读取 params
handler(params)
})
}
}
到这里,最最要害的 on 办法和 emit 办法就实现结束了。最初咱们补充一个 off 办法:
// 监听器的删除
/*
>>> 是无符号按位右移运算符。思考 indexOf 返回 -1 的状况:splice 办法喜爱把 - 1 解读为以后数组的最初
一个元素,这样子的话,在压根没有对应函数能够删的状况下,不管三七二十一就把最初一个元素给干掉了。而 >>> 符号对正整数没有影响,但对于 - 1 来说它会把 - 1 转换为一个微小的数(你能够本地运行下试试看,应该是一个 32 位全是 1 的二进制数,折算成十进制就是 4294967295)。这个微小的索引 splice 是找不到的,找不到就不删,于是所有保持原状,刚好合乎咱们的预期。*/
off(type, handler) {if(this.eventMap[type]) {this.eventMap[type].splice(this.eventMap[type].indexOf(handler)>>>0,1)
}
}
接着把这些代码片段拼接进一个 class 外面,一个外围性能齐备的 EventEmitter 就实现啦:
class myEventEmitter {constructor() {
// eventMap 用来存储事件和监听函数之间的关系
this.eventMap = {};}
// type 这里就代表事件的名称
on(type, handler) {
// hanlder 必须是一个函数,如果不是间接报错
if (!(handler instanceof Function)) {throw new Error("哥 你错了 请传一个函数");
}
// 判断 type 事件对应的队列是否存在
if (!this.eventMap[type]) {
// 若不存在,新建该队列
this.eventMap[type] = [];}
// 若存在,间接往队列里推入 handler
this.eventMap[type].push(handler);
}
// 别忘了咱们后面说过触发时是能够携带数据的,params 就是数据的载体
emit(type, params) {
// 假如该事件是有订阅的(对应的事件队列存在)if (this.eventMap[type]) {
// 将事件队列里的 handler 顺次执行出队
this.eventMap[type].forEach((handler, index) => {
// 留神别忘了读取 params
handler(params);
});
}
}
// 监听器的删除
/*
>>> 是无符号按位右移运算符。思考 indexOf 返回 -1 的状况:splice 办法喜爱把 - 1 解读为以后数组的最初
一个元素,这样子的话,在压根没有对应函数能够删的状况下,不管三七二十一就把最初一个元素给干掉了。而 >>> 符号对正整数没有影响,但对于 - 1 来说它会把 - 1 转换为一个微小的数(你能够本地运行下试试看,应该是一个 32 位全是 1 的二进制数,折算成十进制就是 4294967295)。这个微小的索引 splice 是找不到的,找不到就不删,于是所有保持原状,刚好合乎咱们的预期。*/
off(type, handler) {if (this.eventMap[type]) {this.eventMap[type].splice(this.eventMap[type].indexOf(handler) >>> 0, 1);
}
}
}
上面咱们对 myEventEmitter 进行一个简略的测试,创立一个 myEvent 对象作为 myEventEmitter 的实例,而后针对名为“test”的事件进行监听和触发:
// 实例化 myEventEmitter
const myEvent = new myEventEmitter();
// 编写一个简略的 handler
const testHandler = function (params) {console.log(`test 事件被触发了,testHandler 接管到的入参是 ${params}`);
};
// 监听 test 事件
myEvent.on("test", testHandler);
// 在触发 test 事件的同时,传入心愿 testHandler 感知的参数
myEvent.emit("test", "newState");
当初你能够试想一下,对于任意的两个组件 A 和 B,如果我心愿实现单方之间的通信,借助 EventEmitter 来做就很简略了,以数据从 A 流向 B 为例。
咱们能够在 B 中编写一个 handler(记得将这个 handler 的 this 绑到 B 身上),在这个 handler 中进行以 B 为上下文的 this.setState 操作,而后将这个 handler 作为监听器与某个事件关联起来。比方这样:
// 留神这个 myEvent 是提前实例化并挂载到全局的,此处不再反复示范实例化过程
const globalEvent = window.myEvent
class B extends React.Component {
// 这里省略掉其余业务逻辑
state = {newParams: ""};
handler = (params) => {
this.setState({newParams: params});
};
bindHandler = () => {globalEvent.on("someEvent", this.handler);
};
render() {
return (
<div>
<button onClick={this.bindHandler}> 点我监听 A 的动作 </button>
<div>A 传入的内容是[{this.state.newParams}]</div>
</div>
);
}
}
接下来在 A 组件中,只须要间接触发对应的事件,而后将心愿携带给 B 的数据作为入参传递给 emit 办法即可。代码如下:
class A extends React.Component {
// 这里省略掉其余业务逻辑
state = {infoToB: "哈哈哈哈我来自 A"};
reportToB = () => {
// globalEvent 从全局对象 window 获取
// 这里的 infoToB 示意 A 本身状态中须要让 B 感知的那局部数据
globalEvent.emit("someEvent", this.state.infoToB);
};
render() {return <button onClick={this.reportToB}> 点我把 state 传递给 B </button>;
}
}
如此一来,便可能实现 A 到 B 的通信了。这里我将 A 与 B 编排为兄弟组件,代码如下:
export default function App() {
return (
<div className="App">
<B />
<A />
</div>
);
}
你须要把重点放在对编码的实现和了解上,尤其是基于“公布 - 订阅”模式实现的 EventEmitter,多年来始终是面试的大热点,务必要好好把握。
这个公布 - 订阅模式是我买的专栏里讲的,我觉讲的比拟好,就间接拿过去了,我感觉老师的功底还是挺深厚的,就是课程数量有点少,感觉把有些内容拿进去精讲一下就好了。上面的二维码就是课程,有须要的同学能够本人买来看看。