「react进阶」20分钟吃透redux原理,我这么笨的都懂了!

1,687 阅读4分钟

前言

大家好,我是寄松,无论是在工作中还是在日常面试中,redux就像是我们一个最熟悉的陌生人,我们很多人只知道react的使用方式它内部究竟干了什么,或者说我们有些使用方式还没搞明白。希望我能 用最通俗的话,讲清楚最难的知识点 ,那今天我就带着源码来细细讲解一下redux原理吧?

什么是 redux?

简单来说,redux是一个可预测状态管理容器。react中组件间的通信方式有很多,例如通过props、ref、eventBus、createContext解决组件的通信方式。但在组件众多的项目中,我们难免会遇到这么一种情况:很多组件都要共享某个数据,比如说登陆,项目中很多组件都要知道当前用户是否登陆、当前的登陆信息,我们把信息存在哪里都不太合适,这时候redux就派上用场了。redux就好像一个容器,我们可以用它本身提供的 Api 把我们的数据存放在里面,然后每个组件都可以用redux和我们约定的方式改变数据、获取数据,它还能帮我们记录改变数据的时间点(让我们更好地定位项目出现问题),让我们项目的数据管理井然有序。

什么是react-redux?

千万没弄混了,这可和redux不是一样的东西,它是react的一个绑定库,配合redux使用的,像我们看见的connect、useSelector, useDispatch就是这个库的。而本文整在讲解的是redux

redux 源码目录结构

image.png 图为redux 4.1.1 版本的目录结构

其实 redux 的目录结构并不复杂,index.js即为项目的入口文件,让我们从这开始慢慢吃透。

index.js文件

image.png

可以看到它导出了createStore.js,combineReducers.js,bindActionCreators.js,applyMiddleware.js,compose.js,actionTypes.js,接下来我们一个一个看这六个文件。

actionTypes.js

在看createStore.js之前,我们先看一个最简单的文件

// 封装了一个 ActionTypes 对象,对象跟随机数有关,后面大家就知道它的用途了。
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

createStore.js

image.png

createStore.js 文件导出了dispatchsubscribegetStatereplaceReducer[$$observale],相信大家对store.dispatchstore.subscribestore.getState都不陌生,那store.replaceReducerstore.[$$observale]又是什么呢?让我们一一揭晓。

