redux相关原理解析

redux概念

应用的全部状态以一个对象的形式存储在惟一的store里面,咱们能够把这个存储状态的对象叫状态树,而更改对象树的惟一方式就是emit一个action(这个action是一个描述发生了什么的对象),为了把这个action转换为对象树里面的某个值,就须要提供一个reducer, 因此简单来讲,一个redux程序包括一个store、一个reducer和多个action。node

简单例子, 也是一个基本使用

import { createStore } from 'redux';

export const reducer = (state = {}, action) => {
    switch (action.type) {
        case 'EXAMPLE_TEST': {
            // balabala
            return {text: 'ready go'}
        }
        default:{
            return state;
        }
    }
};

const initialState = {}; // 初始state
const store = createStore(reducer, initialState);

store.dispatch({type: 'EXAMPLE_TEST', data: {}});
store.subscribe(() => {
   const stateObj = store.getState();
   
   if (stateObj.xxx...) {
     // state树更改了, balabala
   }
})
console.log(store.getState())

上面涉及到的相关api逻辑详细解析(node_modules/redux/src..)

createStore

export default function createStore(reducer, preloadedState, enhancer) {
    ...
    
    dispatch({ type: ActionTypes.INIT })
    
    return {
        dispatch,
        subscribe,
        getState,
        replaceReducer,
        [$$observable]: observable
    }
}

从参数能够看出,它是action和reducer的粘合剂,调用一下dispatch生出一个初始化后的对象树(如何生成,见下面的dispatch解释),而后向外曝露出一些功能函数,具体功能函数见下面说明。react

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)
    }
  }

把经过store.subscribe(()=>{})添加的listener放到nextListeners数据里,并返回一个unsubscribe函数,用于取消订阅,这里面屡次出现的函数:ensureCanMutateNextListeners做用就是确保currentListeners和nextListeners不指向同一个数组,把将要执行的listeners和之后要执行的listeners区分开,这样能够避免掉listener正在被执行的时候,忽然取消订阅的问题。webpack

getState
function getState() {
    return currentState
}

做用就是返回当前的状态树对象git

