http://www.slideshare.net/JonasOhlsson/using-redux#13

從 source code 來看 React-Redux 怎麼讓 Redux 跟 React 共舞

陳冠霖
8 min readApr 18, 2016

--

react-redux 的實現其實比 redux 本身還要複雜很多,redux 本身的 source code 讀起來的感覺是這種設計的方式簡潔又相當精妙,而 react-redux 雖然主要就是一個 <Provider /> component 跟一個 connect function 而已, 但是 connect function 的實作為了避免一些不相干 state 的 update 導致重新render 以致於有點難懂,所以這篇文章不會太講太細的實作,也不會講api如何使用,著重在 redux 如何跟 react 互動的設計方式。至於 redux 的 source code 實作可以參考小弟拙作,從 source code 來看 Redux 更新 state 的運行機制這篇文章。這篇文章是自己的看 source code 的心得,難免有知識不足誤解缺漏的地方,還請各方大神路過不吝指教<(_ _)>

tl;dr

  • 為了 Performance 提昇,connect 有很多 boolean 很難 trace 很難讀,要讀要有耐心XD
  • Provider Component 就是單純的把 store 用 context 傳下去,不過要注意React.Children.only 的關係,Provider 只能有一個 Child Component
  • Connect Component 包住原本的 Component,並把有 setState 的 handleChange function 在 componentDidMount 的時候,透過 redux 裡頭的 subscribe 放到 nextListener 的 array 裡面,而 redux 每次 dispatch 都會跑過一次 nextListener,就會跑過每一個 Connect Component 裡的 handleChange
  • connect 提昇 performance 的方式,主要是在 handleChange 的時候,檢查 mapStateToProps 的回傳值有沒有變化,決定要不要 setState,以及在render 的時候,檢查最後傳給 WrappedComponent 的 mergeProps 有沒有改變,沒有則回傳上次 cache 住的 this.renderElement

<Provider />: Component providing the redux store

Provider 這個 Component 其實作用就是將 Redux 裡頭 createStore 跟整個component tree 做連結,createStore return 出來的四個 function 從最上層透過 context 傳整個 component tree,用 context 取代 props 來傳遞就可以在整個 component tree 中取用 context 而不用繁瑣的一層一層寫 props

比較有趣的部份是這裡的 render function,用了 React.Children.only 的作法,所以實際上 Provider 只能有一個 Child Component,放入多個Component 會報錯

connect function: Higher Order Function to create Higher Order Component connect to redux

本來想說 connect 是一個 Higher Order Component,但實際上更精確一點來說,他是一個 Higher Order Function ,接受四個參數然後回傳一個 Higher Order Component 的 function,所以使用 connect 的方式不是

const newComponent = connect(Component)

而是

const newComponent = connect(mapStatetoProps, ...)(Component)

這裡先淺談一下 Higher Order Function,如果說 function 所代表的是 input跟 output 映射行為的封裝,只要滿足下列兩個條件之一,輸入是一個或多個以上的 function,或是輸出是 function 就可以稱作 Higher Order Function ,傳入是 function,回傳也是function, 其好處就是不去侵入原本的 function 封裝,保持原本 function 的映射行為,然後提用利用原本的 function 功能去製造回傳出新的 function

用數學的說法就有點像是

h(x) = g(f(x))

f(x) 不會因為外面包了g(x)就改變了他映射行為的邏輯

而 Higher Order Component 就是不侵入原本 Component 的結構,有點類似洋蔥的方式利用傳入的 Component 製造並回傳出新的 Component。

connect function 裡頭 return 的 wrapWithConnect 就是一個 Higher Order Component 的實作,把傳入的 Component 外面再包入一層 Connect Component再回傳出去

Connect Component 是如何把 redux 裡的 state 傳給 Wrapped Component 的

Connect Component 的 constructor 裡頭取得 context 裡頭的 redux store,就用 store.getState 取得 redux 的 state 之後放入 Connect Component 的 state 之中,所以Connect Component 的 State 就是 redux 裡面所有的 state,

但是並非所有 redux 的 state 都跟這個 Wrapped Component 有關,因此在Connect Component 中的 render method 裡頭會調用一些 function 去用一開始 connect function 傳入的 mapStateToProps 等等的 function 算出真正要傳給 Wrapped Component 的 props。

Wrapped Component 的 props 會有三個來源,一個是本身自帶的 props,一個是經由建立 connect function 傳入的 mapStateToProps 把 redux 裡頭的 state map 成 props,還有 mapDispatchToProps 帶入 dispatch 合成的 props,最後由這三種 props 會經由 mergeProps 的 method 合成傳給Wrapped Component

Connect Component 怎麼處理 redux 裡頭 state 與 react 裡頭的 Component 連動?

react 要重新 render 主要就是靠 setState 去觸發,setState 在 handleChange 這個 function 裡面,componentDidMount 的時候會把 handleChange 透過 store.subscribe 放入redux 裡頭的一個 nextListeners array 裡面,

function subscribe(listener) { 
...
nextListeners.push(listener)
...
}

而 redux 裡頭每次 dispatch 完都會去執行這個 array 裡頭的 function,就可以讓 redux 的 dispatch 都可以觸發 Component 的 handleChange 了

var listeners = currentListeners = nextListeners;for (var i = 0; i < listeners.length; i++) { 
listeners[i]()
}

所以最後畫了張圖觸發一次 action,redux 跟 react-redux 整個 flow 如下圖

防止 Connect Component 不必要的更新

Connect Component 防止不必要的更新主要在兩個地方,一個在handleChange 裡面,如果你的 mapStateToProps 沒有接收 ownProps 作為參數,直接相依於redux,而且計算結果相同,那麼 handleChange 就會直接return 不做 setState

再來就是在 render function 裡面,一步一步檢查 ownProps, stateProps, dispatchProps 有沒有變,有變動就會重新 mergeProps 然後 createElement,沒有變動就回傳上次 cache 住的 renderElement

--

--

陳冠霖

Yahoo Taiwan Sr. Frontend Engineer. Write something about web and React.js here.