Redux源码剖析

Redux运用中的几个点:

  1. Redux三大设想准绳
  2. Create Store
  3. Redux middleware
  4. combineReducer
  5. Provider与Connect
  6. Redux流程梳理
  7. Redux设想特征

1. Redux三大设想准绳

1. 单一数据源

在传统的 MVC 架构中,我们能够依据须要建立无数个 Model,而 Model 之间能够互相监听、触发事宜以至轮回或嵌套触发事宜,这些在 Redux 中都是不许可的。由于在 Redux 的头脑里,一个运用永久只需唯一的数据源。
实际上,运用单一数据源的优点在于全部运用状况都保留在一个对象中,如许我们随时能够提掏出全部运用的状况举行耐久化(比方完成一个针对全部运用的立即保留功用)。另外,如许的设想也为服务端衬着供应了能够。

2. 状况是只读的

在 Redux 中,我们并不会本身用代码来定义一个 store。取而代之的是,我们定义一个 reducer,它的功用是依据当前触发的 action 对当前运用的状况(state)举行迭代,这里我们并没有直接修正运用的状况,而是返回了一份全新的状况。

Redux 供应的 createStore 要领会依据 reducer 天生 store。末了,我们能够应用 store. dispatch
要领来到达修正状况的目标。

3.状况修正均由纯函数完成

在 Redux 里,我们经由历程定义 reducer 来肯定状况的修正,而每一个 reducer 都是纯函数,这意味着它没有副作用,即接收肯定的输入,必定会取得肯定的输出。

如许设想的优点不仅在于 reducer 里对状况的修正变得简朴、地道、可测试,更有意义的是,Redux 应用每次新返回的状况天生酷炫的时刻游览(time travel)调试体式格局,让跟踪每一次由于触发 action 而转变状况的结果成为了能够。

2.Create Store

我们从store的降生最先提及。create store函数API文档以下:

createStore(reducer, [initialState], enhancer)

能够看出,它接收三个参数:reducer、initialState 和 enhancer 。Store enhancer 是一个组合 store creator 的高阶函数,返回一个新的强化过的 store creator。这与 middleware 类似,它也许可你经由历程复合函数转变 store 接口。

再来看看他的返回值:

{
    dispatch: f (action),
    getState: f (),
    replaceReducer: f (nextReducer),
    subscribe: f (listener),
    Symbol(observable): f ()    
}

store的返回值就是一个一般对象,内里有几个常常使用的要领:

  • dispatch:就是我们最常常使用的dispatch要领,派发action。
  • getState:经由历程该要领,我们能够拿到当前状况树state。
  • replaceReducer:这个要领重要用于 reducer 的热替代,下面引见该要领。
  • subscribe:增加一个变化监听器。每当 dispatch(action)的时刻就会实行,state 树中的一部份能够已变化。
  • observable:观察者形式,用于处置惩罚定阅关联。

这里挑几个要领引见:

getState

在完成基础的参数校验以后,在 createStore 中声明以下变量及 getState 要领:

var currentReducer = reducer
var currentState = initialState
var listeners = [] // 当前监听 store 变化的监听器
var isDispatching = false // 某个 action 是不是处于分发的处置惩罚历程当中
/**
* Reads the state tree managed by the store.
 *
* @returns {any} The current state tree of your application.
 */
function getState() {
 return currentState
} 

getState要领就是简朴返回当前state,假如state没有被reducer处置惩罚过,他就是initialState。

subscribe

在 getState 以后,定义了 store 的另一个要领 subscribe:

function subscribe(listener) {
 listeners.push(listener)
 var isSubscribed = true
 return function unsubscribe() {
 if (!isSubscribed) {
 return
 }
 isSubscribed = false
 var index = listeners.indexOf(listener)
 listeners.splice(index, 1)
 }
} 

Store 许可运用store.subscribe要领设置监听函数,一旦 State 发作变化,就自动实行这个函数。

明显,只需把 View 的更新函数(关于 React 项目,就是组件的render要领或setState要领)放入listen,就会完成 View 的自动衬着。你能够会觉得新鲜,彷佛我们在 Redux 运用中并没有运用 store.subscribe 要领?事实上,

React Redux 中的 connect 要领隐式地帮我们完成了这个事情。

store.subscribe要领返回一个函数,挪用这个函数就能够消除监听。

dispatch

