Redux 源码剖析(JS版)

982 阅读9分钟

文章已同步至【个人博客】,欢迎访问【我的主页】😃
文章地址:blog.fanjunyang.zone/archives/re…

什么是Redux?

Redux 的工作流程(核心思想):

1.设计全局 state 的数据结构状态树
2.设计更改 state 数据、状态的 actionType 常量
3.根据 actionType,编写 actionCreator
4.根据各个 actionCreator 的返回值,用 reducer 做数据处理
5.有个 reducer 之后,用 createStore 来得到全局唯一的 store,来管理 state
6.用 bindActionCreator 函数将 actionCreator 和 store.dispatch 绑定起来,得到一组能更改 state 的函数
7.分发使用各个状态修改函数(dispatch)

源码剖析

源码地址

源码结构

src
├── utils                #工具函数文件夹
├── applyMiddleware.js
├── bindActionCreators.js        
├── combineReducers.js     
├── compose.js       
├── createStore.js  
└── index.js             #入口 js

index.js

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'

function isCrushed() {}

if (
  process.env.NODE_ENV !== 'production' &&
  typeof isCrushed.name === 'string' &&
  isCrushed.name !== 'isCrushed'
) {
  warning(
    '。。。'
  )
}

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose
}

这里的 isCrushed 函数主要是为了验证在非生产环境下 redux 是否被压缩(默认情况下,isCrushed.name等于isCrushed,如果被压缩了,函数的名称会变短,一般会压缩成数字,那么 (isCrushed.name !== 'isCrushed') 就是 true),如果被压缩,就给开发者一个 warn 提示)。

然后就是暴露 createStorecombineReducersbindActionCreatorsapplyMiddlewarecompose这几个接口给开发者使用。

createStore.js

createStore是redux的核心API。
createStore会生成一个仓库(store),用来维护一个全局的state

import isPlainObject from 'lodash/isPlainObject'
import ?observable from 'symbol-observable'

// 私有 action
export var ActionTypes = {
  INIT: '@@redux/INIT'
}

export default function createStore(reducer, preloadedState, enhancer) {
  // 判断接受的参数个数,来指定 reducer 、 preloadedState 和 enhancer
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  // 如果 enhancer 存在并且适合合法的函数,那么调用 enhancer,并且终止当前函数执行
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }

  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

  // 储存当前的 currentReducer
  var currentReducer = reducer
  // 储存当前的状态
  var currentState = preloadedState
  // 储存当前的监听函数列表
  var currentListeners = []
  // 储存下一个监听函数列表
  var nextListeners = currentListeners
  var isDispatching = false

  // 这个函数可以根据当前监听函数的列表生成新的下一个监听函数列表引用
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

/*  函数
  ... getState ...

  ... subscribe ...

  ... dispatch ...

  ... replaceReducer ...

  ... observable ...
*/

  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [?observable]: observable
  }
}

createStore接收3个参数:

  • reducer (处理函数,下面介绍)
  • preloadedState(state的初始值)
  • enhancer(一个高阶函数,可以改变store的接口,下面介绍)

createStore的返回值是dispatchsubscribegetStatereplaceReducer[?observable]: observable,他们共同组成了一个store

action

action代表的是用户的操作。

redux规定action一定要包含一个type属性,且type属性也要唯一,相同的type,redux视为同一种操作,因为处理action的函数reducer只判断action中的type属性。

reducer

reducer 只是一个模式匹配的东西,真正处理数据的函数,一般是额外写在别的地方(当然直接写在reducer中也没问题,只是不利于后期维护),只是在reducer中调用罢了。

export default (state, action) => {
    switch (action.type) {
        case A:
        return handleA(state)
        case B:
        return handleB(state)
        case C:
        return handleC(state)
        default:
        return state  // 如果没有匹配上就直接返回原 state
    }
}

reducer 接收两个参数,state 以及 action 函数返回的 action对象,并返回最新的 state。

