美文网首页React Native开发让前端飞Web前端之路
React入门 5:组件通信 - 任意组件通信

React入门 5:组件通信 - 任意组件通信

作者: JaniceZD | 来源:发表于2019-09-29 11:59 被阅读0次

    本篇文章内容包括:

    • 任意两个组件之间如何通信
    • 发布订阅模式
    • 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 任意组件之间如何通信。

    参考答案

    1. 使用 eventHub/eventBus 来通信
      一个组件监听某个事件,另一个组件触发相同的事件并传参,即可实现两个组件的通信
      缺点是事件容易越来越多,不好控制代码的复杂度
    2. 使用 Redux
      ⅰ. 每次操作触发一个 action
      ⅱ. action 会触发对应的 reducer
      ⅲ. reducer 会用旧的 stateaction 造出一个新的 state
      ⅳ. 使用 store.subscribe 监听 state 的变化,一旦 state 变化就重新 renderrender 会做 DOM diff,确保只更新该更新的 DOM)

    相关文章

      网友评论

        本文标题:React入门 5:组件通信 - 任意组件通信

        本文链接:https://www.haomeiwen.com/subject/ptctpctx.html