爲何redux要返回一個新的state引起的血案

Redux的內幕(一)

一個問題引起的血案

博主在面試的過程當中,面試官問 : 「看你簡歷,Vue和React都使用過,你能說一下 Vue和React的區別嘛?」, 而後吧唧吧唧說了一下,因而!血案發生了,當我答道Vuex和Redux的時候,面試官問了一句,爲何Redux老是要返回一個新的 state ?返回舊的 state 爲何不行 ?面試結果不用說,GG了。javascript

重振旗鼓

過了大半個月,本身總結面試經驗的時候,把Redux的源碼看了一遍,ojbk,看的暈頭轉向,而後去github上看了一些大哥們的解讀,再本身總結一哈,寫個專欄,用於本身之後的複習html

基礎瞭解

Redux是什麼?

Redux 是 JavaScript 狀態容器,提供可預測化的狀態管理方案, 官網裏是這麼介紹的 :java

// Redux is a predictable state container for JavaScript apps.
複製代碼

那麼它能作什麼?

// It helps you write applications that behave consistently, run in different environments (client, server, and native) and are easy to test.
  // On top of that, it provides a great developer experience, such as live code editing combined with a time traveling debugger.
複製代碼

三大原則

  • 單一數據源 : 整個應用的 state 都存儲在一顆 state tree 中,而且只存在與惟一一個 store 中react

  • state 是隻讀的 : 惟一改變 state 的方法只能經過觸發 action,而後經過 action 的 type 進而分發 dispatch 。不能直接改變應用的狀態git

  • 狀態修改均由純函數完成 : 爲了描述 action 如何改變 state tree,須要編寫 reducersgithub

修修邊幅

這裏咱們先來了解一下store、middleware、action、reducer等知識面試

store

這裏的 store 是由 Redux 提供的 createStore(reducers, preloadedState, enhancer) 方法生成。從函數簽名看出,要想生成 store,必需要傳入 reducers,同時也能夠傳入第二個可選參數初始化狀態(preloadedState)。第三個參數通常爲中間件 applyMiddleware(thunkMiddleware),看看代碼,比較直觀json

import { createStore, applyMiddleware } from 'redux'
  import thunkMiddleware from 'redux-thunk' // 這裏用到了redux-thunk

  const store = createStore(
    reducers,
    state,
    applyMiddleware(thunkMiddleware) // applyMiddleware首先接收thunkMiddleware做爲參數,二者組合成爲一個新的函數(enhance)
  )
複製代碼

redux 中最核心的 API 就是 —— createStore, 經過 createStore 方法建立的store是一個對象,它自己包含4個方法 :redux

  • getState() : 獲取 store 中當前的狀態。app

  • dispatch(action) : 分發一個 action,並返回這個 action,這是惟一能改變 store 中數據的 方式。

  • subscribe(listener) : 註冊一個監聽者,它在 store 發生變化時被調用。

  • replaceReducer(nextReducer) : 更新當前 store 裏的 reducer,通常只會在開發模式中調用該方法。

middleware

下圖中表達的是Redux 中一個簡單的同步數據流動場景,點擊 button 後,在回調中分發一個 action, reducer 收到 action 後,更新 state 並通知 view 從新渲染。

單向數據流,看着沒什麼問題。 可是,若是須要打印每個 action 信息來調試,就得去改 dispatch 或者 reducer 實現,使其具備 打印日誌的功能。

又好比,點擊 button 後,須要先去服務端請求數據,只有等數據返回後,才能從新渲染 view,此時咱們但願 dispatch 或 reducer 擁有異步請求的功能。再好比,須要異步請求數據返回後,打印一條日誌,再請求數據,再打印日誌,再渲染。

面對多樣的業務場景,單純地修改 dispatch 或 reducer 的代碼顯然不具備普適性,Redux 借鑑了 Node.js Koa 裏 middleware 的思想,Redux 中 reducer 更關心的是數據的轉化邏輯,因此 middleware 就是爲了加強 dispatch 而出現的。

Action

// 引用官網的介紹

  // Actions are payloads of information that send data from your application to your store. 
  // They are the only source of information for the store
  // You send them to the store using store.dispatch().
複製代碼

Action 是把數據從應用傳到 store 的有效載荷。它是 store 數據的惟一來源。簡單來講,Action就是一種消息類型,他告訴Redux是時候該作什麼了,並帶着相應的數據傳到Redux內部。

Action就是一個簡單的對象,其中必需要有一個type屬性,用來標誌動做類型(reducer以此判斷要執行的邏輯),其餘屬性用戶能夠自定義。如:

const START_FETCH_API = 'START_FETCH_API'
複製代碼
{
    type: START_FETCH_API,
    data: {
      id: itemId,
      value: 'I am Value'
    }
  }
複製代碼

Action Creator

看看官網中的介紹 : Action Creator are exactly that—functions that create actions. It's easy to conflate the terms 「action」 and 「action creator」, so do your best to use the proper term。 也就是說 : Redux 中的 Action Creator 只是簡單的返回一個 Action

function fetchStartRequestApi(jsondata) {
    return {
      type: START_FETCH_API,
      data: jsondata
    }
  }
複製代碼

咱們知道,Redux 由 Flux 演變而來,在傳統的 Flux 中, Action Creators 被調用以後常常會觸發一個dispatch。好比:

