為什麼 redux 要返回一個新的 state 引發的血案(二)

彭道寬發表於2019-01-03

一堆廢話

繼上一篇文章《為什麼 redux 要返回一個新的 state 引發的血案》之後,過了半個月我彭三漢又回來了,直接看原始碼,github 戳這裡,我們可以看到這樣的檔案架構

·
├── utils
│   ├── actionTypes
│   ├── isPlainObject
│   ├── warning
│   └─
│
├── applyMiddleware
│
├── bindActionCreatorts
│
├── combineReducers
│
├── compose
│
├── createStore
│
├── index.js
│
└─
複製程式碼

看起來檔案比較少,所以,開始跟我阿寬,不對,跟著各路大哥們的總結,一起看原始碼吧。

index.js

從小媽媽就告訴我,看原始碼要從 index.js 入手,就我看來,index都作為入口檔案,所以我們去 index.js 中看一下程式碼。簡單明瞭,我就不多說~

import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'

/*
 * 這是一個虛擬函式,用於檢查函式名稱是否已被縮小更改.
 * 如果這個函式被修改且 NODE_ENV !== 'production',警告使用者
 */
function isCrushed() {}

if (
  process.env.NODE_ENV !== 'production' &&
  typeof isCrushed.name === 'string' &&
  isCrushed.name !== 'isCrushed'
) {
  warning('巴拉巴拉,要看提示就去看原始碼哈')
}

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes
}
複製程式碼

createStore.js

我們回顧下之前說的 :redux 中最核心的 API 就是 —— createStore, 如何使用呢 ?

const store = createStore(reducers, preloadedState, enhance)
複製程式碼

三個引數,reducers、preloadedState、enhance,原始碼中也有對這三個引數給出瞭解釋

/**
 * 建立一個包含狀態樹的Redux儲存
 * 更改store中資料的唯一方法是在其上呼叫 `dispatch()`
 *
 * 你的app中應該只有一個store,指定狀態樹的不同部分如何響應操作
 * 你可以使用 `combineReducers` 將幾個reducer組合成一個reducer函式
 *
 * @param {Function} reducer 給定當前狀態樹和要處理的操作的函式,返回下一個狀態樹
 *
 * @param {any} [preloadedState] 初始狀態. 你可以選擇將其指定為中的universal apps伺服器狀態,或者還原以前序列化的使用者會話。
 * 如果你使用 `combineReducers` 來產生 root reducer 函式,那麼它必須是一個與 `combineReducers` 鍵形狀相同的物件
 *
 * @param {Function} [enhancer] store enhancer. 你可以選擇指定它來增強store的第三方功能
 * 比如 middleware、time travel、persistence, Redux附帶的唯一商店增強器是 `applyMiddleware()`
 *
 * @returns {Store} Redux Store,允許您讀取狀態,排程操作和訂閱更改。
 */
複製程式碼

看完了上面的解釋,(...谷歌翻譯好累啊), 我們繼續往下看,我們從Redux 官網中,能看到,Store Mehods 有四個方法,原始碼中也有看得到~

  • getState()
  • dispatch(action)
  • subscribe(listener)
  • replaceReducer(nextReducer)
import $$observable from 'symbol-observable'

import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'

