深刻理解redux

爲何寫這篇文章vue


業餘時間我也算看了很多優秀開源項目的源碼好比react,redux,vuex,vue,babel,ant-design等,可是不多系統地進行總結,學到的知識很是有限,所以我一直想寫一篇完善的源碼解讀方面的文章。java

第二個緣由是最近面試的過程當中,發現不少候選人對redux的理解很淺,甚至有錯誤的理解。真正理解redux的思想的人很是好,更不要說理解它其中的精妙設計了。react

所以就有了這篇文章的誕生。面試

圖片

REDUX是什麼vuex


深刻理解redux以前,首先來看下,redux是什麼,解決了什麼問題。編程

下面是redux官方給出的解釋:redux

Redux is a predictable state container for JavaScript apps.api

上面的概念比較抽象,若是對redux不瞭解的人是很難理解的。數組

一個更容易被人理解的解釋(一樣是redux官方的解釋):promise

redux是flux架構的實現,受Elm啓發

首先科普兩個名字,flux和Elm。

flux

下面是facebook官方對flux的解釋:

Application Architecture for Building User Interfaces

更具體地說:

An application architecture for React utilizing a unidirectional data flow.

flux是隨着react一塊兒推出的數據管理框架,它的核心思想是單項數據流。

一圖勝千言,讓咱們經過圖來了解下flux

圖片flux

經過react構建view,而react又是數據驅動的,那麼解決數據問題就解決了view的問題,經過flux架構管理數據,使得數據可預測。這樣
view也變得可預測。 很是棒~

Elm

Elm是一門編譯代碼到javaScript的語言,它的特色是性能強和無運行時異常。Elm也有虛擬DOM的實現。

Elm的核心理念是使用Model構建應用,也就是說Model是應用的核心。
構建一個應用就是構建Model,構建更新Model的方式,以及如何構建Model到view的映射。

更多關於elm的介紹

瞭解了上面的東西,你會發現其實redux的任務就是管理數據。redux的數據流能夠用下面的圖來標示:

圖片redux

redux中核心就是一個單一的state。state經過閉包的形式存放在redux store中,保證其是隻讀的。若是你想要更改state,只能經過發送action進行,action本質上就是一個普通的對象。

你的應用能夠經過redux暴露的subscribe方法,訂閱state變化。若是你在react應用中使用redux,則表現爲react訂閱store變化,並re-render視圖。

最後一個問題就是如何根據action來更新視圖,這部分是業務相關的。redux經過reducer來更新state,關於reducer的介紹,我會在後面詳細介紹。

它精妙的設計咱們在後面進行解讀。

圖片

最小化實現REDUX


學習一個東西的最好方法就是本身寫一個。好在redux並不複雜,從新實現一個redux並不困難。redux源碼也就區區200行左右。
裏面大量使用高階函數,閉包,函數組合等知識。讓代碼看起來更加簡短,結構更加清晰。

咱們來寫一個"redux"

實現

咱們要實現的redux主要有以下幾個功能:

  • 獲取應用state

  • 發送action

  • 監聽state變化

讓咱們來看下redux store暴漏的api

1const store = {
2  state: {}, // 全局惟一的state,內部變量,經過getState()獲取
3  listeners: [], // listeners,用來諸如視圖更新的操做
4  dispatch: () => {}, // 分發action
5  subscribe: () => {}, // 用來訂閱state變化
6  getState: () => {}, // 獲取state
7}

咱們來實現createStore,它返回store對象,
store的對象結構上面已經寫好了。createStore是用來初始化redux store的,是redux最重要的api。
咱們來實現一下:

createStore

 1const createStore = (reducer, initialState) => {
2  // internal variables
3  const store = {};
4  store.state = initialState;
5  store.listeners = [];
6
7  // api-subscribe
8  store.subscribe = (listener) => {
9    store.listeners.push(listener);
10  };
11  // api-dispatch
12  store.dispatch = (action) => {
13    store.state = reducer(store.state, action);
14    store.listeners.forEach(listener => listener());
15  };
16
17  // api-getState
18  store.getState = () => store.state;
19
20  return store;
21};

經過上面的20行左右的代碼已經實現了redux的最基本功能了,是否是很驚訝?咱們下面來試下。

使用

咱們如今能夠像使用redux同樣使用了咱們的"redux"了。

如下例子摘自官網

你能夠把下面這段腳本加上咱們上面實現的"redux",拷貝到控制檯執行,看下效果。是否和redux官方給的結果一致。

 1// reducer
