說 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 的方式,向子元件進行通訊。
如果父元件與子元件之間不止一個層級,如 parent 與 child_1_1 這樣的關系,可通過 <code>... 運算符</code>(object 剩餘和展開屬性),将父元件的資訊,以更簡潔的方式傳遞給更深層級的子元件。通過這種方式,不用考慮性能的問題,通過 babel 轉義後的 <code>... 運算符</code> 性能和原生的一緻,且上級元件 props 與 state 的改變,會導緻元件本身及其子元件的生命周期改變,
在上一個例子中,父元件可以通過傳遞 props 的方式,自頂而下向子元件進行通訊。而子元件向父元件通訊,同樣也需要父元件向子元件傳遞 props 進行通訊,隻是父元件傳遞的,是作用域為父元件自身的函數,子元件調用該函數,将子元件想要傳遞的資訊,作為參數,傳遞到父元件的作用域中。
在上面的例子中,我們使用了 <code>箭頭函數</code>,将父元件的 transfermsg 函數通過 props 傳遞給子元件,得益于箭頭函數,保證子元件在調用 transfermsg 函數時,其内部 <code>this</code> 仍指向父元件。
當然,對于層級比較深的子元件與父元件之間的通訊,仍可使用 <code>... 運算符</code>,将父元件的調用函數傳遞給子元件,具體方法和上面的例子類似。
對于沒有直接關聯關系的兩個節點,就如 child_1 與 child_2 之間的關系,他們唯一的關聯點,就是擁有相同的父元件。參考之前介紹的兩種關系的通訊方式,如果我們向由 child_1 向 child_2 進行通訊,我們可以先通過 child_1 向 parent 元件進行通訊,再由 parent 向 child_2 元件進行通訊,是以有以下代碼。
然而,這個方法有一個問題,由于 parent 的 state 發生變化,會觸發 parent 及從屬于 parent 的子元件的生命周期,是以我們在控制台中可以看到,在各個元件中的 componentdidupdate 方法均被觸發。
有沒有更好的解決方式來進行兄弟元件間的通訊,甚至是父子元件層級較深的通訊的呢?
在傳統的前端解耦方面,觀察者模式作為比較常見一種設計模式,大量使用在各種架構類庫的設計當中。即使我們在寫 react,在寫 jsx,我們核心的部分還是 javascript。
觀察者模式也叫 <code>釋出者-訂閱者模式</code>,釋出者釋出事件,訂閱者監聽事件并做出反應,對于上面的代碼,我們引入一個小子產品,使用觀察者模式進行改造。
我們在 child_2 元件的 componentdidmount 中訂閱了 <code>msg</code> 事件,并在 child_1 componentdidmount 中,在 1s 後釋出了 <code>msg</code> 事件,child_2 元件對 <code>msg</code> 事件做出相應,更新了自身的 state,我們可以看到,由于在整個通訊過程中,隻改變了 child_2 的 state,因而隻有 child_2 和 child_2_1 出發了一次更新的生命周期。
而上面代碼中,神奇的 eventproxy.js 究竟是怎樣的一回事呢?
eventproxy 中,總共有 on、one、off、trigger 這 4 個函數:
on、one:on 與 one 函數用于訂閱者監聽相應的事件,并将事件響應時的函數作為參數,on 與 one 的唯一差別就是,使用 one 進行訂閱的函數,隻會觸發一次,而 使用 on 進行訂閱的函數,每次事件發生相應時都會被觸發。
trigger:trigger 用于釋出者釋出事件,将除第一參數(事件名)的其他參數,作為新的參數,觸發使用 one 與 on 進行訂閱的函數。
off:用于解除所有訂閱了某個事件的所有函數。
下面将來好好聊聊 redux 在元件間通訊的方式。
flux 需要四大部分組成:dispatcher、stores、views/controller-views、actions,其中的 views/controller-views 可以了解為我們上面所說的 parent 元件,其作用是從 state 當中擷取到相應的資料,并将其傳遞給他的子元件(descendants)。而另外 3 個部分,則是由 redux 來提供了。
在上面的例子中,我們将一個名為 <code>reducer</code> 的函數作為參數,生成我們所需要的 store,reducer 接受兩個參數,一個是存儲在 store 裡面的 state,另一個是每一次調用 dispatch 所傳進來的 action。reducer 的作用,就是對 dispatch 傳進來的 action 進行處理,并将結果傳回。而裡面的 state 可以通過 store 裡面的 getstate 方法進行獲得,其結果與最後一次通過 reducer 處理後的結果保持一緻。
在 child_1 元件中,我們每隔 1s 通過 store 的 dispatch 方法,向 store 傳入包含有 type 字段的 action,reducer 直接将 action 進行傳回。
而在 child_2 與 child_2_1 元件中,通過 store 的 subscribe 方法,監聽 store 的變化,觸發 dispatch 後,所有通過 subscribe 進行監聽的函數都會作出相應,根據目前通過 store.getstate() 擷取到的結果進行處理,對目前元件的 state 進行設定。是以我們可以在控制台上看到各個元件更新及存儲在 store 中 state 的情況:
在 redux 中,store 的作用,與 mvc 中的 model 類似,可以将我們項目中的資料傳遞給 store,交給 store 進行處理,并可以實時通過 store.getstate() 擷取到存儲在 store 中的資料。我們對上面例子的 reducer 及各個元件的 componentdidmount 做點小修改,看看 store 的這一個特性。
我們對建立 store 時所傳進去的 reducer 進行修改。reducer 中,其參數 state 為目前 store 的值,我們對不同的 action 進行處理,并将處理後的結果存儲在 state 中并進行傳回。此時,通過 store.getstate() 擷取到的,就是我們處理完成後的 state。
redux 内部的實作,其實也是基于觀察者模式的,reducer 的調用結果,存儲在 store 内部的 state 中,并在每一次 reducer 的調用中并作為參數傳入。是以在 child_1 元件第 2s 的 dispatch 後,child_2 與 child_2_1 元件通過 subscribe 監聽的函數,其通過 getstate 獲得的值,都包含有 child_2 與 child_2_1 字段的,這就是為什麼第 2s 後的響應,child_2 也進行了一次生命周期。是以在對 subscribe 響應後的處理,最好還是先校對通過 getstate() 擷取到的 state 與目前元件的 state 是否相同。
加上這樣的校驗,各個元件的生命周期的觸發就符合我們的預期了。
redux 對于元件間的解耦提供了很大的便利,如果你在考慮該不該使用 redux 的時候,社群裡有一句話說,“當你不知道該不該使用 redux 的時候,那就是不需要的”。redux 用起來一時爽,重構或者将項目留給後人的時候,就是個大坑,redux 中的 dispatch 和 subscribe 方法遍布代碼的每一個角落。剛剛的例子不是最好的,flux 設計中的 controller-views 概念就是為了解決這個問題出發的,将所有的 subscribe 都置于 parent 元件(controller-views),由最上層元件控制下層元件的表現,然而,這不就是我們所說的 <code>子元件向父元件通訊</code> 這種方式了。
轉載自:http://taobaofed.org/blog/2016/11/17/react-components-communication/
作者:鬥臣