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

本篇文章內(nèi)容包括:

  • 任意兩個組件之間如何通信
  • 發(fā)布訂閱模式
  • Redux

1. 回顧父子/爺孫組件通信

任何一個函數(shù)都是組件煮岁。
任何一個函數(shù)都可以寫成標(biāo)簽的形式椒楣,內(nèi)容就是 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'))

點擊后灌侣,子組件調(diào)用了 fn

2. 使用eventHub實現(xiàn)通信

需求說明:有一個家族卖陵,家族內(nèi)的人共用一筆總資產(chǎn)恋昼,當(dāng)兒子2 消費時,將剩余金額通知到家族內(nèi)的每個人赶促,即組件通訊達(dá)到金額的「同步」液肌。

示例.png

如果還是使用父子通信,那么只能不斷去傳遞總資產(chǎn)鸥滨,十分麻煩嗦哆。

兒子2 如何跟所有人交互呢谤祖?——使用經(jīng)典設(shè)計模式:發(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粥喜,獲取數(shù)據(jù)用 props,只有 App 知道 money橘券。

單向數(shù)據(jù)流

上面的例子额湘,數(shù)據(jù)就是單向流動的,即只有下旁舰,沒有上锋华。

數(shù)據(jù)流對比.png

可以看到 eventHub 模式的特點:

  • 所有的數(shù)據(jù)都放在頂層組件
  • 所有動作都通過事件來溝通

3. 使用 Redux 來代替 eventHub

提出約定:把所有數(shù)據(jù)放在 store 里,傳給最頂層的組件箭窜。

  • store 存放數(shù)據(jù)
  • reducer 對數(shù)據(jù)的變動操作
  • 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);     //訂閱

關(guān)鍵代碼:

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

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,數(shù)據(jù)還不會實時更新块差。

訂閱

Redux 里一定要訂閱才會更新修改后的數(shù)據(jù)侵续。

render();
store.subscribe(render);      //訂閱

面試題:請簡述 React 任意組件之間如何通信。

參考答案

  1. 使用 eventHub/eventBus 來通信
    一個組件監(jiān)聽某個事件憾儒,另一個組件觸發(fā)相同的事件并傳參,即可實現(xiàn)兩個組件的通信
    缺點是事件容易越來越多乃沙,不好控制代碼的復(fù)雜度
  2. 使用 Redux
    ⅰ. 每次操作觸發(fā)一個 action
    ⅱ. action 會觸發(fā)對應(yīng)的 reducer
    ⅲ. reducer 會用舊的 stateaction 造出一個新的 state
    ⅳ. 使用 store.subscribe 監(jiān)聽 state 的變化起趾,一旦 state 變化就重新 renderrender 會做 DOM diff,確保只更新該更新的 DOM)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末警儒,一起剝皮案震驚了整個濱河市训裆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蜀铲,老刑警劉巖边琉,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異记劝,居然都是意外死亡变姨,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門厌丑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來定欧,“玉大人渔呵,你說我怎么就攤上這事】仇” “怎么了扩氢?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長爷辱。 經(jīng)常有香客問我录豺,道長,這世上最難降的妖魔是什么饭弓? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任双饥,我火速辦了婚禮,結(jié)果婚禮上示启,老公的妹妹穿的比我還像新娘兢哭。我一直安慰自己,他們只是感情好夫嗓,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布迟螺。 她就那樣靜靜地躺著,像睡著了一般舍咖。 火紅的嫁衣襯著肌膚如雪矩父。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天排霉,我揣著相機與錄音窍株,去河邊找鬼。 笑死攻柠,一個胖子當(dāng)著我的面吹牛球订,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瑰钮,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼冒滩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了浪谴?” 一聲冷哼從身側(cè)響起开睡,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎苟耻,沒想到半個月后篇恒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡凶杖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年胁艰,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡蝗茁,死狀恐怖醋虏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情哮翘,我是刑警寧澤颈嚼,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站饭寺,受9級特大地震影響阻课,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜艰匙,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一限煞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧员凝,春花似錦署驻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至糖埋,卻和暖如春宣吱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瞳别。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工征候, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人祟敛。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓疤坝,卻偏偏與公主長得像,于是被迫代替她去往敵國和親馆铁。 傳聞我的和親對象是個殘疾皇子跑揉,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容