function fetchStartRequestApiDispatch(jsondata) {
    const action = {
      type: START_FETCH_API,
      data: jsondata
    }
    dispatch(action)
  }
複製代碼

可是,在Redux中,咱們只須要把 Action Creators 返回的結果傳給 dispatch() ,就完成了發起一個dispatch 的過程,甚至於 建立一個 被綁定的 Action Creators 來自動 dispatch

// example 1
  dispatch(fetchStartRequestApi(jsondata))

  // example 2
  const boundFetchStartRequestApiDispatch = jsondata => dispatch(fetchStartRequestApi(jsondata))
複製代碼

這裏有人就要昏厥了,dispatch() 是個啥?其實前面就講過了,經過 createStore() 建立的 store 對象,他有一個方法 : dispatch(action),store 裏能直接經過 store.dispatch() 調用 dispatch() 方法,可是多數狀況下,咱們都會使用 react-redux 提供的 connect() 幫助器來調用。bindActionCreators() 能夠自動把多個 action 建立函數綁定到 dispatch() 方法上。

Reducers

// 引用官網的介紹

  // Reducers specify how the application's state changes in response to actions sent to the store. 
  // Remember that actions only describe what happened, but don't describe how the application's state changes
複製代碼

上邊也說過了,Reducers必須是一個純函數,它根據action處理state的更新,若是沒有更新或遇到未知action,則返回舊state;不然返回一個新state對象。__注意:不能修改舊state,必須先拷貝一份state,再進行修改,也可使用Object.assign函數生成新的state。__具體爲何,咱們讀源碼的時候就知道啦~

永遠不要在 reducer 裏作這些操做:

  • 修改傳入參數;

  • 執行有反作用的操做,如 API 請求和路由跳轉;

  • 調用非純函數,如 Date.now() 或 Math.random();

下邊上個例子代碼,幫助消化,發送請求,獲取音樂列表

// action.js

  /* * action 類型 */  
  export const START_FETCH_API = 'START_FETCH_API'
  export const STOP_FETCH_API = 'STOP_FETCH_API'
  export const RECEIVE_DATA_LIST = 'RECEIVE_DATA_LIST'
  export const SET_OTHER_FILTERS = 'SET_OTHER_FILTERS'

  /* * 其它的常量 */
  export const otherFilters = {
    SHOW_ALL: 'SHOW_ALL',
    SHOW_ACTIVE: 'SHOW_ACTIVE'
  }

  /* * action 建立函數 */
  export function startFetchApi() {
    return {
      type: START_FETCH_API
    }
  }

  export function stopFetchApi() {
    return {
      type: STOP_FETCH_API
    }
  }

  export function receiveApi(jsondata) {
    return {
      type: RECEIVE_DATA_LIST,
      data: jsondata
    }
  }

  export function setOtherFilters (filter) {
    return {
      type: SET_OTHER_FILTERS,
      data: filter
    }
  }

  // 異步
  export const fetchMusicListApi = (music_id) => dispatch => {
    dispatch(startFetchApi())
    fetch({
      url: url,
      method: 'POST',
      data: {
        music_id: music_id
      }
    }).then((res) => {
      dispatch(stopFetchApi())
      dispatch(receiveApi())
    }).catch((err) => {
      console.log(err)
    })
  }
複製代碼
// reducers.js
  
  // 引入 action.js
  import { otherFilters } from './action'

  // 初始 state
  const initialState = {
    otherFilters: otherFilters.SHOW_ALL,
    list: [],
    isFetching: false
  }

  function reducers(state, action) {
    switch(action.type) {
      case SET_OTHER_FILTERS: 
        return Object.assign({}, state, {
          otherFilters: action.payload.data
        })
      case START_FETCH_API:
        return Object.assign({}, state, {
          isFetching: true
        })
      case STOP_FETCH_API:
        return Object.assign({}, state, {
          isFetching: false
        })
      case RECEIVE_DATA_LIST:
        return Object.assign({}, state, {
          list: [ ...action.payload.data ]
        })
      default: 
        return state
    }
  }
複製代碼

注意

  1. 不要修改 state。 使用 Object.assign() 新建了一個副本。不能這樣使用 Object.assign(state, { otherFilters: action.payload.data }),由於它會改變第一個參數的值。你必須把第一個參數設置爲空對象

  2. 在 default 狀況下返回舊的 state。遇到未知的 action 時,必定要返回舊的 state。

心中有疑惑

  • bindActionCreators() 是如何自動幫我把action綁定到dispatch上的?

  • 什麼是純函數?

  • 爲何reducer必須是純函數?

  • 爲何只能經過action來修改state,直接修改有什麼問題?

  • bindActionCreators() 是如何自動幫我把action綁定到dispatch上的?

  • 爲何 reducers 在 default 的狀況下,必定要返回舊的state?

  • ...

未待完續

還沒寫完,還有下文,只是想再瞭解深刻一點,而後繼續寫總結,目的是給本身,包括新手們看的,望各位大佬,手下留情,各位大佬沒有興趣能夠跳過~,直接去github上看源碼哈,傳送門在這裏 : Redux


2019.01.03 更新,下文在這 : 《爲何 redux 要返回一個新的 state 引起的血案(二)》

相關連接

相關文章
相關標籤/搜索