React入門 5:組件通信 - 任意組件通信

本篇文章內容包括:

  • 任意兩個組件之間如何通信
  • 發(fā)布訂閱模式
  • 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 如何跟所有人交互呢?——使用經典設計模式:發(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,獲取數據用 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發(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);     //訂閱

關鍵代碼:

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)聽事件并修改數據

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 來通信
    一個組件監(jiān)聽某個事件,另一個組件觸發(fā)相同的事件并傳參,即可實現兩個組件的通信
    缺點是事件容易越來越多,不好控制代碼的復雜度
  2. 使用 Redux
    ⅰ. 每次操作觸發(fā)一個 action
    ⅱ. action 會觸發(fā)對應的 reducer
    ⅲ. reducer 會用舊的 stateaction 造出一個新的 state
    ⅳ. 使用 store.subscribe 監(jiān)聽 state 的變化,一旦 state 變化就重新 renderrender 會做 DOM diff,確保只更新該更新的 DOM)
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容