reducer 为什么叫 reducer 呢?因为 action 对象各种各样,每种对应某个 case ,但最后都汇总到 state 对象中,从多到一,这是一个减少( reduce )的过程,所以完成这个过程的函数叫 reducer。

getState

function getState() {
  return currentState
}

整个项目的currentState是处于一个闭包之中,所以能一直存在,getState会返回当前最新的state。

简单的说,就是类似于一个get的方法,返回currentState的值。

subscribe

function subscribe(listener) {
  if (typeof listener !== 'function') {
    throw new Error('Expected listener to be a function.')
  }

  let isSubscribed = true

  ensureCanMutateNextListeners()
  nextListeners.push(listener)

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

    isSubscribed = false

    ensureCanMutateNextListeners()
    const index = nextListeners.indexOf(listener)
    nextListeners.splice(index, 1)
  }
}

subscribe接收一个listener

他的作用是给store添加监听函数nextListeners储存了整个监听函数列表。
subscribe的返回值是一个unsubscribe,是一个解绑函数,调用该解绑函数,会将已经添加的监听函数删除,该监听函数处于一个闭包之中,会一直存在,所以在解绑函数中能删除该监听函数。

dispatch

function dispatch(action) {
  if (!isPlainObject(action)) {
    throw new Error(
      'Actions must be plain objects. ' +
      'Use custom middleware for async actions.'
    )
  }

  if (typeof action.type === 'undefined') {
    throw new Error(
      'Actions may not have an undefined "type" property. ' +
      'Have you misspelled a constant?'
    )
  }

  if (isDispatching) {
    throw new Error('Reducers may not dispatch actions.')
  }

  try {
    isDispatching = true
    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
}

dispatch接收一个参数action

代码会先调用createStore传入的参数reducer方法,reducer接收当前stateaction,通过判断actionType,来做对应的操作,并返回最新的currentState
dispatch还会触发整个监听函数列表,所以最后整个监听函数列表都会按顺序执行一遍。

dispatch返回值就是传入的action

replaceReducer

function replaceReducer(nextReducer) {
  if (typeof nextReducer !== 'function') {
    throw new Error('Expected the nextReducer to be a function.')
  }

  currentReducer = nextReducer
  dispatch({ type: ActionTypes.INIT })
}

replaceReducer是替换当前的reducer的函数。

replaceReducer接收一个新的reducer,替换完成之后,会执行 dispatch({ type: ActionTypes.INIT }) ,用来初始化store的状态。

官方举出了三种replaceReducer的使用场景,分别是:

  • 当你的程序要进行代码分割的时候
  • 当你要动态的加载不同的reducer的时候
  • 当你要实现一个实时reloading机制的时候

combineReducers.js

// 以下只留下了核心代码
// combination 函数是 combineReducers(reducers) 的返回值,它是真正的 rootReducer
// finalReducers 是 combineReducers(reducers) 的 reducers 对象去掉非函数属性的产物
 // mapValue 把 finalReducers 对象里的函数,映射到相同 key 值的新对象中
 
function combination(state = defaultState, action) {
    var finalState = mapValues(finalReducers, (reducer, key) => {
      var newState = reducer(state[key], action); //这里调用子 reducer 
      if (typeof newState === 'undefined') {
        throw new Error(getErrorMessage(key, action));
      }
      return newState; //返回新的子 state
    });
    //...省略一些无关的代码
    return finalState; //返回新 state
 };

这个函数可以组合一组 reducers,然后返回一个新的 reducer。

随着整个项目越来越大,state 状态树也会越来越庞大,state的层级也会越来越深,当某个action.type所对应的 case 要修改深层属性时,那样的话函数写起来就非常难看,所以必须在这个函数的头部验证 state 对象有没有那个属性。

combineReducers实现方法也比较简单,它遍历传入的reducers,返回一个新的reducer,这个新对象的 key 跟传入的reducers一样,它的 value 则是传入的reducers的不同key对应的value展开的{ key: value }

举个例子:

var reducers = {
    todos: (state, action) { // 此处的 state 参数是全局 state.todos属性
        switch (action.type) {...} // 返回的 new state 更新到全局 state.todos 属性中
    },
    activeFilter: (state, action) { // 拿到 state.activeFilter 作为此处的 state
        switch (action.type) {...} // new state 更新到全局 state.activeFilter 属性中
    }
}
var rootReducer = combineReducers(reducers)

combineReducers 内部会将 state.todos 属性作为 todos: (state, action) 的 state 参数传进去,通过 switch (action.type) 之后返回的 new state 也会更新到 state.todos 属性中;也会将 state.activeFilter 属性作为 activeFilter: (state, action) 的 state 参数传进去,通过 switch (action.type) 之后返回的 new state 也会更新到 state.activeFilter 属性中。

bindActionCreators.js

function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}

