redux源码

Redux实现思想:

针对React通过setState更新组件的实现,Redux在React组件中的交互意义也就是通过action更新state。与常规事件系统不同的是,常规事件系统通过事件名挂载和触发该事件名下的绑定函数;Redux得益于React的实现机理,绑定函数最主要的工作内容重新渲染页面这一项,可以借由React自身去判断该重新渲染页面的那一部分,所以Redux最主要的目的是通过action向state传递数据,而不是判断该触发哪一个绑定函数。在Redux的实际代码实现中,通过dispatch方法派发的事件也将触发通过subscribe监听的所有listener绑定函数执行。

为着实现state更新的目的,以及面向用户的动态配置,Redux在使用createStore构建事件系统的时候,需要由用户传入reducer函数。该函数接收prevState、action两个参数,其意义是通过旧有的state、以及dispatch函数传入的action,构建新的state,最终对React页面视图产生影响。

出于模块分离、便于维护的目的,Redux提供bindActionCreators函数构建便捷的事件触发机制。在其返回函数或方法内部,起先借由bindActionCreators的传参函数生成action,然后再调用dispatch直接触发事件。这样处理,简化了dispatch函数派发事件时需要预知action的过程,职责也更为单一。其意义类同在传统的事件系统中,使用on方法绑定函数后,直接返回解绑函数,不提供remove方法。

为着处理state数据极为复杂、且操作颗粒度更为细腻的场景,Redux提供combineReducers方法,用于将多个reducer复合形成一个reducer,每个reducer用于更新state下的同名属性。 

1.index.js

内核为事件系统,可用于react之外的前端框架。

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 (
    typeof process !== 'undefined' &&
    process.env.NODE_ENV !== 'production' &&
    typeof isCrushed.name === 'string' &&
    isCrushed.name !== 'isCrushed'
) {
    warning(
        'You are currently using minified code outside of NODE_ENV === \'production\'. ' +
        'This means that you are running a slower development build of Redux. ' +
        'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' +
        'or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' +
        'to ensure you have the correct code for your production build.'
    )
}

export {
    // 生成store实例,提供dispatch派发事件、subscribe监听事件、getState方法
    createStore,
    // 以键值对形式合并多个reducer,同等作用于相同键值对的state
    combineReducers,
    // 生成action的同时,执行dispatch触发事件,调用listener回调
    bindActionCreators,
    // 挂载中间件,用于装饰dispatch方法,完成打印action和state等功能
    applyMiddleware,
    // applyMiddleware方法内部使用,外部不详
    compose
}

2.createStore.js

生成store实例,提供dispatch派发事件、subscribe监听事件、getState方法、replaceReducer更换reducer方法、observable将当前state作为参数传入绑定函数listener方法。

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

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