[$$observable]: observable
function observable() {
    const outerSubscribe = subscribe
    return {
        subscribe(observer) {
            if (typeof observer !== 'object') {
                throw new TypeError('Expected the observer to be an object.')
            }

            function observeState() {
            if (observer.next) {
                observer.next(getState())
            }
        }

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

      [$$observable]() {
        return this
      }
    }
  }

把store针对state树变动可observable,以支持redux和RxJS, XStream and Most.js等库的联合使用,须要再详细说明的是$$observable,它来自于symbol-observable这个npm包,这个包的做用就是使一个对象observable, 而且避免掉observable对象调用完error, complete以后重复调用以及unsubscribe以后再调用next, error, complete函数,具体使用见README.md:https://github.com/benlesh/sy...github

dispatch
function dispatch(action) {
    ...... // 省略掉了非关键代码
    
    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,就是把当前state对象和action传递给传给store的reducer做为参数,而后reducer返回的新对象就是新的state树,生成新的states树以后,再执行全部经过store.subscribe函数注册的listener, 注册方式见上面subscribe说明,最后,咱们回到上面createStore遗留的dispatch({ type: ActionTypes.INIT })部分,就很明了了,就是生成初始的state树。web

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

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

这个函数主要是替代creatStore传进来的reducer, 使用场景如:代码逻辑里有代码分割逻辑,须要动态加载reducer; 或者为了实现redux的热加载, 如:npm

if (module.hot) {
    module.hot.accept('../reducers', () => {
        const nextRootReducer = require('../reducers').default;
            store.replaceReducer(nextRootReducer);
        });
    }

其中module.hot是webpack经过HotModuleReplacementPlugin开启了模块热替换以后赋予module的hot属性。编程

上面说了基本的redux使用原理,可是在实际使用中,咱们可能会遇到以下问题

  1. 若是项目很复杂,把全部的action处理都放到一个reducer下,那reducer的case得写多少,有可能会致使单个文件过大,代码很差维护?
  2. redux有没有调试工具,以便于咱们在开发阶段进行调试,若是有,怎么用?
  3. 项目中每一个都少不了网络请求,那么咱们每一个请求都要判断成功,失败,和请求中三个状况的dispatch,那么每一个请求都这么写,显的累赘, 提取公共部分到公共函数就能解决吗?
  4. redux应用到项目中,每一个组件都要subscribe一下,而后判断本身关注的state是否是变了吗?

问题一:

redux提供一个combineReducers工具,容许咱们写多个小reducer,而后经过combineReducers组合成一个root reducer,也就是一个大reducer,源码以下:redux

export default function combineReducers(reducers) {
  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)
  ...
  return function combination(state = {}, action) {
    ...
    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

给combineReducers传入一个reducer对象,而后返回一个reducer函数,参数里面每一个value须要是一个小reducer函数, 这些小的reducer能够分布到不一样的文件里面,那么触类旁通,一些最小的reducer能够经过combineReducers组装成中等的reducer, 而后这些中等的reducer又能够combineReducers成最大的reducer:api

export default combineReducers({
    a: combineReducers(...),
    b: combineReducers(...),
    c: combineReducers(...)
});

问题二:

已经有redux-logger相似的工具了,咱们看它的定义:

// 默认的logger
export const logger: Redux.Middleware;
// 自定义logger
export function createLogger(options?: ReduxLoggerOptions): Redux.Middleware;
// Redux.Middleware
export interface Middleware {
  <S>(api: MiddlewareAPI<S>): (next: Dispatch<S>) => Dispatch<S>;
}

从上面能够看出返回类型都是Redux.Middleware,这代表它是以redux中间件的形式融入到redux中, 这些中间件实现方式都是统一的,接收一个MiddlewareAPI参数,而后返回一个function(这个function接收一个dispatch,而后将dispatch结果返回)。这些中间件注入到redux中须要借助redux提供的applyMiddleware函数:

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    const store = createStore(reducer, preloadedState, enhancer)
    let dispatch = store.dispatch
    let chain = []

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

    return {
      ...store,
      dispatch
    }
  }
}

applyMiddleware接收多个中间件,而后返回一个函数,这个函数参数是一个createStore, 函数逻辑是调用createSotre拿到最基本的store,而后遍历全部的中间件,给其传入MiddlewareAPI,拿到中间件执行结果(返回是一个函数,是Redux.Middleware类型),而后经过compose把全部中间件给柯里化一下,把基本的store.dispatch武装成一个强大的dispatch,这样每次dispatch(action)就会走这些中间件了,也就能够观察action结果变化,其中compose的函数源代码以下:

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)))
}

结合中间件场景:dispatch = compose(...chain)(store.dispatch)能够理解为以传入的中间件顺序倒序为基准,依次将基础的store.dispatch传给各个中间件的返回函数。
综上,中间件落实到项目中使用就是:

import { createStore, applyMiddleware } from 'redux';

const bigCreateStore = applyMiddleware(middleware1, middleware2, ...)(createStore)

bigCreateStore.dispatch(action)

还有不少其余中间件,好比你action是个function的话就要借助redux-thunk中间件。

问题三

咱们把它提取到工具类公共函数中,能够是能够,可是整个项目代码看起来不优雅,咱们能够向redux看齐,以一个中间件的形式落地这个异步请求处理,咱们能够本身实现一个中间件,也可使用现成的, 好比:https://github.com/agraboso/r...

问题四

整个项目中处处充满着subscribe,并且一旦整个对象树更改了,全部组件的subscribe都要执行state变化判断,看是否从新render,这样容易作成无用功,咱们能够借助已有的工具:react-redux, 给咱们解决掉这个判断处理麻烦,使用事例:

import { Provider, connect } from 'react-redux';

class App extends Component {
    render () {
        return (
            <div>app内容</div>
        );
    }
}
export default 

export default class RootComponent extends Component {
    render () {
        return (
            <Provider store={store}>
                connect(state => ({
                    a0: state.a.a0,
                    b0: state.b.b0,
                    c0: state.c.c0
                }), {
                    ...actions
                })(App)
            </Provider>
        )
    }
}

从上面使用上看,主要是Provider Class和connect函数两个东西。经过connect包装以后的Component和Provider经过context进行通讯,分开来看,先说Provider:

Provider.propTypes = {
    store: storeShape.isRequired,
    children: PropTypes.element.isRequired,
}
Provider.childContextTypes = {
    [storeKey]: storeShape.isRequired,
    [subscriptionKey]: subscriptionShape,
}

