React作為前端的新一代主流框架壳嚎,因其組件化的思想席爽,徹底革新了前端停留在DOM操作的古老開發(fā)方式。使用React的組件蹦玫,不再需要模板赎婚,也不用再過分擔(dān)心渲染和更新帶來的性能問題。開發(fā)一個(gè)個(gè)組件钳垮,就像一個(gè)個(gè)模塊一般惑淳,在需要的地方放在那里就好。但是這么多的組件之間饺窿,如何進(jìn)行信息交流和信息傳遞歧焦,就引發(fā)了一個(gè)新的問題——組件通信。
我是目錄
- 事件通信
- 觀察者模式
- 關(guān)于Redux與Flux
首先肚医,我們先假設(shè)我們的組件結(jié)構(gòu)如下:
Parent
____|____
| |
ChildA ChildB
1. 事件通信
1.1 父?jìng)髯?/h3>
在 React
中绢馍,父組件可以向子組件通過傳 props
的方式,向子組件進(jìn)行通訊肠套。
class Parent extends Component {
state = { msg: 'start' };
componentDidMount() {
setTimeout(() => { this.setState({ msg: 'end' }); }, 1000);
}
render() {
// 將傳遞值當(dāng)做組件的props屬性傳遞給子組件
// return <ChildA msg={this.state.msg} />;
// 使用...運(yùn)算符將父組件信息以更簡(jiǎn)潔的方式傳遞給子組件
return <ChildA {...this.state} />;
}
}
class ChildA extends Component {
render() {
// 獲取父組件傳遞過來的數(shù)據(jù)中的msg
return <p>{this.props.msg}</p>;
}
}
export default Parent;
1.2 子傳父
子組件向父組件通訊舰涌,同樣也需要父組件向子組件傳遞 props
進(jìn)行通訊,只是父組件傳遞的你稚,是作用域?yàn)楦附M件自身的函數(shù)瓷耙,子組件調(diào)用該函數(shù),將子組件想要傳遞的信息刁赖,作為參數(shù)搁痛,傳遞到父組件的作用域中。
class Parent extends Component {
state = { msg: 'start' };
transferMsg(childMsg) {
this.setState({ msg: childMsg });
}
render() {
return (
<div>
<p>{this.state.msg}</p>
// 將事件傳遞給子組件宇弛,子組件通過事件傳遞參數(shù)與父組件通信
<ChildA transferMsg={childMsg => this.transferMsg(childMsg)} />
</div>
);
}
};
class ChildA extends Component {
componentDidMount() {
setTimeout(() => { this.props.transferMsg('end'); }, 1000);
}
render() {
return <div />;
}
}
1.3 兄弟組件通信
對(duì)于沒有直接關(guān)聯(lián)關(guān)系的兩個(gè)節(jié)點(diǎn)鸡典,就如 ChildA
與 ChildB
之間的關(guān)系,他們唯一的關(guān)聯(lián)點(diǎn)枪芒,就是擁有相同的父組件彻况。參考之前介紹的兩種關(guān)系的通訊方式谁尸,如果我們向由 ChildA
向 ChildB
進(jìn)行通訊,我們可以先通過 ChildA
向 Parent
組件進(jìn)行通訊纽甘,再由 Parent
向 ChildB
組件進(jìn)行通訊良蛮。
class Parent extends Component {
state = { msg: 'parent' };
transferMsg(childAMsg) {
this.setState({ msg: childAMsg });
}
componentDidUpdate() {
console.log('Parent is update'); // 測(cè)試更新State后哪些組件也被更新了生命周期
}
render() {
return (
<div>
<ChildA transferMsg={childAMsg => this.transferMsg(childAMsg)} />
<ChildB {...this.state} />
</div>
);
}
}
class ChildA extends Component {
componentDidMount() {
setTimeout(() => {
this.props.transferMsg('ChildA');
}, 1000);
}
componentDidUpdate() {
console.log('ChildA is update'); // 測(cè)試更新State后哪些組件也被更新了生命周期
}
render() {
return <div />;
}
}
class ChildB extends Component {
componentDidUpdate() {
console.log('ChildB update'); // 測(cè)試更新State后哪些組件也被更新了生命周期
}
render() {
return (
<div>
<p>I am ChildB, this is ChildA to parent and then to ChildB: {this.props.msg}</p>
<ChildBchild />
</div>
);
}
}
class ChildBchild extends Component {
componentDidUpdate() {
console.log('ChildBchild is update'); // 測(cè)試更新State后哪些組件也被更新了生命周期
}
render() {
return <div />;
}
}
當(dāng)我們?cè)跒g覽器運(yùn)行時(shí),可以從控制臺(tái)發(fā)現(xiàn)悍赢,各個(gè)組件的 componentDidUpdate
方法均被觸發(fā)背镇。所以,有沒有更好的解決方式呢泽裳?
2. 觀察者模式
觀察者模式也叫發(fā)布-訂閱者模式,發(fā)布者發(fā)布事件破婆,訂閱者監(jiān)聽事件并做出反應(yīng)涮总。我們通過這種模式,在全局定義一個(gè)事件代理管理器祷舀,每一個(gè)組件只需要引入這個(gè)事件代理者即可瀑梗。
事件代理文件,eventBus.js
:
const eventBus = {
onObj: {},
// 事件監(jiān)聽
on(key, fn) {
this.onObj[key] === undefined && (this.onObj[key] = []);
this.onObj[key].push(fn);
},
// 事件關(guān)閉
off(key) {
this.onObj[key] = [];
this.oneObj[key] = [];
},
// 事件觸發(fā)
trigger() {
/*
備注:
除了事件參數(shù)裳扯,其他參數(shù)允許傳入數(shù)組抛丽,但對(duì)于傳入的map結(jié)構(gòu)、函數(shù)饰豺、以及多個(gè)其他參數(shù)都沒有做處理亿鲜,
這點(diǎn)可以根據(jù)個(gè)人需要進(jìn)行拓展
*/
// 無參返回false
if (arguments.length === 0) {
return false;
}
// key是事件,args是參數(shù) - 通信信息
const argumentsArr = [...arguments];
let key = argumentsArr[0];
let args = argumentsArr.slice(1);
if (this.onObj[key] !== undefined && this.onObj[key].length > 0) {
for (let i in this.onObj[key]) {
this.onObj[key][i].apply(null, args);
}
}
}
};
export default eventBus;
事件代理文件冤吨,eventBus.js
:
class Parent extends Component {
render() {
return (
<div>
<ChildA />
<ChildB />
</div>
);
}
}
class ChildA extends Component {
componentDidMount() {
let hello = 'ChildA - 結(jié)束';
setTimeout(() => {
eventBus.trigger('change', hello);
}, 1000);
}
render() {
return <div></div>;
}
}
class ChildB extends Component {
state = {
msg: 'ChildB - 開始'
};
componentDidMount() {
eventBus.on('change', msg => {
this.setState({
msg
});
});
}
render() {
return (
<div>
<p>ChildA to ChildB component: {this.state.msg}</p>
</div>
);
}
}
3. 關(guān)于Redux與Flux
關(guān)于 Redux
與 Flux
就是用來管理狀態(tài)和解決組件通信問題的蒿柳。但雖然 Redux
對(duì)于組件間的解耦提供了很大的便利,如果你在考慮該不該使用 Redux
的時(shí)候漩蟆,社區(qū)里有一句話說垒探,“當(dāng)你不知道該不該使用 Redux
的時(shí)候,那就是不需要的”怠李。Redux
用起來一時(shí)爽圾叼,重構(gòu)或者將項(xiàng)目留給后人的時(shí)候,就是個(gè)大坑捺癞,Redux
中的 dispatch
和 subscribe
方法遍布代碼的每一個(gè)角落夷蚊。雖然 Flux
設(shè)計(jì)中的 Controller-Views
概念就是為了解決這個(gè)問題出發(fā)的,將所有的 subscribe
都置于 Parent
組件(Controller-Views
)翘簇,由最上層組件控制下層組件的表現(xiàn)撬码,然而,這不就是我們所說的子組件向父組件通訊這種方式了版保。
參考來源:淘寶-React組件通信原理