本篇文章內容包括:
- 任意兩個組件之間如何通信
- 發(fā)布訂閱模式
- Redux
1. 回顧父子/爺孫組件通信
任何一個函數都是組件。
任何一個函數都可以寫成標簽的形式,內容就是 return 的東西。
父子組件通信示例代碼:
function Foo(props){
return(
<p>
message 是 {props.message}
<button onClick={props.fn}>change</button>
</p>
)
}
class App extends React.Component {
constructor(){
super()
this.state = {
message: "你好!"
}
}
changeMessage(){
this.setState({
message: "真好!"
})
}
render(){
return(
<div>
<Foo message={this.state.message}
fn={this.changeMessage.bind(this)}/>
</div>
)
}
}
ReactDOM.render(<App />, document.querySelector('#root'))
點擊后,子組件調用了 fn 。
2. 使用eventHub實現通信
需求說明:有一個家族,家族內的人共用一筆總資產,當兒子2 消費時,將剩余金額通知到家族內的每個人,即組件通訊達到金額的「同步」。

示例.png
如果還是使用父子通信,那么只能不斷去傳遞總資產,十分麻煩。
兒子2 如何跟所有人交互呢?——使用經典設計模式:發(fā)布訂閱模式(EventHub模式)
EventHub,其主要的功能是發(fā)布事件(trigger 觸發(fā))和訂閱事件(on 監(jiān)聽)。
var money = {
amount: 10000
};
var eventHub = {
events: {},
trigger(eventName, data) {
var fnList = this.events[eventName];
for(let i = 0; i < fnList.length; i++) {
fnList[i].call(undefined, data);
}
},
on(eventName, fn) {
if(!(eventName in this.events)) {
this.events[eventName] = [];
}
this.events[eventName].push(fn);
}
};
var init = () => {
// 訂閱事件
eventHub.on('consume', (data) => {
money.amount -= data;
// 修改money后再次render
render();
})
};
init();
class App extends React.Component {
constructor() {
super();
this.state = {
money: money
};
}
render() {
return(
<div className="app-wrapper">
<BigDad money={this.state.money}/>
<LittleDad money={this.state.money}/>
</div>
);
}
}
class BigDad extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<div className="bigDad">
<span>大爸:</span>
<span className="amount">{this.props.money.amount}</span>
<button>消費</button>
<Son1 money={this.props.money}/>
<Son2 money={this.props.money}/>
</div>
);
}
}
class LittleDad extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<div className="littleDad">
<span>小爸:</span>
<span className="amount">{this.props.money.amount}</span>
<button>消費</button>
<Son3 money={this.props.money}/>
<Son4 money={this.props.money}/>
</div>
);
}
}
...//省略了Son1的代碼
class Son2 extends React.Component {
constructor(props) {
super(props);
}
// 消費
consume() {
// 發(fā)布事件
eventHub.trigger('consume', 100);
}
render() {
return(
<div className="son2">
<span>兒子2:</span>
<span className="amount">{this.props.money.amount}</span>
<button onClick={this.consume.bind(this)}>消費</button>
</div>
);
}
}
class Son3 extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<div className="son3">
<span>兒子3:</span>
<span className="amount">{this.props.money.amount}</span>
<button>消費</button>
</div>
);
}
}
...//省略了Son4的代碼
render();
function render() {
ReactDOM.render(<App/>, document.querySelector('#root'));
}
所有組件不得修改 money,獲取數據用 props,只有 App 知道 money。
單向數據流
上面的例子,數據就是單向流動的,即只有下,沒有上。

數據流對比.png
可以看到 eventHub 模式的特點:
- 所有的數據都放在頂層組件
- 所有動作都通過事件來溝通
3. 使用 Redux 來代替 eventHub
提出約定:把所有數據放在 store 里,傳給最頂層的組件。
-
store存放數據 -
reducer對數據的變動操作 -
subscribe訂閱
eventHub.on('Pay', function (data) { // subscribe
money.amount -= data // reducer
render() // Use DOM Diff algorithm to modify data
})
-
action想做一件事,就是一個動作,trigger 的動作就是 action
pay() {
// Action
// Action Type: 'Pay'
// Payload: 100
eventHub.trigger('Pay', 100)
}
-
dispatch發(fā)布,發(fā)布action
store.dispatch({ type: 'consume', payload: 100});
引入 Redux 改寫代碼:
import { createStore } from "redux";
let reducers = (state, action) => {
state = state || {
money: {
amount: 100000
}
};
switch (action.type) {
case "pay":
return {
money: {
amount: state.money.amount - action.payload
}
};
default:
return state;
}
};
let store = createStore(reducers);
class App extends React.Component {
constructor(props) {
super();
}
render() {
return (
<div className="app">
<Big store={this.props.store} />
<Small store={this.props.store} />
</div>
);
}
}
function Big(props) {
return (
<div className="papa">
<span>大爸:</span>
<span className="amount">{props.store.money.amount}</span>
<button>消費</button>
<Son2 money={props.store.money} />
</div>
);
}
function Small(props) {
return (
<div className="papa">
<span>小爸:</span>
<span className="amount">{props.store.money.amount}</span>
<button>消費</button>
<Son3 money={props.store.money} />
</div>
);
}
class Son2 extends React.Component {
constructor(props) {
super();
}
x() { //發(fā)布
store.dispatch({
type: "pay",
payload: 100
});
}
render() {
return (
<div className="son">
<span>兒子2:</span>
<span>{this.props.money.amount}</span>
<button onClick={this.x.bind(this)}>消費</button>
</div>
);
}
}
function Son3(props) {
return (
<div className="son">
<span>兒子3:</span>
<span>{props.money.amount}</span>
<button>消費</button>
</div>
);
}
const rootElement = document.getElementById("root");
function render() {
ReactDOM.render(<App store={store.getState()} />, rootElement);
}
render();
store.subscribe(render); //訂閱
關鍵代碼:
store傳入頂層組件
const rootElement = document.getElementById("root");
function render() {
ReactDOM.render(<App store={store.getState()} />, rootElement);
}
發(fā)布消息
在 class Son2 里,
不同于trigger,這里用dispatch,其實原理上來說都是一樣的。
x() {
//發(fā)布
store.dispatch({
type: "pay",
payload: 100
});
}
監(jiān)聽事件并修改數據
let reducers = (state, action) => {
state = state || {
money: {
amount: 100000
}
};
switch (action.type) {
case "pay":
return {
money: {
amount: state.money.amount - action.payload
}
};
default:
return state;
}
};
但因為沒有重新 render,數據還不會實時更新。
訂閱
Redux 里一定要訂閱才會更新修改后的數據。
render();
store.subscribe(render); //訂閱
面試題:請簡述 React 任意組件之間如何通信。
參考答案
- 使用 eventHub/eventBus 來通信
一個組件監(jiān)聽某個事件,另一個組件觸發(fā)相同的事件并傳參,即可實現兩個組件的通信
缺點是事件容易越來越多,不好控制代碼的復雜度- 使用 Redux
ⅰ. 每次操作觸發(fā)一個action
ⅱ.action會觸發(fā)對應的reducer
ⅲ.reducer會用舊的state和action造出一個新的state
ⅳ. 使用store.subscribe監(jiān)聽state的變化,一旦state變化就重新render(render會做 DOM diff,確保只更新該更新的 DOM)