本篇文章內(nèi)容包括:
- 任意兩個組件之間如何通信
- 發(fā)布訂閱模式
- Redux
1. 回顧父子/爺孫組件通信
任何一個函數(shù)都是組件煮岁。
任何一個函數(shù)都可以寫成標(biāo)簽的形式椒楣,內(nèi)容就是 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'))
點擊后灌侣,子組件調(diào)用了 fn
。
2. 使用eventHub實現(xiàn)通信
需求說明:有一個家族卖陵,家族內(nèi)的人共用一筆總資產(chǎn)恋昼,當(dāng)兒子2 消費時,將剩余金額通知到家族內(nèi)的每個人赶促,即組件通訊達(dá)到金額的「同步」液肌。
如果還是使用父子通信,那么只能不斷去傳遞總資產(chǎn)鸥滨,十分麻煩嗦哆。
兒子2 如何跟所有人交互呢谤祖?——使用經(jīng)典設(shè)計模式:發(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
粥喜,獲取數(shù)據(jù)用 props
,只有 App 知道 money
橘券。
單向數(shù)據(jù)流
上面的例子额湘,數(shù)據(jù)就是單向流動的,即只有下旁舰,沒有上锋华。
可以看到 eventHub 模式的特點:
- 所有的數(shù)據(jù)都放在頂層組件
- 所有動作都通過事件來溝通
3. 使用 Redux 來代替 eventHub
提出約定:把所有數(shù)據(jù)放在 store
里,傳給最頂層的組件箭窜。
-
store
存放數(shù)據(jù) -
reducer
對數(shù)據(jù)的變動操作 -
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); //訂閱
關(guān)鍵代碼:
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)聽事件并修改數(shù)據(jù)
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,數(shù)據(jù)還不會實時更新块差。
訂閱
Redux 里一定要訂閱才會更新修改后的數(shù)據(jù)侵续。
render();
store.subscribe(render); //訂閱
面試題:請簡述 React 任意組件之間如何通信。
參考答案
- 使用 eventHub/eventBus 來通信
一個組件監(jiān)聽某個事件憾儒,另一個組件觸發(fā)相同的事件并傳參,即可實現(xiàn)兩個組件的通信
缺點是事件容易越來越多乃沙,不好控制代碼的復(fù)雜度- 使用 Redux
ⅰ. 每次操作觸發(fā)一個action
ⅱ.action
會觸發(fā)對應(yīng)的reducer
ⅲ.reducer
會用舊的state
和action
造出一個新的state
ⅳ. 使用store.subscribe
監(jiān)聽state
的變化起趾,一旦state
變化就重新render
(render
會做 DOM diff,確保只更新該更新的 DOM)