应用的全部状态以一个对象的形式存储在惟一的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())
export default function createStore(reducer, preloadedState, enhancer) { ... dispatch({ type: ActionTypes.INIT }) return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable } }
从参数能够看出,它是action和reducer的粘合剂,调用一下dispatch生出一个初始化后的对象树(如何生成,见下面的dispatch解释),而后向外曝露出一些功能函数,具体功能函数见下面说明。react
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
function getState() { return currentState }
做用就是返回当前的状态树对象git
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
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
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提供一个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。
高阶组件的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, 若是后续版本相关逻辑更改了请见谅。