---
title: "Reading Redux Source Code：深入理解 Redux store 結構與更新機制"
description: "透過 Redux 原始碼拆解 store 的結構、dispatch action、reducer 更新與 listener 通知流程，理解嚴格單向資料流實際如何運作。"
lang: "zh-Hant-TW"
tags: ["Redux", "React", "JavaScript"]
pubDate: 2017-02-16T00:00:00.000Z
---

「Redux 的 store 到底怎麼是怎麼設計的？」、「Redux 的 reducer 是怎麼更新 state？」，自己雖然已經會使用 Redux，但對於 Redux 本身的資料流還想理解地更徹底，所以就來讀讀原始碼來理解 Redux 怎麼實作。

## Redux 的 lifecycle：嚴格單向資料流（strict unidirectional data flow）

在 Redux 的 tutorial 中，有提到 Redux 的嚴格單向資料流 lifecycle 有這幾個步驟：

> 1. call `store.dispatch(action)`.
> 2. The Redux store calls the reducer function you gave it.
> 3. The root reducer may combine the output of multiple reducers into a single state tree.
> 4. The Redux store saves the complete state tree returned by the root reducer.

以下就從 redux 原始程式碼和較簡單的 `todomvc` 範例（在 Github 的 redux repo 裡有打包了）來一步一步詳細拆解這個過程。如此將更瞭解「store 是什麼」這個基本問題，也就可以更清楚「dispatch action」和「reducer 會去更新 store」這些動作究竟是怎麼發生的。

### 1. call `store.dispatch(action)`

顯然 `dispatch` 是物件 store 的一個方法。要看 `dispatch` 這個方法是什麼，當然就要去 store 物件裡面找。我們用 `redux/examples` 底下的 `todomvc` 案例來看：

```js
// redux/examples/todomvc/src/index.js

import React from "react";
import { render } from "react-dom";
import { createStore } from "redux";
import { Provider } from "react-redux";
import App from "./containers/App";
import reducer from "./reducers";
import "todomvc-app-css/index.css";

const store = createStore(reducer);

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root"),
);
```

看到 `store = createStore(reducer)` ，非常簡單。那再來看呼叫 `createStore()` 回傳的東西是什麼，就知道我們的 `store` 是什麼：

```js
// redux/src/createStore.js

export default function createStore(reducer, preloadedState, enhancer) {
  // ...... something else ......

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable,
  };
}
```

呼叫 `createStore()` 就只是 return 一個有四個 properties 的物件，這就是我們的 store。

### 2. The Redux store calls the reducer function you gave it.

那再來看 `dispatch` 這個 property 是什麼：

```js
// redux/src/createStore.js

export default function createStore(reducer, preloadedState, enhancer) {
  var currentReducer = reducer;
  var currentState = preloadedState;
  var currentListeners = [];
  var nextListeners = currentListeners;
  var isDispatching = false;

  /* ...... something else ...... */

  function dispatch(action) {
    /* ...... some if-else checker ...... */

    try {
      isDispatching = true;
      currentState = currentReducer(currentState, action);
    } finally {
      isDispatching = false;
    }

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

    return action;
  }

  /* ...... something else ...... */
}
```

重點在此：

```js
currentState = currentReducer(currentState, action);
```

這邊呼叫的 `currentReducer()` 就是我們在創建 store 時傳進 `createStore()` 的參數 `reducer`（也是一個函式）。

### 3. The root reducer may combine the output of multiple reducers into a single state tree.

讓我們繼續追蹤在 `store.dispatch()` 裡面呼叫的 `currentReducer()`，也就是傳進 `createStore()` 的參數 `reducer` 是什麼。

```js
// redux/examples/todomvc/src/reducers/index.js

import { combineReducers } from "redux";
import todos from "./todos";

const rootReducer = combineReducers({
  todos,
});

export default rootReducer;
```

上面這邊可以發現，我們傳進 `createStore()` 的參數，是一個變數 `rootReducer`，他的值是呼叫 `combineReducers(ruducers)` 的回傳值。

那繼續沿著這個線索追蹤，看看 `combineReducers()` 的回傳值是什麼：

```js
// redux/src/combineReducers.js

export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers);
  const finalReducers = {};
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i];

    if (NODE_ENV !== "production") {
      if (typeof reducers[key] === "undefined") {
        warning(`No reducer provided for key "${key}"`);
      }
    }

    if (typeof reducers[key] === "function") {
      finalReducers[key] = reducers[key];
    }
  }
  const finalReducerKeys = Object.keys(finalReducers);

  /* ...... some checker ...... */

  return function combination(state = {}, action) {
    /* ...... some checker ...... */

    var hasChanged = false;
    var nextState = {};
    for (var i = 0; i < finalReducerKeys.length; i++) {
      var key = finalReducerKeys[i];
      var reducer = finalReducers[key];
      var previousStateForKey = state[key];
      var nextStateForKey = reducer(previousStateForKey, action);
      if (typeof nextStateForKey === "undefined") {
        var errorMessage = getUndefinedStateErrorMessage(key, action);
        throw new Error(errorMessage);
      }
      nextState[key] = nextStateForKey;
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
    }
    return hasChanged ? nextState : state;
  };
}
```