/* 
    createStore 需要传入三个参数:
    reducer是一个纯函数、preloadedState一般是一个对象、enhance是函数类型的中间件
*/
export default function createStore(reducer: Function, preloadedState: any, enhancer: Function) {
    
  // 如果传入的 preloadedState 是 function 而 enhancer 是 underfined,则把第二个参数当作enhancer
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  // 如果传入了中间件,则用中间件处理createStore,中间件下面会细谈
  if (typeof enhancer !== 'undefined') {
    return enhancer(createStore)(reducer, preloadedState)
  }

  // 传入的reducer
  let currentReducer = reducer 
  // 传入的preloadedState
  let currentState = preloadedState  
  // subscribe的时候会用到,放监听的函数,dispatch的时候会触发这些函数
  let currentListeners = []  
  // currentListeners 的副本,dispatch实际操作的是它,为 dispatch 提供容错率
  let nextListeners = currentListeners
  // 一个标志,isDispatching 为 true 的时候说明 store 正处于 dispatch 调用中
  let isDispatching = false 
    
  // 返回所有 redux 的数据,我们经常会使用 store.getState()
  function getState() {
    return currentState
  }
  
  // 创建 currentListeners 的副本 nextListenere
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }
  
  // 我们经常使用 store.subscribe(funcion...),dispatch后会触发这个function
  function subscribe(listener) {
    // dispatch 正在调用时不能使用 store.subscribe()
    if (isDispatching) {
      throw new Error(
        'You may not call store.subscribe() while the reducer is executing.'
      )
    }
    // 创建 nextListeners,将需要监听的 listener 函数放入
    ensureCanMutateNextListeners()
    nextListeners.push(listener)
    /* 
       subscribe 返回一个 unsubscribe 函数提供我们解绑监听使用,
       我们经常使用这样做: var unsubscribe = store.subsribe(Function)
       不再监听这个函数的时候就 unsubscribe() 解绑,以免造成额外的内存消耗。
    */
    return function unsubscribe() {
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }
  
  function dispatch(action) {
    try {
      isDispatching = true
      // 当 dispatch被调用,将 action 传入 currentReducer 更新 currentState
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }
    // 将之前加入监听的函数全部执行一遍
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }
    return action
  }
  
  // 替换原本你一开始传入的 reducer,一般不会用到
  function replaceReducer(nextReducer) {
    currentReducer = nextReducer
    
  /* 
      还记得之前的actionTypes.js文件吗?ActionTypes.REPLACE 是个随机数。
      为什么要 dispatch 一次。因为reducer替换了。我们需要重新更新 store 中的 currentState。
      
      我们一般创建的reducer纯函数是这样的:
      myNewRedcuer = (state = {...myNewState}, action) => {...}
      reducer如果替换了,state 也重传了一个新对象,肯定要重新初始化 state 的状态赋给 currentState。
      方法就是 dispatch 一下让 currentState = currentReducer(currentState, action) 执行。
    */
    dispatch({ type: ActionTypes.REPLACE })
  }
  
  /*
      外层也有个dispatch({ type: ActionTypes.INIT }),也是用于一开始创建store初始化currentState的。
      
      我们是这样创建store的:createStore(myReducer, myPreloadedState)
      若我们没传第二个参数,那么redux容器初始化的数据就用我们myNeducer传入的第一个参数state
      
      再重复一遍,我们的创建的reducer一般是这样的:
      myRedcuer = (state = {...myNewState}, action) => {...},
      一开始 dispatch 一次让 store 中的 currentState = myNewState
      可以结合上面的 replaceReducer 中的  dispatch({ type: ActionTypes.REPLACE }) 一起理解。
  */
  dispatch({ type: ActionTypes.INIT });
  
  // 这个方法用的比较少,比较鸡肋,大概就是监听一个对象,相信大家能看懂。
  function observable() {
    const outerSubscribe = subscribe
    return {
      subscribe(observer) {
        function observeState() {
          if (observer.next) {
            observer.next(getState())
          }
        }

        observeState()
        const unsubscribe = outerSubscribe(observeState)
        return { unsubscribe }
      },

      [$$observable]() {
        return this
      },
    }
  }
  
  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable,
  }
  
}

combineReducers.js

看完了createStore.js,我们来看看combineReducers.js

// 我们经常这么用 combinReducers:
// const store = createStore(combineReducers({reducer1,reducer2,...}), myState, myEnhance)

// 接下来我们来看看它的原理
export default function combineReducers(reducers) {

  // 复制一个 reducers 的副本 finalReducers,接下来我们操作的是 finalReducers
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]

    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)
  // 可以把 combination 看作一个全新的reducer
  // 举例,当我们使用创建 createStore(combineReducers({reducer1,reducer2,...}) 会发生什么呢
  // 还记得 createStore.js 有个 dispatch({ type: ActionTypes.INIT }) 吗?
  // 它会初始化数据,执行这个全新的 reducer(即下面的函数combination)
  return function combination(state = {}, action) {
    let hasChanged = false
    const nextState = {}
    // 遍历所有的reducer,变量有点多要仔细看
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      // 一开始 state = {},所以每一次的 previousState 都是 underfined
      const previousStateForKey = state[key]
      // 执行 reducer(previousStateForKey, action)
      const nextStateForKey = reducer(previousStateForKey, action)
      
      /* 
          若我们定义的reducer传了初始值
          像 const reducer1 = function(state = myNewState,action),
          则 nextState[key] 是 myNewState,若我们没传初始值,则是 underfined
      */
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }

    /*
        nextState 的结构一开始会是怎样的?
        比如我们写的是 createStore(combineReducers({reducer1,reducer2,reducer3})
        则它的结构如下:
        
        nextState : {
            reducer1: {},
            reducer2: {},
            reducer3: {},
        }
        
    */
    return nextState
  }
  
  // 明白了吗?不明白多看几遍,若是我们再执行 dispatch(action) 会怎样呢?
  // 会再次执行 combineReducers,遍历每个 reducer 传入 action 执行 reducer(previousStateForKey, action)
}

