美文网首页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:组件通信 - 任意组件通信

    本篇文章内容包括: 任意两个组件之间如何通信 发布订阅模式 Redux 1. 回顾父子/爷孙组件通信 任何一个函数...

  • 干货博客集

    基础 webpack4.x 入门一篇足矣 react组件通信方式汇总 vue组件之间的通信 原生小程序组件通信 w...

  • react 组件通信

    概述 react中的组件通信问题,根据层级关系总共有四种类型的组件通信:父子组件、爷孙组件、兄弟组件和任意组件。前...

  • React组件间通信

    不借助redux等状态管理工具的React组件间的通信解决方法 组件通信分类 React组件间通信分为2大类,3种...

  • 总结

    React组件之间的通信方式 1.父组件向子组件通信 React数据流动是单向的,通过props传递 2.子组件向...

  • (1)React的开发

    1、React项目架构搭建2、JSX语法3、React组件化开发4、React组件间通信5、React中的事件6、...

  • React父子组件间通信的实现方式

    React学习笔记之父子组件间通信的实现:今天弄清楚了父子组件间通信是怎么实现的。父组件向子组件通信是通过向子组件...

  • 【Vue】组件通信(任意通信)

    本节所需的基础知识: 【Vue】组件通信(父传子props) 【Vue】组件通信(子传父$emit) 任意组件相互...

  • React的组件通信和特点

    一、React应用的架构图 二、组件通信的实现 首先,组件通信只能实现上下两层组件的直接通信,如果不是上下两层组件...

  • React笔记 -- 组件传值

    通信问题 组件会发生三种通信。 向子组件发消息 向父组件发消息 向其他组件发消息 React 只提供了一种通信手段...

网友评论

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

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