0
点赞
收藏
分享

微信扫一扫

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

本篇文章内容包括:

  • 任意两个组件之间如何通信
  • 发布订阅模式
  • 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 消费时,将剩余金额通知到家族内的每个人,即组件通讯达到金额的「同步」。

如果还是使用父子通信,那么只能不断去传递总资产,十分麻烦。

儿子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

单向数据流

上面的例子,数据就是单向流动的,即只有下,没有上。

可以看到 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);      //订阅
举报

相关推荐

0 条评论