export default function bindActionCreators(actionCreators, dispatch) {
  const keys = Object.keys(actionCreators)
  const boundActionCreators = {}
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

bindActionCreators的代码就是将actionCreatordispatch联结在一起。

对于多个 actionCreator,我们可以像reducers一样,组织成一个 key/action的组合。
由于很多情况下,actionactionCreator 返回的,实际上要这样调用 store.dispatch(actionCreator(...args))很麻烦,只能再封装一层,通过反复组合,将嵌套的函数分离。

compose.js

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

compose调用了ES5的Array.prototype.reduce方法,将形如fn(arg1)(arg2)(arg3)...的柯里化函数按照顺序执行。

其传入的参数为函数数组,返回的为reduce从左到右合并后的新的函数,是一个类似于链式调用的过程。

funcs.reduce((a, b) => (...args) => a(b(...args)))

这句特别重要,组合函数的这部非常重要,我们发现...args参数会依次的从右到左执行,比如将b(...args)的执行结果,传入a中作为参数继续执行。

applyMiddleware.js

export default function applyMiddleware(...middlewares) {
  return createStore => (reducer, initialState) => {
    var store = createStore(reducer, initialState);
    var dispatch = store.dispatch; //拿到真正的 dispatch
    // 将最重要的两个方法 getState/dispatch 整合出来
    var middlewareAPI = {
      getState: store.getState,
      dispatch: action => dispatch(action)
    };
    // 依次传递给 middleware,让它们有控制权
    var chain = middlewares.map(middleware => middleware(middlewareAPI));
    dispatch = compose(...chain, dispatch); // 再组合出新的 dispatch

    return {
      ...store,
      dispatch
    };
  };
}

applyMiddleware就是中间件的意思。

applyMiddleware接收中间件为参数,并返回一个以createStore为参数的函数;

同时applyMiddleware又是createStore函数中的第三个参数,所以我们回到createStore的代码,找到了:

if (typeof enhancer !== 'undefined') {
  if (typeof enhancer !== 'function') {
    throw new Error('Expected the enhancer to be a function.')
  }

  return enhancer(createStore)(reducer, preloadedState)
}

createStore中传了第三个参数的时候,会执行enhancer(createStore)(reducer, preloadedState),这是一个柯里化函数;

我们可以设想中间件的使用方法:const store = createStore( reducer, applyMiddleware([...中间件]))
applyMiddleware([...中间件])的返回值是一个以createStore为参数的函数,这个函数会在createStore中执行,返回的函数也会继续执行,最后返回一个store

继续回到applyMiddleware中,在返回store之前,中间件将最重要的两个方法 getState/dispatch 整合出来,并传递给中间件使用,中间件处理完之后,返回一个新的dispatch

applyMiddleware把中间件放在一个chain数组中,并通过compose方法(我们上面已经介绍过了),让每个中间件按照顺序一次传入dispatch参数执行,再组合出新的 dispatch。由此可见,每个中间件的格式都应该是接收一个{ dispatch, getState },返回一个(dispatch) => { return function(action) { ... }}

补充(结合React使用)

通常我们使用Redux的时候,都是React要使用
那么就避免不了把这两者联系起来,其实本质上ReduxReact是没有任何联系的。

那么我们要用到依赖react-redux

react-redux

就是把 react 和 redux 联系到一起。

react-redux提供两个方法:connectProvider

connect

connect方法就是连接React组件和Redux store

connect实际上是一个高阶函数,返回一个新的已经与 Redux store 连接的组件类。

例如:

const VisibleCounter = connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter)

