【轉】React 元件間通訊
說 React 元件間通訊之前,我們先來討論一下 React 元件究竟有多少種層級間的關係。假設我們開發的專案是一個純 React 的專案,那我們專案應該有如下類似的關係:
父子:Parent 與 Child_1、Child_2、Child_1_1、Child_1_2、Child_2_1
兄弟:Child_1 與 Child_2、Child_1_1 與 Child_2、etc.
針對這些關係,我們將來好好討論一下這些關係間的通訊方式。
(在 React 中,React 元件之間的關係為從屬關係,與 DOM 元素之間的父子關係有所不同,下面只是為了說明方便,將 React 元件的關係類比成父子關係進行闡述)
父元件向子元件通訊
通訊是單向的,資料必須是由一方傳到另一方。在 React 中,父元件可以向子元件通過傳 props 的方式,向子元件進行通訊。
class Parent extends Component{ state = { msg: 'start' }; componentDidMount() { setTimeout(() => { this.setState({ msg: 'end' }); }, 1000); } render() { return <Child_1 |
如果父元件與子元件之間不止一個層級,如 Parent 與 Child_1_1 這樣的關係,可通過 ...
運算子
(Object 剩餘和展開屬性),將父元件的資訊,以更簡潔的方式傳遞給更深層級的子元件。通過這種方式,不用考慮效能的問題,通過 babel 轉義後的 ...
運算子
效能和原生的一致,且上級元件 props 與 state 的改變,會導致元件本身及其子元件的生命週期改變,
// 通過 ... 運算子 向 Child_1_1 傳遞 Parent 元件的資訊 class Child_1 extends Component{ render() { return <div> <p>{this.props.msg}</p> <Child_1_1 {...this.props}/> </div> } } class Child_1_1 extends Component{ render() { return <p>{this.props.msg}</p> } } |
子元件向父元件通訊
在上一個例子中,父元件可以通過傳遞 props 的方式,自頂而下向子元件進行通訊。而子元件向父元件通訊,同樣也需要父元件向子元件傳遞 props 進行通訊,只是父元件傳遞的,是作用域為父元件自身的函式,子元件呼叫該函式,將子元件想要傳遞的資訊,作為引數,傳遞到父元件的作用域中。
class Parent extends Component{ state = { msg: 'start' }; transferMsg(msg) { this.setState({ msg }); } render() { return <div> <p>child msg: {this.state.msg}</p> <Child_1 transferMsg = {msg => this.transferMsg(msg)} /> </div>; } } class Child_1 extends Component{ componentDidMount() { setTimeout(() => { this.props.transferMsg('end') }, 1000); } render() { return <div> <p>child_1 component</p> </div> } } |
在上面的例子中,我們使用了 箭頭函式
,將父元件的 transferMsg
函式通過 props 傳遞給子元件,得益於箭頭函式,保證子元件在呼叫 transferMsg 函式時,其內部 this
仍指向父元件。
當然,對於層級比較深的子元件與父元件之間的通訊,仍可使用 ... 運算子
,將父元件的呼叫函式傳遞給子元件,具體方法和上面的例子類似。
兄弟元件間通訊
對於沒有直接關聯關係的兩個節點,就如 Child_1 與 Child_2 之間的關係,他們唯一的關聯點,就是擁有相同的父元件。參考之前介紹的兩種關係的通訊方式,如果我們向由 Child_1 向 Child_2 進行通訊,我們可以先通過 Child_1 向 Parent 元件進行通訊,再由 Parent 向 Child_2 元件進行通訊,所以有以下程式碼。
class Parent extends Component{ state = { msg: 'start' }; transferMsg(msg) { this.setState({ msg }); } componentDidUpdate() { console.log('Parent update'); } render() { return ( <div> <Child_1 transferMsg = {msg => this.transferMsg(msg)} /> <Child_2 msg = {this.state.msg} /> </div> ); } } class Child_1 extends Component{ componentDidMount() { setTimeout(() => { this.props.transferMsg('end') }, 1000); } componentDidUpdate() { console.log('Child_1 update'); } render() { return <div> <p>child_1 component</p> </div> } } class Child_2 extends Component{ componentDidUpdate() { console.log('Child_2 update'); } render() { return <div> <p>child_2 component: {this.props.msg}</p> <Child_2_1 /> </div> } } class Child_2_1 extends Component{ componentDidUpdate() { console.log('Child_2_1 update'); } render() { return <div> <p>child_2_1 component</p> </div> } } |
然而,這個方法有一個問題,由於 Parent 的 state 發生變化,會觸發 Parent 及從屬於 Parent 的子元件的生命週期,所以我們在控制檯中可以看到,在各個元件中的 componentDidUpdate 方法均被觸發。
有沒有更好的解決方式來進行兄弟元件間的通訊,甚至是父子元件層級較深的通訊的呢?
觀察者模式
在傳統的前端解耦方面,觀察者模式作為比較常見一種設計模式,大量使用在各種框架類庫的設計當中。即使我們在寫 React,在寫 JSX,我們核心的部分還是 JavaScript。
觀察者模式也叫 釋出者-訂閱者模式
,釋出者釋出事件,訂閱者監聽事件並做出反應,對於上面的程式碼,我們引入一個小模組,使用觀察者模式進行改造。
import eventProxy from '../eventProxy' class Parent extends Component{ render() { return ( <div> <Child_1/> <Child_2/> </div> ); } } // componentDidUpdate 與 render 方法與上例一致 class Child_1 extends Component{ componentDidMount() { setTimeout(() => { // 釋出 msg 事件 |