React

從 Source Code 中來搞懂在 React 上的 Redux 實踐方式

對於知識的了解,主要分為兩種層次,第一種層次是知道了某種事物或知識相關的名稱,再更進一步的是了解這個事物或知識的本質,很可惜的是大多數人都只停留在第一種層次。 就像對於 redux,我隱約知道大致上知道他在做什麼,但是卻沒有辦法再更深入的說明,就算講解也只是透過含糊的專有名詞來講解,專業名詞是在專業人士在彼此溝通中方便快速的理解彼此講的複雜概念,一個名詞包含了許多複雜概念,反過來說,如果在一開始就烙這些名詞,很大的可能是根本也不知道裡面複雜的細節。當然,這也是一種取捨,可以用就好了,研究這麼多,你哪來的時間去做別的事情,但最近漸漸意識到真的欠了太多知識債。

對於知識的了解,主要分為兩種層次,第一種層次是知道了某種事物或知識相關的名稱,再更進一步的是了解這個事物或知識的本質,很可惜的是大多數人都只停留在第一種層次。

就像對於 redux,我隱約知道大致上知道他在做什麼,但是卻沒有辦法再更深入的說明,就算講解也只是透過含糊的專有名詞來講解,專業名詞是在專業人士在彼此溝通中方便快速的理解彼此講的複雜概念,一個名詞包含了許多複雜概念,反過來說,如果在一開始就烙這些名詞,很大的可能是根本也不知道裡面複雜的細節。當然,這也是一種取捨,可以用就好了,研究這麼多,你哪來的時間去做別的事情,但最近漸漸意識到真的欠了太多知識債。

逃避雖可恥但是有用

這句話在平常上班時段想要快點有進度時是很有用,但是當開發進展到稍微複雜的應用情境時,難免有點搞不懂在做些什麼了。所以才興起想要認真理解 redux 的 source code,看看到底是如何實踐這套資料流的想法。

redux 介紹

為什麼 redux,要稱作 predictable 呢?在實作上,只要將整個觀念區分為三塊,第一是定義 reducer,第二是定義 action。在 redux 中主要有三大概念,分別是 action、reducer 以及 store。

bindActionCreators 透過這個來 dispatch
reducer,只專注在 dispatch 正確的 action。至於 action 實際上要做些什麼,則完全交給 action 本身去實現,不用管。所以 reducer 就像是某種介面一樣,只需要將指令分發出去,就會有正確對應的行為會產生。

createStore

在 redux 中,一個 app 的所有 state 都存在一個地方集中管理,就是 redux 中的 store。createStore function 就是負責建立這唯一一個 store。createStore return 的 functions 有以下這些。

return {
   dispatch,
   subscribe,
   getState,
   replaceReducer,
   [$$observable]: observable
}
  • dispatch : 所有 state 的變更都需要透過 dispatch 來去呼叫 reducer 去將新的 state 狀態賦值 currentState。以下是 dispatch function 中的部分程式碼,可以看到 currentState 是藉由執行 currentReducer 後來得到新的 state 狀態。
try {
  isDispatching = true
  currentState = currentReducer(currentState, action)
} finally {
  isDispatching = false
}
  • getState : 很單純的將 currentState 給 return 出來。讓外部獲取 state。
function getState() {
  return currentState
}
  • subscribe : 主要是當 state 改變時,會觸發有 subscribe 的 function 執行。subscribe function 中,在一開始針對要加入 subscribe 的 function 做檢查,看型別是否為 function,以及是否加入過。以上都沒問題的話就會將此 function push 到 nextListeners array 中。
function subscribe(listener) {
  if (typeof listener !== 'function') {
    throw new Error('Expected listener to be a function.')
  }

  let isSubscribed = true

  ensureCanMutateNextListeners()
  nextListeners.push(listener)

  return function unsubscribe() {
    if (!isSubscribed) {
      return
    }

    isSubscribed = false

    ensureCanMutateNextListeners()
    const index = nextListeners.indexOf(listener)
    nextListeners.splice(index, 1)
  }
}

combineReducers

上述提到會透過 reducer 來處理 state 的變化,但是今天開發的 component 變多了,這些 component 都有各自的 state,如果還透過一個龐大的 reducer 來處理這些 state 的變化,那個這個 reducer 的可讀性將會變得很差,所以最好的做法是將這些變更不同 state 的 reducer 拆開來,最後再透過 combineReducers 將多個 reducers 組合成一個 root reducer。

bindActionCreators

applyMiddleware

react-redux 介紹

react-redux 主要是由一個繼承 react component 的 Provider component,以及 connect function。在一開始直接就直接使用 react-redux 時,真的是各種錯亂,搞不清楚 state 的變化所觸發的 render。所以還是先去稍微了解 redux 後,再來看 react-redux。

Provider

Provider 的作用在於將 redux 中透過 createStore 建立出來的 store 透過 context 傳遞給 child component,簡單說一下 context 的概念是當有比較深的 child component 時,如果只能透過 props 一層一層的將組態傳下去也太麻煩,若是透過 context,就能在任何 child component 中取用。getChildContext()定義了要傳遞下去的組態。另外再透過 contextTypes 定義數據類型。

class Provider extends Component {
  getChildContext() {
    return { [storeKey]: this[storeKey], [subscriptionKey]: null }
  }

  constructor(props, context) {
    super(props, context)
    this[storeKey] = props.store;
  }

  render() {
    return Children.only(this.props.children)
  }
}

Provider.childContextTypes = {
  [storeKey]: storeShape.isRequired,
  [subscriptionKey]: subscriptionShape,
}

connect

接著就是最有趣的部分了,connect 主要接收四個參數 mapStateToProps, mapDispatchToProps, mergeProps, options,最後回傳一個新的 component。

function connect(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps,
    {
      pure = true,
      areStatesEqual = strictEqual,
      areOwnPropsEqual = shallowEqual,
      areStatePropsEqual = shallowEqual,
      areMergedPropsEqual = shallowEqual,
      ...extraOptions
    } = {}
  • mapStateToProps
    將 state 轉成 props 給 child component,另外這邊的 state 包含了 app 中所有的 state,可以從 state 中取得所需要使用的 state。例如這邊是只需要 state 中的 items,在丟入 connect 前只需要這樣定義。
function mapStateToProps(state) {
  return {
    item_name: state.items
  };
}
  • mapDispatchToProps

這跟 mapStateToProps 的概念一樣,主要是將 dispatch 賦值到 props 中。

  • mergeProps

所以 connect 核心的功能是將原有的 component 加入了上面的參數包裝成新的 component,並且會透過比較 props 是否有更新來決定是否重新 render。

get the full picture


References

  1. You Might Not Need Redux – Dan Abramov – Medium
  2. Presentational and Container Components – Dan Abramov – Medium
0 Comments 0 Comments
0 Comments 0 Comments