dispatch是redux的中心要领:

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
    }
    listeners.slice().forEach(listener => listener())
    return action
} 

推断当前是不是处于某个 action 的分发历程当中,这个搜检重要是为了防止在 reducer 平分发 action 的状况,由于如许做能够致使分发死轮回,同时也增加了数据活动的庞杂度。

确认当前不属于分发历程当中后,先设定标志位,然后将当前的状况和 action 传给当前的reducer,用于天生最新的 state。这看起来一点都不庞杂,这也是我们反复强调的 reducer 事情历程——纯函数、接收状况和 action 作为参数,返回一个新的状况。

在取得新的状况后,顺次挪用一切的监听器,关照状况的变动。须要注重的是,我们在关照监听器变动发作时,并没有将最新的状况作为参数通报给这些监听器。这是由于在监听器中,我们能够直接挪用 store.getState() 要领拿到最新的状况。

终究,处置惩罚以后的 action 会被 dispatch 要领返回。

replaceReducer

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

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

这是为了拿到一切 reducer 中的初始状况(你是不是还记得在定义 reducer 时,第一个参数为previousState,假如该参数为空,我们供应默许的 initialState)。只需一切的初始状况都胜利猎取后,Redux 运用才井井有条地最先运作。

3.Redux middleware

It provides a third-party extension point between dispatching an action, and the moment it reaches

the reducer

它供应了一个分类处置惩罚 action 的时机。在middleware 中,你能够校阅阅兵每一个流过的 action,挑选出特定范例的action 举行相应操纵,给你一次转变 action 的时机。

通例的同步数据流形式的流程图以下:
《Redux源码剖析》
差别营业需求下,比方实行action之前和以后都要打log;action触发一个异步的要求,要求返来以后衬着view等。须要为这一类的action增加大众的要领或许处置惩罚,运用redux middleware流程图以下:
《Redux源码剖析》
每一个 middleware 处置惩罚一个相对自力的营业需求,经由历程串连差别的 middleware 完成变化多样的功用。比方上面的营业,我们把处置惩罚log的代码封装成一个middleware,处置惩罚异步的也是一个middleware,二者串连,却又互相自力。

运用middleware以后,action触发的dispatch并非本来的dispatch,而是经由封装的new dispatch,在这个new dispatch中,根据递次顺次实行每一个middleware,末了挪用原生的dispatch。

我们来看下logger middleware怎样完成的:

export default store => next => action => {
    console.log('dispatch:', action); 
    next(action);
    console.log('finish:', action);
 } 

这里代码非常简约,就是在next挪用下一个middleware之前和以后,离别打印两次。

Redux 供应了 applyMiddleware 要领来加载 middleware,该要领的源码以下:

import compose from './compose';

