redux源码分析
前言
Redux是React生态中一个极其重要的状态管理库,在官网的介绍中,他能使得应用在不同的运行环境下表现出一致性,并使得应用容易被测试。另外通过time traveling debugger
能带来更好的开发体验。(但是写起来比起mobx
还是略显啰嗦)
redux中的三个概念
action
action是一个plain object(即action的prototype是object),如果用TypeScript的接口来定义action,它是这样子的
interface Action<T = any> {
type: T;
}
interface AnyAction extends Action {
// Allows any extra properties to be defined in an action.
[extraProps: string]: any;
}
Action至少有一个type属性,这个type属性显然是用来表示这个action的类型的。然后可以包含其它属性,下面是一个action的例子
const ADD_TODO = 'ADD_TODO'
{
type: ADD_TODO,
text: 'Build my first Redux app'
}
那么就有一类函数叫做action creator
,它的类型定义是是这样的。
interface ActionCreator<A> {
(...args: any[]): A;
}
这类函数接受若干个参数,然后返回一个action object。一个action creator的例子如下
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
这个action是由reducer接收的。
reducer
reducer也是一类函数,它接受当前的state和收到的action作为参数,返回一个新的state,reducer被要求是纯函数(即无副作用的函数),reducer的TypeScript定义如下
type Reducer<S = any, A extends Action = AnyAction> = (state: S | undefined, action: A) => S
在reducer中,通过判断action.type来得知收到的是何种action,以决定如何处理 一个reducer的例子如下
unction todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
case ADD_TODO:
return Object.assign({}, state, {
todos: [
...state.todos,
{
text: action.text,
completed: false
}
]
})
default:
return state
}
}
注意,当reducer第一次运行,state为undefined,action为 { type: '@@redux/INIT' }
,这个时候要返回初始的initialState,另外,当前reducer不需要因当前action改变state时,也需要返回当前的state。
想象一下,当我们需要处理的action数量变得无限复杂时,把所有类型的action放在一个reducer里处理显然是不合适的。redux有一个combineReducer工具方法,把多个reducer合并成一个
import { combineReducers } from 'redux'
import {
ADD_TODO,
TOGGLE_TODO,
SET_VISIBILITY_FILTER,
VisibilityFilters
} from './actions'
const { SHOW_ALL } = VisibilityFilters
function visibilityFilter(state = SHOW_ALL, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return action.filter
default:
return state
}
}
function todos(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [
...state,
{
text: action.text,
completed: false
}
]
case TOGGLE_TODO:
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: !todo.completed
})
}
return todo
})
default:
return state
}
}
const todoApp = combineReducers({
visibilityFilter,
todos
})
export default todoApp
todoApp是一个reducer,他返回的state类型是这样的。
interface TodoApp {
visibilityFilter: 'SHOW_ALL' | 'SHOW_COMPLETED', | 'SHOW_ACTIVE'
todos: Array<{ text: string; completed: boolean; }>
}
每一次dispatch action的时候,todoApp这个reducer会将action传给每一个子reducer,并把新的state更新到对应的子字段中
store
store是一个对象,由createStore创建,他有这些功能
- 存储state
- 允许通过他的getState方法获取当前state
- 通过
dispatch(action)
来派发一个action获得新的state - 通过
subscribe(listener)
方法注listener监听state的变化和action的接收 - 通过
subscribe(listener)
返回的方法取消监听。
interface Store<S = any, A extends Action = AnyAction> {
dispatch: Dispatch<A>;
getState(): S;
subscribe(listener: () => void): Unsubscribe;
replaceReducer(nextReducer: Reducer<S, A>): void;
}
创建一个store非常简单,将一个reducer传入createStore即可,他有个可选的参数,表示初始的state,在服务端渲染的应用中可以将获取的到的数据传入这个第二个参数。
combineReducer的实现
下面我会直接注释在代码里。
function combineReducers(reducers) {
var reducerKeys = Object.keys(reducers)
// 获得每个reducer的key
var finalReducers = {}
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i]
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
// 过滤掉reducer中非function的字段,避免调用了一个不是函数的玩意
// 到这里finalReducers里的所有value肯定都是function
var finalReducerKeys = Object.keys(finalReducers)
var sanityError
try {
assertReducerSanity(finalReducers)
// 这个函数判断reducer有没有initalState,如果有将会记录这个错误并在下面的代码中抛出异常
} catch (e) {
sanityError = e
}
// 最终返回的reducer
return function combination(state = {}, action) {
if (sanityError) {
throw sanityError
}
// 非production模式下判断
// 1. reducer是否为空对象({})
// 2. action是否为plain object
// 如果满足任意其1,抛出异常
if (process.env.NODE_ENV !== 'production') {
var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action)
if (warningMessage) {
warning(warningMessage)
}
}
// 记录reducer返回的state是否改变
var hasChanged = false
var nextState = {}
// 传入action,调用每个子reducer获得子state并更新到父state上
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
// 判断当前key的子state与之前的子state是否strict equal,如果否则说明父state改变了
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
// 返回当前state或者新的state
// 这样做的原因是,使用redux的库(如react-redux)会通过strict equal(即 === 运算符)
// 来判断state是否变化,因为参会不同的引用会导致外部认为state发生了变化,即使他们deepEqual
return hasChanged ? nextState : state
}
}
createStore实现
// createStore的三个参数,第一个是reducer,第二个是初始的state,第三个enhancer必须通过applyMiddleware来创建
function createStore(reducer, initialState, enhancer) {
// 这种情况是仅仅传入了reducer和enhancer的情况,需要调整一下参数位置
if (typeof initialState === 'function' && typeof enhancer === 'undefined') {
enhancer = initialState
initialState = undefined
}
// enhancer存在时,通过enhancer去创建store
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, initialState)
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
var currentReducer = reducer
var currentState = initialState
var currentListeners = []
var nextListeners = currentisteners
var isDispatching = false
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
/**
* 获取state
*
* @returns {any} 返回当前state
*/
function getState() {
return currentState
}
/**
* 添加一个listener,每当action被dispatch的时候,它将被调用。
* Adds a change listener. It will be called any time an action is dispatched,
* 有些state会隐性地被改变
* 在回调用调用`getState()` 以获得当前的state
* call `getState()` to read the current state tree inside the callback.
*
* 你可能会在listener中dispatch action,请注意下列事项
*
* 1. 所有的订阅都在每次调用`dispatch()`前留下快照,在listener中增加或取消订阅不会再当前的
* dispatch流程中有效,只有下次dispatch才会有效
*
* 2. The listener should not expect to see all state changes, as the state
* might have been updated multiple times during a nested `dispatch()` before
* the listener is called. It is, however, guaranteed that all subscribers
* registered before the `dispatch()` started will be called with the latest
* state by the time it exits.
*
* @param {Function} listener A callback to be invoked on every dispatch.
* @returns {Function} A function to remove this change listener.
*/
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.')
}
var isSubscribed = true
// 判断当前的listeners是否与下一个listeners相等,相等说明listener没有被改变过,如果是从当前listeners拷贝出新的listenrs数组
ensureCanMutateNextListeners()
nextListeners.push(listener)
// 取消订阅的函数
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
ensureCanMutateNextListeners()
var index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
/**
* Dispatches an action. It is the only way to trigger a state change.
*
* The `reducer` function, used to create the store, will be called with the
* current state tree and the given `action`. Its return value will
* be considered the **next** state of the tree, and the change listeners
* will be notified.
*
* The base implementation only supports plain object actions. If you want to
* dispatch a Promise, an Observable, a thunk, or something else, you need to
* wrap your store creating function into the corresponding middleware. For
* example, see the documentation for the `redux-thunk` package. Even the
* middleware will eventually dispatch plain object actions using this method.
*
* @param {Object} action A plain object representing “what changed”. It is
* a good idea to keep actions serializable so you can record and replay user
* sessions, or use the time travelling `redux-devtools`. An action must have
* a `type` property which may not be `undefined`. It is a good idea to use
* string constants for action types.
*
* @returns {Object} For convenience, the same action object you dispatched.
*
* Note that, if you use a custom middleware, it may wrap `dispatch()` to
* return something else (for example, a Promise you can await).
*/
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?'
)
}
// 这种情况只可能reducer是异步的才会出现
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// 将nextListeners与currentListeners合并
var listeners = currentListeners = nextListeners
// 依次调用每个listener
for (var i = 0; i < listeners.length; i++) {
listeners[i]()
}
return action
}
/**
* 替换reducer,在某些情况下可能需要吧
* Replaces the reducer currently used by the store to calculate the state.
*
* You might need this if your app implements code splitting and you want to
* load some of the reducers dynamically. You might also need this if you
* implement a hot reloading mechanism for Redux.
*
* @param {Function} nextReducer The reducer for the store to use instead.
* @returns {void}
*/
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.INIT })
替换reducer后重新初始化store
}
/**
* Interoperability point for observable/reactive libraries.
* @returns {observable} A minimal observable of state changes.
* For more information, see the observable proposal:
* https://github.com/zenparsing/es-observable
*/
function observable() {
var outerSubscribe = subscribe
return {
/**
* The minimal observable subscription method.
* @param {Object} observer Any object that can be used as an observer.
* The observer object should have a `next` method.
* @returns {subscription} An object with an `unsubscribe` method that can
* be used to unsubscribe the observable from the store, and prevent further
* emission of values from the observable.
*/
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
}
}
}
// dispatch一个初始的action来获取reducer的initial state
// When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
applyMiddleware的实现
//
function compose(...funcs)
if (funcs.length === 0) {
return arg => arg
} else {
const last = funcs[funcs.length - 1]
const rest = funcs.slice(0, -1)
return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}
}
function applyMiddleware(...middlewares) {
// 返回上面createStore中的enhancer
return (createStore) => (reducer, initialState, enhancer) => {
var store = createStore(reducer, initialState, enhancer)
var dispatch = store.dispatch
var chain = []
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
// chain中的每个函数执行后都会调用一个middleware
// 每个middleware可能会修饰dispatch函数(比如打log等)
// 被修饰过的dispatch函数会作为参数传给下一个middleware
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}