// 参数reducer处理器,接受action作为参数,定义根据action更改state,在dispatch方法内执行
// 使用闭包存储currentState(当前state)、currentReducer(state处理机)、currentListeners(绑定函数集)
export default function createStore(reducer, preloadedState, enhancer) {
  	if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    	enhancer = preloadedState
    	preloadedState = undefined
  	}

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

	var currentReducer = reducer
	var currentState = preloadedState
	var currentListeners = []
	var nextListeners = currentListeners
	var isDispatching = false

	function ensureCanMutateNextListeners() {
		if (nextListeners === currentListeners) {
		  	nextListeners = currentListeners.slice()
		}
	}

    // store.getState获取当前最新的state,无论派发dispatch方法执行了多少遍
    function getState() {
        return currentState
    }

    // 订阅,雷同jquery事件系统的on方法,返回remove解绑函数又类同react中调用putListener后返回remove
    // 与常规事件系统不同的是,redux没有事件名,绑定的listener函数不根据事件名触发,而是dispatch方法执行时触发
    //      listener内部可以调用getState方法获取redux当前的state,设置业务逻辑
    //      这样处理使得redux方案和常规事件系统的静态传参迥异,getState方法获取到的永远是最新的state
    function subscribe(listener) {
        if (typeof listener !== 'function') {
            throw new Error('Expected listener to be a function.')
        }

        var isSubscribed = true

        ensureCanMutateNextListeners()
        nextListeners.push(listener)

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

            isSubscribed = false

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

    // 通过currentReducer获得最新的state后,触发subscribe挂载的所有listener函数执行
    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
        }

        var listeners = currentListeners = nextListeners
        for (var i = 0; i < listeners.length; i++) {
            var listener = listeners[i]
            listener()
        }

        return action
    }

    // 替换当前的ruducer,触发ActionTypes.INIT获取初始的state后,执行绑定函数listener
    function replaceReducer(nextReducer) {
        if (typeof nextReducer !== 'function') {
            throw new Error('Expected the nextReducer to be a function.')
        }

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

	// subscribe方法中,绑定函数linsenter需要手动调用store.getState()获取当前的state
	// 通过observable().subscribe(observer)挂载的函数listener,直接传入当前的state作为参数
	// 		返回值为带有解绑函数unsubscribe的对象
	function observable() {
	var 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()
				var unsubscribe = outerSubscribe(observeState)
				return { unsubscribe }
			},

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

  	// 触发ActionTypes.INIT获取初始的state,执行按action为INIT绑定的函数listener
  	dispatch({ type: ActionTypes.INIT })

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

3.combineReducers.js

以键值对形式合并多个reducer,同等作用于相同键值对的state。

import { ActionTypes } from './createStore'
import isPlainObject from 'lodash/isPlainObject'
import warning from './utils/warning'

var NODE_ENV = typeof process !== 'undefined' ? process.env.NODE_ENV : 'development'

// 复合reducers中每个子reducer返回state为undefined时的错误文案
function getUndefinedStateErrorMessage(key, action) {
    var actionType = action && action.type
    var actionName = actionType && `"${actionType.toString()}"` || 'an action'

    return (
        `Given action ${actionName}, reducer "${key}" returned undefined. ` +
        `To ignore an action, you must explicitly return the previous state.`
    )
}

// 开发环境,复合reducers中每个子reducer不存在,或者传入每个子reducer的state不是普通对象
// 或者传入每个子reducer的state对象的属性没有对应子reducer时,警告提示
function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) {
    var reducerKeys = Object.keys(reducers)
    var argumentName = action && action.type === ActionTypes.INIT ?
        'preloadedState argument passed to createStore' :
        'previous state received by the reducer'

    if (reducerKeys.length === 0) {
        return (
            'Store does not have a valid reducer. Make sure the argument passed ' +
            'to combineReducers is an object whose values are reducers.'
        )
    }

    if (!isPlainObject(inputState)) {
        return (
            `The ${argumentName} has unexpected type of "` +
            ({}).toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
            `". Expected argument to be an object with the following ` +
            `keys: "${reducerKeys.join('", "')}"`
        )
    }

    var unexpectedKeys = Object.keys(inputState).filter(key =>
        !reducers.hasOwnProperty(key) &&
        !unexpectedKeyCache[key]
    )

    unexpectedKeys.forEach(key => {
        unexpectedKeyCache[key] = true
    })

    if (unexpectedKeys.length > 0) {
        return (
            `Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
            `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
            `Expected to find one of the known reducer keys instead: ` +
            `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
        )
    }
}

// action.type为初始化ActionTypes.INIT或带*,通过复合reducer的首参undefined返回initialState,不是则报错
function assertReducerSanity(reducers) {
    Object.keys(reducers).forEach(key => {
        var reducer = reducers[key]
        var initialState = reducer(undefined, { type: ActionTypes.INIT })

        if (typeof initialState === 'undefined') {
            throw new Error(
                `Reducer "${key}" returned undefined during initialization. ` +
                `If the state passed to the reducer is undefined, you must ` +
                `explicitly return the initial state. The initial state may ` +
                `not be undefined.`
            )
        }

        var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.')
        if (typeof reducer(undefined, { type }) === 'undefined') {
            throw new Error(
                `Reducer "${key}" returned undefined when probed with a random type. ` +
                `Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
                `namespace. They are considered private. Instead, you must return the ` +
                `current state for any unknown actions, unless it is undefined, ` +
                `in which case you must return the initial state, regardless of the ` +
                `action type. The initial state may not be undefined.`
            )
        }
    })
}

// 复合子reducer,子reducer的键值和传入reducer的参数state的属性一一对应,修改state的该属性后返回新的state
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 (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 (NODE_ENV !== 'production') {
        var unexpectedKeyCache = {}
    }

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

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

        if (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
    }
}

4.applyMiddleware.js、compose.js

挂载中间件,用于装饰dispatch方法,完成打印action和state等功能。

// applyMiddleware
import compose from './compose'

// 使用中间件装饰dispach方法
export default function applyMiddleware(...middlewares) {
    return (createStore) => (reducer, preloadedState, enhancer) => {
        var store = createStore(reducer, preloadedState, enhancer)
        var dispatch = store.dispatch
        var chain = []

        var middlewareAPI = {
            getState: store.getState,
            dispatch: (action) => dispatch(action)
        }
        // middleware中间件接受参数为包含getState、dispatch的对象,返回函数,该函数接受上一个中间件返回值作为参数
        chain = middlewares.map(middleware => middleware(middlewareAPI))
        dispatch = compose(...chain)(store.dispatch)

        return {
            ...store,
            dispatch
        }
    }
}



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

	// 只有一个中间件时,中间件先接受getState、dispatch作为参数,返回函数,该函数接受dispatch作为函数并装饰,随后又返回函数,接受action作为参数
	if (funcs.length === 1) {
		return funcs[0]
	}

	const last = funcs[funcs.length - 1]
	const rest = funcs.slice(0, -1)

	// 数组的reduceRight方法,自右向左遍历执行首参函数(该函数首参为前一次函数执行的结果),次参为初始化传入首参函数的composed
	// reduce为自左向右遍历数组,调用首参函数逐次修改最终结果值并返回,供下一个首参函数使用
	// redux中使用为middleware中间件数组作为参数funcs,dispatch作为参数args,最末一个中间件装饰dispacth方法后,交给上一个中间件装饰
	// 		具体功能实现为打印接收到的action和state变化,最终返回值接受action作为参数,完成派发事件的目的
	return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}

5.bindActionCreators.js

使用actionCreator函数生成普通对象action,并使用dispatch派发事件,currentReducer接收到action后,更改当前的state,最终触发绑定函数listener的执行。意义是传入参数,触发事件。

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

export default function bindActionCreators(actionCreators, dispatch) {
    if (typeof actionCreators === 'function') {
        // 生成action并派发
        return bindActionCreator(actionCreators, dispatch)
    }

    if (typeof actionCreators !== 'object' || actionCreators === null) {
        throw new Error(
            `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
            `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
        )
    }

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

相关推荐