Counter 是 UI 组件,VisibleCounter就是由 react-redux 通过connect方法自动生成的容器组件。

  1. mapStateToProps:从Redux状态树中提取需要的部分作为props传递给当前的组件。
  2. mapDispatchToProps:将需要绑定的响应事件(action)作为props传递到组件上(也可以不用写这个,直接把actions绑上去就好了)。
    例如:
//需要引入actions,就是一系列的方法
const VisibleCounter = connect(
  mapStateToProps,
  actions
)(Counter)

代码示例:

import React, { Component } from 'react';
import { connect } from 'react-redux'
import actions from '../store/actions/counter1'

class Counter1 extends Component {
    constructor(props) {
        super(props)
    }
    render() {
        return (
            <>
                <h1>Counter1</h1>
                <p>{this.props.number}</p>
                <button onClick={() => this.props.increment(2)}>+</button>
                <button onClick={() => this.props.decrement(2)}>-</button>
            </>
        )
    }
}

// state 就是仓库中的状态
// function mapStateToProps(state){
//     // number第1个number是指这个组件中的属性
//     // state.number 指的是仓库中的number
//     return {number:state.number}
// }

// let mapStateToProps = function(state){
//     // number第1个number是指这个组件中的属性
//     // state.number 指的是仓库中的number
//     return {number:state.number}
// }

// ({number:state.number})  如果返回一个对象,需要给穿上对象外面包一个小括号
// let mapStateToProps = state=>({number:state.number})

//第一种写法------------------------------------------------
// function mapStateToProps(state) {
//     return {
//         number: state.counter1.number
//     }
// }

// function mapDispatchToProps(dispatch) {
//     return bindActionCreators(actions, dispatch)
// }

// export default connect(mapStateToProps,mapDispatchToProps)(Counter1);

//第二种写法------------------------------------------------
// let mapStateToProps = state=>({number:state.counter1.number})

// let mapDispatchToProps = dispatch=>bindActionCreators(actions,dispatch)

// export default connect(mapStateToProps,mapDispatchToProps)(Counter1);

//第三种写法------------------------------------------------
let mapStateToProps = state=>({number:state.counter1.number})

export default connect(mapStateToProps,actions)(Counter1);

其中引入的actions就是一系列的方法:

import * as types from "../action-types"

function increment(payload) {
    return { type: types.ADD1, payload }
}
function decrement(payload) {
    return { type: types.SUB1, payload }
}

export default { increment, decrement }

Provider

Provider可以实现store的全局访问,将store传给每个组件。

原理:使用Reactcontextcontext可以实现跨组件之间的传递。

使用时,我们需要在入口index.js文件中引入使用,如下:

import React from "react"
import ReactDOM from "react-dom"
import App from './App'
//使用react-redux中的Provider
import { Provider } from 'react-redux'

import store from './store'

ReactDOM.render(
    // 别忘了包起来,并且把store绑上去,可以让所有的组件都可以使用仓库里的状态
    <Provider store={store}>
        <App></App>
    </Provider>
    , window.app)

并且我们需要使用其中的connect方法实现数据和方法的映射,使用方式也非常简单

redux-thunk

就是增强store.dispatch()的功能,即可以在reducer中进行一些异步的操作,可以派发多种类型的东西

使用方式也非常简单,直接将thunk中间件引入,放在applyMiddleware方法之中即可。

redux-logger

可以输出日志信息

使用的时候需要通过createLogger创建一个logger,再把创建这个logger放在applyMiddleware方法之中即可。

import { createLogger } from 'redux-logger'

const logger = createLogger({
    // ...options
})

applyMiddleware(logger)

等等......


TOT