2function counter(state = 0, action{
3  switch (action.type) {
4  case 'INCREMENT':
5    return state + 1
6  case 'DECREMENT':
7    return state - 1
8  default:
9    return state
10  }
11}
12
13let store = createStore(counter)
14
15store.subscribe(() =>
16  console.log(store.getState())
17)
18
19
20store.dispatch({ type'INCREMENT' })
21// 1
22store.dispatch({ type'INCREMENT' })
23// 2
24store.dispatch({ type'DECREMENT' })
25// 1

能夠看出咱們已經完成了redux的最基本的功能了。若是須要更新view,就根據咱們暴漏的subscribe去更新就行了,這也就解釋了 redux並非專門用於react的,以及爲何要有react-redux這樣的庫存在。

爲了方便各個階段的人員可以看懂,我省略了applyMiddleware的實現,可是不要擔憂,我會在下面redux核心思想章節進行解讀。

REDUX核心思想


redux的核心思想出了剛纔提到的那些以外。
我的認爲還有兩個東西須要特別注意。
一個是reducer, 另外一個是middlewares

reducer 和 reduce

reducer能夠說是redux的精髓所在。咱們先來看下它。reducer被要求是一個純函數

  • 被要求很關鍵,由於reducer並非定義在redux中的一個東西。而是用戶傳進來的一個方法。

  • - 純函數也很關鍵,reducer應該是一個純函數,這樣state纔可預測(這裏應證了我開頭提到的Redux is a predictable state container for JavaScript apps.)。

平常工做咱們也會用到reduce函數,它是一個高階函數。reduce一直是計算機領域中一個很是重要的概念。

reducer和reduce名字很是像,這是巧合嗎?

咱們先來看下reducer的函數簽名:

1fucntion reducer(state, action) {
2    const nextState = {};
3    // xxx
4    return nextState;
5}

再看下reduce的函數簽名

1[].reduce((state, action) => {
2    const nextState = {};
3    // xxx
4    return nextState;
5}, initialState)

能夠看出兩個幾乎徹底同樣。最主要區別在於reduce的須要一個數組,而後累計變化。
reducer則沒有這樣的一個數組。

更確切地說,reducer累計的時間上的變化,reduce是累計空間上的變化。

如何理解reducer是累計時間上的變化?

咱們每次經過調用dispatch(action)的時候,都會調用reducer,而後將reducer的返回值去更新store.state。

每次dispatch的過程,其實就是在空間上push(action)的過程,相似這樣:

1[action1, action2, action3].reduce((state, action) => {
2    const nextState = {};
3    // xxx
4    return nextState;
5}, initialState)

所以說,reducer實際上是時間上的累計,是基於時空的操做。

middlewares

關於middleware的概念咱們很少介紹,
感興趣能夠訪問這裏查看更多信息。

以下能夠實現一個redux的middlewares:

1store.dispatch = function dispatchAndLog(action{
2  console.log('dispatching', action)
3  let result = next(action)
4  console.log('next state', store.getState())
5  return result
6}

上述代碼會在dispatch先後進行打印信息。相似的middleware咱們能夠寫不少。
好比咱們還定義了另外幾個類似的中間件。

咱們須要將多箇中間件按照必定順序執行:

 1// 用reduce實現compose,很巧妙。
2function compose(...funcs{
3  if (funcs.length === 0) {
4    return arg => arg
5  }
6
7  if (funcs.length === 1) {
8    return funcs[0]
9  }
10
11  return funcs.reduce((a, b) => (...args) => a(b(...args)))
12}
13
14// applyMiddleware 的源碼
15function applyMiddleware(...middlewares{
16  return createStore => (...args) => {
17    const store = createStore(...args)
18    let dispatch = () => null;
19    let chain = [];
20
21    const middlewareAPI = {
22      getState: store.getState,
23      dispatch(...args) => dispatch(...args)
24    }
25    chain = middlewares.map(middleware => middleware(middlewareAPI))
26    // 將middlewares組成一個函數
27    // 也就是說就從前到後依次執行middlewares
28    dispatch = compose(...chain)(store.dispatch)
29
30    return {
31      ...store,
32      dispatch
33    }
34  }
35}
36
37// 使用
38let store = createStore(
39  todoApp,
40  // applyMiddleware() tells createStore() how to handle middleware
41  applyMiddleware(logger, dispatchAndLog)
42)

上面就是redux關於middleware的源碼,很是簡潔。可是想要徹底讀懂仍是要花費點心思的。首先redux經過createStore生成了一個原始的store(沒有被enhance),而後最後將原始store的dispatch改寫了,在調用原生的reducer之間,插入中間件邏輯(中間件鏈會順序依次執行). 代碼以下:

1function applyMiddleware(...middlewares{
2  return createStore => 
3    (...args) => { const store = createStore(...args);
4    // let dispatch = xxxxx; return { ...store, dispatch } } 
5

而後咱們將用戶傳入的middlewares順序執行,這裏藉助了compose,compose是函數式編程中很是重要的一個概念,他的做用就是將多個函數組合成一個函數,compose(f, g, h)()最終生成的大概是這樣:

1function(...args{ f(g(h(...args))) } 

所以chain大概長這個樣子:

1chain = [
2  function middleware1(next{
3    // 內部能夠經過閉包訪問到getState和dispath },
4  function middleware2(next{
5    // 內部能夠經過閉包訪問到getState和dispath },
6... ]

有了上面compose的概念以後,咱們會發現每一個middleware的input 都是一個參數next爲的function,第一個中間件訪問到的next其實就是原生store的dispatch。代碼爲證:dispatch = compose(...chain)(store.dispatch)。從第二個中間件開始,next其實就是上一個中間件返回的 action => retureValue 。有沒有發現這個函數簽名就是dispatch的函數簽名。output是一個參數爲action的function, 返回的function簽名爲 action => retureValue 用來做爲下一個middleware的next。這樣middleware就能夠選擇性地調用下一個 middleware(next)。社區有很是多的redux middleware,最爲經典的dan本人寫的redux thunk,核心代碼只有兩行, 第一次看真的震驚了。從這裏也能夠看出redux 的厲害之處。基於redux的優秀設計,社區中出現了不少很是優秀的第三方redux中間價,好比redux-dev-tool, redux-log, redux-promise 等等,有機會我會專門出一個redux thunk的解析。

圖片

總結


本篇文章主要講解了redux是什麼,它主要作了什麼。而後經過不到20行代碼實現了一個最小化的redux。最後深刻講解了redux的核心設計reducer和middlewares。

redux還有一些很是經典的學習資源,這裏推薦redux做者本人的getting started with redux和You Might Not Need Redux。學習它對於你理解redux以及如何使用redux管理應用狀態是很是有幫助的。

相關文章
相關標籤/搜索