詳解React中的組件通信問題
引入
本來我是沒想過總結(jié)這些東西的,會(huì)感覺比較入門。但是之前同學(xué)去騰訊面試問到了這個(gè)問題(react或vue的組件通信),我?guī)退恚槺銓慸emo的過程中,會(huì)有一些新的體會(huì),多總結(jié)還是有利于進(jìn)步的呀。
父子組件
父 → 子
parent組件傳給child組件,符合react的單向數(shù)據(jù)流理念,自上到下傳遞props。
// 父組件
class Parent extends Component {
constructor() {
super();
this.state = {
value: '',
}
}
handleChange = e => {
this.value = e.target.value;
}
handleClick = () => {
this.setState({
value: this.value,
})
}
render() {
return (
<div>
我是parent
<input onChange={this.handleChange} />
<div className="button" onClick={this.handleClick}>通知</div>
<div>
<Child value={this.state.value} />
</div>
</div>
);
}
}
// 子組件
class Child extends Component {
render() {
const { value } = this.props;
return (
<div>
我是Child,得到傳下來的值:{value}
</div>
);
}
}
父組件做的就是定義好 state ,定義好事件函數(shù),input onChange 的時(shí)候,去緩存 value 值,然后點(diǎn)擊 button 的時(shí)候,改變 state , 子組件只負(fù)責(zé)展示 value 。
子 → 父
child 組件通知 parent 組件, 主要是依靠 parent 傳下來的 callback 函數(shù)執(zhí)行,改變 parent 組件的狀態(tài),或者把 child 自己的 state 通知 parent 。分兩種情況:
state 定義在 parent 組件
// parent
class Parent extends Component {
constructor() {
super();
this.state = {
value: '',
}
}
setValue = value => {
this.setState({
value,
})
}
render() {
return (
<div>
<div>我是parent, Value是:{this.state.value}</div>
<Child setValue={this.setValue} />
</div>
);
}
}
class Child extends Component {
handleChange = e => {
this.value = e.target.value;
}
handleClick = () => {
const { setValue } = this.props;
setValue(this.value);
}
render() {
return (
<div>
我是Child
<div className="card">
state 定義在 parent
<input onChange={this.handleChange} />
<div className="button" onClick={this.handleClick}>通知</div>
</div>
</div>
);
}
}
parent 組件把改變 state 的 setValue 函數(shù)傳給 child ,child 組件自己處理內(nèi)部的狀態(tài)(這里是表單的value值),當(dāng) child 組件分發(fā)消息的時(shí)候, 執(zhí)行 parent 的 setValue 函數(shù),從而改變了 parent 的 state,state發(fā)生變化, parent 組件執(zhí)行 re-render 。
state 定義在 child 組件
// parent
class Parent extends Component {
onChange = value => {
console.log(value, '來自 child 的 value 變化');
}
render() {
return (
<div>
<div>我是parent
<Child onChange={this.onChange} />
</div>
);
}
}
class Child extends Component {
constructor() {
super();
this.state = {
childValue: ''
}
}
childValChange = e => {
this.childVal = e.target.value;
}
childValDispatch = () => {
const { onChange } = this.props;
this.setState({
childValue: this.childVal,
}, () => { onChange(this.state.childValue) })
}
render() {
return (
<div>
我是Child
<div className="card">
state 定義在 child
<input onChange={this.childValChange} />
<div className="button" onClick={this.childValDispatch}>通知</div>
</div>
</div>
);
}
}
有時(shí)候 state 是需要定義在 child 組件的,比如彈窗, CheckBox 這種開關(guān)性質(zhì)的,邏輯是重復(fù)的,state 定義在組件內(nèi)部更好維護(hù), 復(fù)用性更好。但是 child 的 state 是需要告知我的 parent 組件的, 同樣還是執(zhí)行 parent 傳下來的 change 函數(shù)。
兄弟組件
有時(shí)候可能出現(xiàn)頁面中的某兩部分通信,比如省市的級(jí)聯(lián)選擇,點(diǎn)擊 button 改變顏色等等,組件并不是父子級(jí),沒有嵌套關(guān)系的時(shí)候。這種時(shí)候通常是依賴共有的頂級(jí) Container 處理或者第三方的狀態(tài)管理器。其實(shí)原理都是相通的,兄弟 A 的 value 發(fā)生變化,分發(fā)的時(shí)候把 value 值告訴一個(gè)中間者 C ,C 會(huì)自動(dòng)告知 B,實(shí)現(xiàn) B 的自動(dòng)render 。
利用共有的Container
// container
class Container extends Component {
constructor() {
super();
this.state = {
value: '',
}
}
setValue = value => {
this.setState({
value,
})
}
render() {
return (
<div>
<A setValue={this.setValue}/>
<B value={this.state.value} />
</div>
);
}
}
// 兄弟A
class A extends Component {
handleChange = (e) => {
this.value = e.target.value;
}
handleClick = () => {
const { setValue } = this.props;
setValue(this.value);
}
render() {
return (
<div className="card">
我是Brother A, <input onChange={this.handleChange} />
<div className="button" onClick={this.handleClick}>通知</div>
</div>
)
}
}
// 兄弟B
const B = props => (
<div className="card">
我是Brother B, value是:
{props.value}
</div>
);
export default B;
組件 A 中的表單 value 值,告知了父級(jí) Container 組件(通過 setValue 函數(shù)改變 state),組件 B 依賴于 Container 傳下來的 state,會(huì)做出同步更新。這里的中間者是 Container。
利用Context
上面的方式,如果嵌套少還可以,如果嵌套特別多,比如一級(jí)導(dǎo)航欄下的二級(jí)導(dǎo)航欄下的某個(gè)按鈕,要改變頁面中 content 區(qū)域的 table 里的某個(gè)列的值...他們同屬于一個(gè) page 。這樣傳遞 props 就會(huì)很痛苦,每一層組件都要傳遞一次。
// 頂級(jí)公共組件
class Context extends Component {
constructor() {
super();
this.state = {
value: '',
};
}
setValue = value => {
this.setState({
value,
})
}
getChildContext() { // 必需
return {
value: this.state.value,
setValue: this.setValue,
};
}
render() {
return (
<div>
<AParent />
<BParent />
</div>
);
}
}
// 必需
Context.childContextTypes = {
value: PropTypes.string,
setValue: PropTypes.func,
};
// A 的 parent
class AParent extends Component {
render() {
return (
<div className="card">
<A />
</div>
);
}
}
// A
class A extends Component {
handleChange = (e) => {
this.value = e.target.value;
}
handleClick = () => {
const { setValue } = this.context;
setValue(this.value);
}
render() {
return (
<div>
我是parentA 下的 A, <input onChange={this.handleChange} />
<div className="button" onClick={this.handleClick}>通知</div>
</div>
);
}
}
// 必需
A.contextTypes = {
setValue: PropTypes.func,
};
// B 的 parent
class BParent extends Component {
render() {
return (
<div className="card">
<B />
</div>
);
}
}
// B
class B extends Component {
render() {
return (
<div>
我是parentB 下的 B, value是:
{this.context.value}
</div>
);
}
}
B.contextTypes = {
value: PropTypes.string,
};
組件 A 仍是 消息的發(fā)送者,組件 B 是接收者, 中間者是 Context 公有 Container 組件。context是官方文檔的一個(gè) API ,通過 getChildContext 函數(shù)定義 context 中的值,并且還要求 childContextTypes 是必需的。這樣屬于這個(gè) Container 組件的子組件,通過 this.context 就可以取到定義的值,并且起到跟 state 同樣的效果。中間者其實(shí)還是 Container,只不過利用了上下文這樣的 API ,省去了 props 的傳遞。另外:這個(gè)功能是實(shí)驗(yàn)性的,未來可能會(huì)有所改動(dòng)。
發(fā)布訂閱
這種一個(gè)地方發(fā)送消息,另一個(gè)地方接收做出變化的需求,很容易想到的就是觀察者模式了。具體的實(shí)現(xiàn)會(huì)有很多種,這里我們自己寫了一個(gè) EventEmitter 的類(其實(shí)就是仿照 node 中的 EventEmitter 類),如果不了解觀察者,可以看我的另一篇文章 觀察者模式 。
// 發(fā)布訂閱類
class EventEmitter {
_event = {}
// on 函數(shù)用于綁定
on(eventName, handle) {
let listeners = this._event[eventName];
if(!listeners || !listeners.length) {
this._event[eventName] = [handle];
return;
}
listeners.push(handle);
}
// off 用于移除
off(eventName, handle) {
let listeners = this._event[eventName];
this._event[eventName] = listeners.filter(l => l !== handle);
}
// emit 用于分發(fā)消息
emit(eventName, ...args) {
const listeners = this._event[eventName];
if(listeners && listeners.length) {
for(const l of listeners) {
l(...args);
}
}
}
}
const event = new EventEmitter;
export { event };
// Container
import A from './a';
import B from './b';
const Listener = () => {
return (
<div>
<A />
<B />
</div>
);
};
export default Listener;
// 兄弟組件 A
import { event } from './eventEmitter';
class A extends Component {
handleChange = e => {
this.value = e.target.value;
}
handleClick = () => {
event.emit('dispatch', this.value);
}
render() {
return (
<div className="card">
我是Brother A, <input onChange={this.handleChange} />
<div className="button" onClick={this.handleClick}>通知</div>
</div>
)
}
}
// 兄弟組件 B
import { event } from './eventEmitter';
class B extends Component {
state = {
value: ''
}
componentDidMount() {
event.on('dispatch', this.valueChange);
}
componentWillUnmount() {
event.off('dispatch', this.valueChange);
}
valueChange = value => {
this.setState({
value,
})
}
render() {
return (
<div className="card">
我是Brother B, value是:
{this.state.value}
</div>
);
}
}
仍然是組件 A 用于分發(fā)消息,組件 B 去接收消息。這里的中間者其實(shí)就是 event 對(duì)象。需要接收消息的 B 去訂閱 dispatch 事件,并把回調(diào)函數(shù) valueChange 傳入,另外 B 定義了自己的 state,方便得到 value 值的時(shí)候自動(dòng)渲染。組件 A 其實(shí)就是把內(nèi)部的表單 value 在點(diǎn)擊的時(shí)候分發(fā),發(fā)布事件,從而 B 中的 valueChange 執(zhí)行,改變 state。這種方式比較方便,也更直觀,不需要借助 Container 組件去實(shí)現(xiàn),省去了很多邏輯。
Redux || Mobx
Redux 或者 Mobx 是第三方的狀態(tài)管理器,是這里我們通信的中間者。大型項(xiàng)目最直接的就是上庫... 更方便,更不容易出錯(cuò)。 但其實(shí)小項(xiàng)目就沒什么必要了。東西比較多,這里不再闡述它們的實(shí)現(xiàn)和做了什么。
總結(jié)
react 特殊的自上而下的單向數(shù)據(jù)流,和 state 的特性,造就以這樣的思想實(shí)現(xiàn)組件通信。除去發(fā)布訂閱和 Redux 等,其他的都是 props 自上而下傳遞的理念,子組件需要的總是通過父組件傳遞下來的,關(guān)于 state 的定義,還是看具體的應(yīng)用場景了。
另外本次的代碼都放在https://github.com/sunyongjian/rc-communication-demo, 可以 done 下來加深理解。
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
React路由跳轉(zhuǎn)的實(shí)現(xiàn)示例
在React中,可以使用多種方法進(jìn)行路由跳轉(zhuǎn),本文主要介紹了React路由跳轉(zhuǎn)的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12
React實(shí)現(xiàn)模糊搜索和關(guān)鍵字高亮的示例代碼
這篇文章主要為大家詳細(xì)介紹了React如何實(shí)現(xiàn)模糊搜索和關(guān)鍵字高亮的效果,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-11-11
如何創(chuàng)建自己的第一個(gè)React 頁面
React是用于構(gòu)建用戶界面的JavaScript庫,本文主要介紹了如何創(chuàng)建自己的第一個(gè)React頁面,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
React?component.forceUpdate()強(qiáng)制重新渲染方式
這篇文章主要介紹了React?component.forceUpdate()強(qiáng)制重新渲染方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10
React無限滾動(dòng)插件react-infinite-scroll-component的配置優(yōu)化技巧
react-infinite-scroll-component是React無限滾動(dòng)插件,簡化滾動(dòng)加載邏輯,支持自定義提示和觸發(fā)距離,兼容移動(dòng)端,體積小巧,適用于列表、聊天等場景,需結(jié)合虛擬滾動(dòng)優(yōu)化性能,本文介紹React無限滾動(dòng)插件react-infinite-scroll-component的配置+優(yōu)化,感興趣的朋友一起看看吧2025-09-09
ReactHooks批量更新state及獲取路由參數(shù)示例解析
這篇文章主要介紹了React Hooks如何實(shí)現(xiàn)批量更新state以及獲取路由參數(shù)的示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助2021-10-10
React函數(shù)組件useContext useReducer自定義hooks
這篇文章主要為大家介紹了React函數(shù)組件useContext useReducer自定義hooks示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08