其源码中能够看出它接收两个prop,一个是store, 一个是children。另外子孙组件想要和Provider通讯,使用store和subscriptionKey能力,就得经过context[storeKey]和context[subscriptionKey]使用,同时子孙组件也要提供可使用context对象的依据(好比能够看connect函数源代码里面的高阶组件):

const contextTypes = {
    [storeKey]: storeShape,
    [subscriptionKey]: subscriptionShape,
  }

再看connect:

function connect(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps,
    {
      pure = true,
      areStatesEqual = strictEqual,
      areOwnPropsEqual = shallowEqual,
      areStatePropsEqual = shallowEqual,
      areMergedPropsEqual = shallowEqual,
      ...extraOptions
    } = {}
  ) {
    const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps')
    const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps')
    const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')

    return connectHOC(selectorFactory, {
      // used in error messages
      methodName: 'connect',

       // used to compute Connect's displayName from the wrapped component's displayName.
      getDisplayName: name => `Connect(${name})`,

      // if mapStateToProps is falsy, the Connect component doesn't subscribe to store state changes
      shouldHandleStateChanges: Boolean(mapStateToProps),

      // passed through to selectorFactory
      initMapStateToProps,
      initMapDispatchToProps,
      initMergeProps,
      pure,
      areStatesEqual,
      areOwnPropsEqual,
      areStatePropsEqual,
      areMergedPropsEqual,

      // any extra options args can override defaults of connect or connectAdvanced
      ...extraOptions
    })
  }

connect的结果就是返回一个高阶组件,这个高阶组件中的两个重要函数就是:

initSelector() {
    const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions)
    this.selector = makeSelectorStateful(sourceSelector, this.store)
    this.selector.run(this.props)
}

initSubscription() {
    if (!shouldHandleStateChanges) return
    
    const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey]
    this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this))
    this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription)
}

initSelector判断state状态树或者actions是否更改,来决定被包裹组件的新props,关键代码见:

const nextProps = sourceSelector(store.getState(), props)
    if (nextProps !== selector.props || selector.error)  {
        selector.shouldComponentUpdate = true
        selector.props = nextProps
        selector.error = null
}
...
render() {
    ...
    return createElement(WrappedComponent, this.addExtraProps(selector.props))
    ...
}

initSubscription的主要做用就是监听store的state对象树是否变化,若是变化,就执行以下代码:

onStateChange() {
    this.selector.run(this.props) // 获取最新的props

    if (!this.selector.shouldComponentUpdate) {
      this.notifyNestedSubs()
    } else {
      this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate
      this.setState(dummyState)
    }
  }

也就是说先获取最新的props, 这个props里面包括states,而后判断是否须要从新渲染,若是须要,则触发setState,引发高阶组件react生命周期的执行,再看this.notifyNestedSubs()这句,若是被包裹组件订阅了state变化,那么会依次执行全部的listener,被包裹组件若是若是想订阅,须要借助context,由于高阶组件里面定义了以下代码:

const childContextTypes = {
    [subscriptionKey]: subscriptionShape,
}

以上就是问题四的相关内容,使用过程当中须要注意的是connect函数的第二个参数也就是mapDispatchToProps能够是个actions对象,也能够是一个方法,我习惯用对象,写起来方便,其实底层最终都是同样的,只是若是用的是对象的话,react-redux内部会调用

bindActionCreators(mapDispatchToProps, dispatch)

把对象处理成新对象,这个新对象的values都是

(...args) => dispatch(actionCreator(...args))

因此就能够知足咱们项目中this.props.xxx(data)来dispatch一个action。

react-redux中高阶组件是如何判断props更改,而后决定是否从新render的?

高阶组件的props来源两部分,分别是state对橡树里面的对象和actions, 详细看react-redux包下面的src/connect/selectorFactory.js代码片断

function handleSubsequentCalls(nextState, nextOwnProps) {
    const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps)
    const stateChanged = !areStatesEqual(nextState, state)
    state = nextState
    ownProps = nextOwnProps

    if (propsChanged && stateChanged) return handleNewPropsAndNewState()
    if (propsChanged) return handleNewProps()
    if (stateChanged) return handleNewState()
    return mergedProps
  }

总结

redux源代码处处充满着函数式编程以及调用compose柯里化,源代码量很少,简短干净,看了很舒服,上面的整个分析一句的redux版本是3.7.2,react-redux版本5.1.1, 若是后续版本相关逻辑更改了请见谅。

相关文章
相关标签/搜索