export default function applyMiddleware(...middlewares) {
    return function (next) {
        return function (reducer, initialState) {
            let store = next(reducer, initialState);
            let dispatch = store.dispatch;
            let chain = [];
            var middlewareAPI = {
                getState: store.getState,
                dispatch: (action) => dispatch(action),
            };
            chain = middlewares.map(middleware => middleware(middlewareAPI));
            dispatch = compose(...chain)(store.dispatch);

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

个中compose源码以下:

function compose(...funcs) {
    return arg => funcs.reduceRight((composed, f) => f(composed), arg);
} 

运用的时刻,以下:

const newStore = applyMiddleware([mid1, mid2, mid3, ...])(createStore)(reducer, initialState); 

ok,相干源码已就位,我们来细致剖析一波。

函数式编程头脑设想 :middleware 的设想有点特别,是一个层层包裹的匿名函数,这实际上是函数式编程中的
currying,它是一种运用匿名单参数函数来完成多参数函数的要领。applyMiddleware 会对 logger 这个middleware 举行层层挪用,动态地将 store 和 next 参数赋值。currying 的 middleware 构造的优点重要有以下两点。

  • 易串连:currying 函数具有耽误实行的特征,经由历程不停 currying 构成的 middleware 能够积累参数,再配合组合(compose)的体式格局,很轻易构成 pipeline 来处置惩罚数据流。
  •  同享 store: 在 applyMiddleware 实行的历程当中,store 照样旧的,然则由于闭包的存在,applyMiddleware 完成后,一切的 middleware 内部拿到的 store 是最新且雷同的。

给 middleware 分发 store:newStore建立完成以后,applyMiddleware 要领连续取得了3个参数,第一个是 middlewares 数组[mid1, mid2, mid3, …],第二个是 Redux 原生的 createStore ,末了一个是 reducer。然后,我们能够看到 applyMiddleware 应用 createStore 和 reducer 建立了一个 store。而 store 的 getState要领和 dispatch 要领又离别被直接和间接地赋值给 middlewareAPI 变量 store:

const middlewareAPI = {
 getState: store.getState,
 dispatch: (action) => dispatch(action),
};
chain = middlewares.map(middleware => middleware(middlewareAPI)); 

然后,让每一个 middleware 带着 middlewareAPI 这个参数离别实行一遍。实行完后,取得 chain数组 [f1, f2, … , fx, …, fn],它保留的对象是第二个箭头函数返回的匿名函数。由因而闭包,每一个匿名函数都能够接见雷同的 store,即 middlewareAPI。

middlewareAPI 中的 dispatch 为何要用匿名函数包裹呢?

我们用 applyMiddleware 是为了革新 dispatch,所以 applyMiddleware 实行完后,dispatch 是变化了的,而 middlewareAPI 是 applyMiddleware 实行平分发到各个 middleware 的,所以必须用匿名函数包裹 dispatch,如许只需 dispatch 更新了,middlewareAPI 中的 dispatch 运用也会发作变化。

组合串连 middleware:这一层只需一行代码,倒是 applyMiddleware 英华之地点dispatch = compose(...chain)(store.dispatch); ,个中 compose 是函数式编程中的组合,它将 chain 中的一切匿名函数 [f1, f2, … , fx, …, fn]组装成一个新的函数,即新的 dispatch。当新 dispatch 实行时,[f1, f2, … , fx, …, fn],从右到左顺次实行。

compose(…funcs) 返回的是一个匿名函数,个中 funcs 就是 chain 数组。当挪用 reduceRight时,顺次从 funcs 数组的右端取一个函数 fx 拿来实行,fx 的参数 composed 就是前一次 fx+1 实行的结果,而第一次实行的 fn(n 代表 chain 的长度)的参数 arg 就是 store.dispatch。所以,当 compose 实行完后,我们取得的 dispatch 是如许的,假定 n = 3:

dispatch = f1(f2(f3(store.dispatch)))); 

这时候挪用新 dispatch,每一个 middleware 就顺次实行了。

在 middleware 中挪用 dispatch 会发作什么:经由 compose 后,一切的 middleware 算是串连起来了。但是另有一个题目,在分发 store 时,我们提到过每一个 middleware 都能够接见 store,即 middlewareAPI 这个变量,也能够拿到 store 的dispatch 属性。那末,在 middleware 中挪用 store.dispatch() 会发作什么,和挪用 next() 有区分吗?如今我们来申明二者的差别:

const logger = store => next => action => {
 console.log('dispatch:', action);
 next(action);
 console.log('finish:', action);
};
const logger = store => next => action => {
 console.log('dispatch:', action);
 store.dispatch(action);
 console.log('finish:', action);
}; 

在分发 store 时我们诠释过,middleware 中 store 的 dispatch 经由历程匿名函数的体式格局和终究compose 完毕后的新 dispatch 保持一致,所以,在 middleware 中挪用 store.dispatch() 和在其他任何地方挪用的结果一样。而在 middleware 中挪用 next(),结果是进入下一个 middleware,下图就是redux middleware最著名的洋葱模子图。
《Redux源码剖析》

4.combineReducer

假如一个项目过大,我们一般按模块来写reducer,然则redux create store只接收一个reducer参数,所以我们须要兼并reducer。这里就用到了redux供应的combineReducer辅佐函数:

combineReducers({
      layout,
      home,
      ...asyncReducers
  })

这个函数用起来很简朴,就是传入一个对象,key是模块reducer对应的名字, 值是对应reducer。值是一个function,相称因而一个新的reducer,源码以下:

export default function combineReducers(reducers) {
  var reducerKeys = Object.keys(reducers)
  var finalReducers = {}
  for (var i = 0; i < reducerKeys.length; i++) {
    var key = reducerKeys[i]

    if (process.env.NODE_ENV !== 'production') {
      if (typeof reducers[key] === 'undefined') {
        warning(`No reducer provided for key "${key}"`)
      }
    }

    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  var finalReducerKeys = Object.keys(finalReducers)

  if (process.env.NODE_ENV !== 'production') {
    var unexpectedKeyCache = {}
  }

  var sanityError
  try {
    assertReducerSanity(finalReducers)
  } catch (e) {
    sanityError = e
  }

  return function combination(state = {}, action) {
    if (sanityError) {
      throw sanityError
    }

    if (process.env.NODE_ENV !== 'production') {
      var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
      if (warningMessage) {
        warning(warningMessage)
      }
    }

    var hasChanged = false
    var nextState = {}
    for (var i = 0; i < finalReducerKeys.length; i++) {
      var key = finalReducerKeys[i]
      var reducer = finalReducers[key]
      var previousStateForKey = state[key]
      var nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        var errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

源码不是许多,撤除一些考证代码,剩下的就是说:return一个function,我们临时称谓他combination,就相称因而与一个总的reducer,每次action都邑走到combination中,combination会遍历输入的reducer,将action放到每一个reducer中实行一下,盘算出返回结果就是nextState,nextState于previousState假如!==申明转变了,返回nextState,不然返回实行之前的state。

这也诠释了差别模块actionType假如雷同的话,两个模块的reducer都邑走一遍的题目,在actionType称号前面加上模块前缀即可解决题目。

5. Provider与Connect

Provider与Connet组件都是React-Redux供应的中心组件,二者看起来功用一样,都是协助容器组件猎取store中的数据,然则道理与功用却差别。

Provider

Provider组件在一切组件的最外层,其接收store作为参数,将store里的state运用context属性向下通报。部份源码:

export default class Provider extends Component {
 getChildContext() {
 return { store: this.store }
 }
 constructor(props, context) {
 super(props, context)
 this.store = props.store
 }
 render() {
 const { children } = this.props
 return Children.only(children)
 }
} 

应用context这个属性,Provider一切子组件均能够拿到这个属性。

Connect

connect完成的功用是将须要关联store的组件和store的dispatch等数据混合到一块,这块就是一个高阶组件典范的运用:

import hoistStatics from 'hoist-non-react-statics'
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
 // ...
 return function wrapWithConnect(WrappedComponent) {
 // ...
 class Connect extends Component {
 // ...
 render() {
 // ...
 if (withRef) {
 this.renderedElement = createElement(WrappedComponent, {
 ...this.mergedProps,
 ref: 'wrappedInstance'
 })
 } else {
 this.renderedElement = createElement(WrappedComponent,
 this.mergedProps
 )
 }
 return this.renderedElement
 }
 }
 // ...
 return hoistStatcis(Connect, WrappedComponent);
 }
} 

照样先从他的四个参数提及:

1.mapStateToProps

connect 的第一个参数定义了我们须要从 Redux 状况树中提取哪些部份看成 props 传给当前组件。一般来说,这也是我们运用 connect 时常常传入的参数。事实上,假如不传入这个参数,React 组件将永久不会和 Redux 的状况树发生任何关联。详细在源代码中的表现为:

export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
 const shouldSubscribe = Boolean(mapStateToProps)
 // ...
 class Connect extends Component {
 // ...
 trySubscribe() {
 if (shouldSubscribe && !this.unsubscribe) {
 this.unsubscribe = this.store.subscribe(this.handleChange.bind(this))
 this.handleChange()
 }
 }
 // ...
 }
} 

mapStateToProps会定阅 Store,每当state更新的时刻,就会自动实行,从新盘算 UI 组件的参数,从而触发 UI 组件的从新衬着。

mapStateToProps的第一个参数老是state对象,还能够运用第二个参数,代表容器组件的props对象。

这块的源码相对较简朴:

const mapState = mapStateToProps || defaultMapStateToProps 
class Connect extends Component { 
    computeStateProps(store, props) {
        if (!this.finalMapStateToProps) {
          return this.configureFinalMapState(store, props)
        }

        const state = store.getState()
        const stateProps = this.doStatePropsDependOnOwnProps ?
          this.finalMapStateToProps(state, props) :
          this.finalMapStateToProps(state)

        if (process.env.NODE_ENV !== 'production') {
          checkStateShape(stateProps, 'mapStateToProps')
        }
        return stateProps
      }

      configureFinalMapState(store, props) {
        const mappedState = mapState(store.getState(), props)
        const isFactory = typeof mappedState === 'function'

        this.finalMapStateToProps = isFactory ? mappedState : mapState
        this.doStatePropsDependOnOwnProps = this.finalMapStateToProps.length !== 1

        if (isFactory) {
          return this.computeStateProps(store, props)
        }

        if (process.env.NODE_ENV !== 'production') {
          checkStateShape(mappedState, 'mapStateToProps')
        }
        return mappedState
      }
}

这块道理很简朴,举行一些参数校验,推断第一个参数mapStateToProps返回值是不是为function,假如是递归挪用,不是的话算出返回值。假如没传这个参数,默许给{}。

我们能够会迷惑为何传给 connect 的第一个参数本身是一个函数,react-redux 还许可这个函数的返回值也是一个函数呢?

简朴地说,如许设想能够许可我们在 connect 的第一个参数里应用函数闭包举行一些庞杂盘算的缓存,从而完成效力优化的目标

当我们运用的时刻:

const mapStateToProps = (state, props) => ({
    home: state.home,
    layout: state.layout
});

运用ownProps作为参数后,假如容器组件的参数发作变化,也会激发 UI 组件从新衬着

2.mapDispatchToProps

人如其名,它接收 store 的 dispatch 作为第一个参数,同时接收 this.props 作为可选的第二个参数。应用这个要领,我们能够在 connect 中方便地将 actionCreator 与 dispatch 绑定在一起(应用 bindActionCreators 要领),终究绑定好的要领也会作为 props 传给当前组件。这块的源码与mapStateToProps一样,就不贴了。

bindActionCreator

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

3.mergeProps

前两个参数返回的对象,都要跟组件本身的props merge一下,构成一个新的对象赋值给对应组件,我们能够在这一步做一些处置惩罚,这个参数就是干这个的,该参数署名:

mergeProps(stateProps, dispatchProps, ownProps): props

默许状况假如没传该参数,返回Object.assign(ownProps, stateProps, dispatchProps)

4.options

假如指定这个参数,能够定制 connector 的行动。

  • [pure = true] (Boolean): 假如为 true,connector 将实行 shouldComponentUpdate 而且浅对照 mergeProps 的结果,防止不必要的更新,条件是当前组件是一个“纯”组件,它不依赖于任何的输入或 state 而只依赖于 props 和 Redux store 的 state。默许值为 true。
  • [withRef = false] (Boolean): 假如为 true,connector 会保留一个对被包装组件实例的援用,该援用经由历程 getWrappedInstance() 要领取得。默许值为 false。

这个connect组件还干了一件事,状况缓存推断。当store变了的时刻,前后状况推断,假如状况不等,更新组件,而且完成事宜分发。

6. Redux流程梳理

上面讲了大批的函数源码,这么些函数之间的关联:
《Redux源码剖析》
初始化阶段:

  1. createStore建立一个store对象
  2. 将store对象经由历程参数给Provider组件
  3. Provider组件将store经由历程context向子组件通报
  4. Connect组件经由历程context猎取到store,存入本身的state
  5. componentDidMount内里定阅store.subscribe事宜

更新数据阶段:

  1. 用户事宜触发
  2. actionCreator天生action交给dispatch
  3. 实际上交给了封装后的中间层(compose(applyMiddleware(…)))
  4. 要求顺次经由历程每一个中间件,中间件经由历程next举行下一步
  5. 末了一个中间件将action交给store.dispatch
  6. dispatch内部将action交给reducer实行
  7. combineReducer将每一个子reducer实行一遍算出新的state
  8. dispatch内部挪用一切定阅事宜
  9. Connect组件handleChange事宜触发推断新state和旧state是不是===
  10. 而且推断新的state是不是与mapStateToProps shallowEqual
  11. 不等则setState触发更新

7.Redux设想技能

  1. 匿名函数&&闭包运用

    redux中心函数大批运用了匿名函数和闭包来完成数据同享和状况同步。

  2. 函数柯里化运用

    运用函数柯里化s完成参数复用,本质上是下降通用性,进步适用性。

  3. 中心状况读取是拷贝而不是地点

    关于state这类中心状况运用getState()盘算出新的state,而不是直接返回一个state对象。

  4. 观察者定阅者是中心完成

    运用观察者定阅者形式完成数据相应。

  5. context这个api的运用

    日常平凡开辟不常打仗的api完成Provider与Connect通讯。

    原文作者:Aus0049
    原文地址: https://segmentfault.com/a/1190000013763950
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