React入门 5:组件通信 - 任意组件通信
2019-09-29 本文已影响0人
JaniceZD
本篇文章内容包括:
- 任意两个组件之间如何通信
- 发布订阅模式
- 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 如何跟所有人交互呢?——使用经典设计模式:发布订阅模式(EventHub模式)
EventHub,其主要的功能是发布事件(trigger 触发)和订阅事件(on 监听)。
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() {
// 发布事件
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
发布,发布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() { //发布
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);
}
发布消息
在 class Son2
里,
不同于trigger
,这里用dispatch
,其实原理上来说都是一样的。
x() {
//发布
store.dispatch({
type: "pay",
payload: 100
});
}
监听事件并修改数据
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 来通信
一个组件监听某个事件,另一个组件触发相同的事件并传参,即可实现两个组件的通信
缺点是事件容易越来越多,不好控制代码的复杂度- 使用 Redux
ⅰ. 每次操作触发一个action
ⅱ.action
会触发对应的reducer
ⅲ.reducer
会用旧的state
和action
造出一个新的state
ⅳ. 使用store.subscribe
监听state
的变化,一旦state
变化就重新render
(render
会做 DOM diff,确保只更新该更新的 DOM)