export default function createStore(reducer, preloadedState, enhancer) {

  // ...

  // 檢查一哈你的state和enhancer引數是否傳反
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  // 如果有傳入合法的enhance,則通過enhancer再呼叫一次createStore
  if (typeof enhancer !== 'undefined') {
    // enhancer 期望得到的是一個函式
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
    return enhancer(createStore)(reducer, preloadedState)
  }

  // reducer 期望得到的是一個函式
  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false // 是否正在分發事件


  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  // 一般在action middleware 中經常會使用 `getState()`方法去獲取當前的state
  function getState() {
    if (isDispatching) {
      throw new Error(
       // ...
      )
    }

    return currentState
  }

  // 原始碼中這個函式的解釋太長了,我大概讀懂了,但是就不寫翻譯了哈,大概如下
  // 註冊listener,同時返回一個取消事件註冊的方法。當呼叫store.dispatch的時候呼叫listener
  function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error(
        '期待 listener 是一個函式'
      )
    }

    if (isDispatching) {
      throw new Error(
        '在reducer執行時,您可能無法呼叫store.subscribe(),如果你希望在更新store後收到通知,
        請從元件訂閱並在回撥中呼叫 store.getState() 以訪問最新狀態'
      )
    }

    let isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

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

      if (isDispatching) {
        throw new Error(
          '在reducer執行時,你可能無法取消訂閱store偵聽器'
        )
      }

      isSubscribed = false

      // 從 nextListeners 中去除掉當前 listener
      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

  // dispatch方法接收的action是個物件,而不是方法。dispatch()是觸發狀態變化的唯一方法。
  // 這個物件實際上就是我們自定義action的返回值,因為dispatch的時候,已經呼叫過我們的自定義action了
  // 更多詳情去官網看講解 : https://redux.js.org/api/store#example
  function dispatch(action) {
    if (!isPlainObject(action)) {  // isPlainObject 最上邊 import 進來的
      throw new Error(
        'Actions 必須是一個普通物件'
      )
    }

    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions 可能沒有未定義的“型別”屬性'
      )
    }

    // 呼叫dispatch的時候只能一個個呼叫,通過dispatch判斷呼叫的狀態
    if (isDispatching) {
      throw new Error(
       // ...
      )
    }

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

    const listeners = (currentListeners = nextListeners)
    // 遍歷呼叫每個 listener
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

  // Replaces the reducer currently used by the store to calculate the state.
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error(
       // ...
      )
    }

    currentReducer = nextReducer
    dispatch({
      type: ActionTypes.REPLACE // ActionTypes 最上邊 import 進來的
    })
  }

  // ...
  // 當create store的時候,reducer會接受一個type為ActionTypes.INIT的action,使reducer返回他們預設的state,這樣可以快速的形成預設的state的結構
  dispatch({
    type: ActionTypes.INIT
  })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}
複製程式碼

來個小彩蛋

是不是很少,很多英文註釋都去掉了,包括 throw new Error 我也沒寫出來,我更推薦的是,自己去看原始碼!!!

彩蛋嘛,就是我們來揭開上邊中提到的 actionTypesisPlainObject 中的神祕面紗~

actionTypes.js

/**
 * 這些是Redux保留的私有操作型別
 * 對於任何未知 actions,你必須返回當前狀態
 * 如果當前的 state 是 undefined,你必須返回初始state
 * 不要直接在程式碼中引用這些操作型別
 */
const randomString = () =>
  Math.random()
    .toString(36)
    .substring(7)
    .split('')
    .join('.')

const ActionTypes = {
  INIT: `@@redux/INIT${randomString()}`,
  REPLACE: `@@redux/REPLACE${randomString()}`,
  PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`
}

export default ActionTypes
複製程式碼

isPlainObject.js

/**
 * 如果引數是普通物件,則返回true
 * Object.getPrototypeOf() 方法返回指定物件的原型,如果沒有繼承屬性,則返回 null
 */
export default function isPlainObject(obj) {
  if (typeof obj !== 'object' || obj === null) return false

  let proto = obj
  while (Object.getPrototypeOf(proto) !== null) {
    proto = Object.getPrototypeOf(proto)
  }

  return Object.getPrototypeOf(obj) === proto
}
複製程式碼

未待完續

上一篇在這裡 : 《為什麼redux要返回一個新的state引發的血案》

寫部落格文章實在太累了,所以還有下一集,還有上邊如果有錯,請大哥們指出,說真,我也是去 github 看原始碼,去官網看文件,搜一些大哥們的文章,還要開啟谷歌翻譯;

其實我寫的,也都是站在各位大哥的肩膀上去結合自己理解寫的總結,鍵盤俠就不要看了,求求你們了,你們太牛逼了,直接去看原始碼吧,傳送門在底下~要不這也給你一個連結

還有關於為什麼只貼程式碼,是因為,直接看程式碼是最直觀的!,比如一個美女,你要我告訴你,她有多麼的沉魚落雁、閉月羞花、國色天香...,還是你直接看比較直觀?

相關連結

相關文章