compose.js

在看applyMiddldware.js之前,先看一下这个文件。

/*
你没看错,就是这么简单
举个例子: 
compose(funA,funB,funC) 返回:
function new(...args){
    return (...args) => (...args) => funA(funB(...args))(funC(...args))
} 

其实就一直套娃,在后面用于多个中间件改造 dispatch
*/

export default function compose(...funcs) {
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

applyMiddldware.js

接下来我们看看 redux 是怎么运用中间件的,很多小伙伴可能对中间件有误解,它其实就是一个很简单的函数,我们都可以直接模拟一个。

// 我们一开始的 dispatch(action) 中 action 只能是对象,那我要执行异步操作怎么办呢?
// 这就要用到中间件了,中间件一般就是一个函数。
// 以下举个例子实现一个中间件:

/******************a.js*******************/
import createStore from './createStore';

var store = createStore(myReducer, myInitialState)

// 中间件,功能是封装一个新的 dispatch! 给原本的 store.dispatch 改造一下加功能。 
function hasMiddlewareDispatch(action: Function || Object){
    if(action instanceOf Function){
        // 执行这个函数的同时,传入 dispatch
        action(store.dispatch)
    }else if(typeof action == 'object'){
        // 若传的就就是 action({type: '...'}),则正常执行
        store.dispatch(action)
    }
    
}
export default { ...store, dispatch: hasMiddlewareDispatch }
/****************************************/



/******************a.js*******************/
// 使用 store 的文件(命名为b.js)
import store from './b.js';
// 当我们有异步操作获取数据,然后想存在 redux 数据的时候
async function myAsynchronous(dispatch){
    const myData = await Promise((resolve, reject) => { ... })
    dispatch({ type:'changeMyData', payload: myData}})
}

store.dispatch(myAsynchronous)
/****************************************/

一个简单的中间件就这样完成,然后使用了,是不是很简单?

终于可以看 applyMiddldware.js 了,我们一起看看它干了什么 :

// applyMiddleware 这样用的:
// const store = createStore(myReducer, myInitState, applyMiddleware(...middleware))
// 还记得 createStore.js 中如果传了enhance 会执行 enhancer(createStore)(reducer, preloadedState)吗?
// applyMiddleware 就是重新改造了 dispatch。

export default function applyMiddleware(...middlewares) {
  return (createStore) => (...args) => {
    const store = createStore(...args)

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args),
    }
    
    const chain = middlewares.map((middleware) => middleware(middlewareAPI))
    
    // 套娃改造 dispatch,若真想了解细节可以找个开源的 middleware 参考看看中间件干了什么
    dispatch = compose(...chain)(store.dispatch)

    // 看返回值,是不是跟我们之前自己封装改造 dispatch 的方式差不多?
    return {
      ...store,
      dispatch,
    }
  }
}

bindActionCreators.js

最后我们来看看这个文件

/*
举个使用的例子: 
import createStore from './createStore'
const store = createStore(reducer,initialState)
function myFunA(){ 
   ...做自己想做的操作
   return { type: 'add'} 
}
store.bindActionCreators(myFunA)
其实这个 api 的作用就是提供我们在dispatch之前再用一个函数处理一些问题,返回action能力。使用场景比较少。
*/

import { kindOf } from './utils/kindOf'

function bindActionCreator(actionCreator, dispatch) {
  return function () {
    return dispatch(actionCreator.apply(this, arguments))
  }
}

export default function bindActionCreators(actionCreators, dispatch) {

  // 传入actionCreators必须是函数或者一个对象
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  // 如果传的不止一个函数
  const boundActionCreators = {}
  for (const key in actionCreators) {
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

结语

希望能帮到那些一直想了解redux原理的同学

如果你觉得此文对你有一丁点帮助,点个赞,鼓励一下。

如果文章有什么不足之处,欢迎指出和提供建议哈~

如果你想一起学习前端或者摸鱼,那你可以底下留言哦~