這邊可以看到，回傳的 `combination(state, action)` 這個函式就是會將 reducers object 裡面的每一個 reducer 跑過一遍，最後回傳一個合併各個「sub-reducer 回傳之 sub-state」的「總合大 state object」。

在這裡也可以看出，每個個別 sub-reducer 回傳的 sub-state 的名稱是怎麼來的。State 裡面每個 sub-state 的 key 就是來自傳進 `combineReducers(reducers)` 的 參數 `reducers` object 的 key。

舉例來說，如果 `reducers` 長這樣：

```js
reducers = {
  aooo, // <- a reducer function
  booo, // <- a reducer function
  cooo, // <- a reducer function
};
```

回傳的總和 state 就會長這樣：

```js
{
	aooo: { ... }, // <- key 是 reducers 內存放 reducer 函式的變數名 aooo（也就是 reducers 內的 key），value 是呼叫 reducer aooo() 回傳的 state
	booo: { ... }, // 同上
	cooo: { ... }  // 同上
}
```

### 4. The Redux store saves the complete state tree returned by the root reducer.

回到 `store.dispatch` 看：

```js
// redux/src/createStore.js

export default function createStore(reducer, preloadedState, enhancer) {
  var currentReducer = reducer;
  var currentState = preloadedState;
  var currentListeners = [];
  var nextListeners = currentListeners;
  var isDispatching = false;

  /* ...... something else ...... */

  function dispatch(action) {
    /* ...... some if-else checker ...... */

    try {
      isDispatching = true;
      currentState = currentReducer(currentState, action);
    } finally {
      isDispatching = false;
    }

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

    return action;
  }

  // ...... something else ......

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable,
  };
}
```

上面的 code 可以看到在 `dispatch(action){ ... }` 這個函式實字裡面，用到上層函式的 `currentState`、`currentReducer`、`currentListeners`、`nextListeners`、`isDispatching` 等變數，這些變數都是在 `dispatch` 所在的上層函式 `createStore` 內宣告的區域變數。

因為在執行 `createStore()` return 的 object 裡面的方法會用到這些變數，所以即使 `createStore()` 執行完，這些區域變數也會被包在 closure 裡面繼續存在。

我們透過呼叫 `dispatch()` 方法，就會呼叫暫存（傳址？）在 `currentReducer`（也在 closure 內）的 `rootReducer` 函式，用它的回傳值更新存在 closure 裡面的 `currentState` 變數，這個變數也就是一般俗稱的 store 裡面的唯一的 state tree object。

而要讀取例如 `currentState` 這個存在於 `createStore` 函式的 closure 裡面的區域變數，就必須要透過被 return 出來的取值方法，例如 `getState()` 才讀得到。

---

## After the store state has been updated...

另外，在上面 `dispatch` 方法的最後我們還有看到這段：

```js
var listeners = (currentListeners = nextListeners); // assign from right to left
for (var i = 0; i < listeners.length; i++) {
  var listener = listeners[i];
  listener();
}
```

這會把存在 closure 的變數 `nextListeners` 裡面的 listener 函式全部跑過一遍。

而要把 listener 存到 `nextListeners` 變數裡面，則是透過 `store.subscribe` 方法進行。（原始碼同樣參見 `redux/src/createStore.js`）

而如果是搭配 React，通常則會用 [React Redux library](https://github.com/reactjs/react-redux) 裡的 [`connect()`](https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options) 函式來進行：

> 技術上來說，一個 container component 就只是個使用 `store.subscribe()` 來讀取一部份 Redux state tree 並提供 props 讓 presentational component 來 render 用的 React component。你可以手寫一個 container component，但 React Redux 囊括了許多有用的優化，所以我們建議從 React Redux library 使用 `connect()` function 來產生 container component，它提供了許多有用的最佳化來避免不必要的重新 render。（其中一個結果就是，你不必擔心關於 React 的效能建議而自己實作 `shouldComponentUpdate`。）
>
> Technically, a container component is just a React component that uses `store.subscribe()` to read a part of the Redux state tree and supply props to a presentational component it renders. You could write a container component by hand, but we suggest instead generating container components with the React Redux library's `connect()` function, which provides many useful optimizations to prevent unnecessary re-renders. (One result of this is that you shouldn't have to worry about the React performance suggestion of implementing `shouldComponentUpdate` yourself.)
>
> http://redux.js.org/docs/basics/UsageWithReact.html#implementing-container-components

關於從 redux store 傳遞資料到 react props 還有一些進階技巧，如：

> 萬一你擔心 mapStateToProps 會太常建立新的 object，可以參考 `reselect`：
>
> - [計算衍生資料](http://chentsulin.github.io/redux/docs/recipes/ComputingDerivedData.html)
> - [Reselect](https://github.com/reactjs/reselect)
