weekly
weekly copied to clipboard
2018/12/10 - redux 源码分析
redux 源码分析
背景
在之前的文章Redux从入门到实践当中对redux的使用进行了说明,这次就来看下它的源码,从而进一步的熟悉它。
构建
相关git地址
git clone https://github.com/reduxjs/redux.git
构建文档是CONTRBUTING.md
package.json
"main": "lib/redux.js",
// ...
"scripts": {
"clean": "rimraf lib dist es coverage",
"format": "prettier --write \"{src,test}/**/*.{js,ts}\" index.d.ts \"**/*.md\"",
"format:check": "prettier --list-different \"{src,test}/**/*.{js,ts}\" index.d.ts \"**/*.md\"",
"lint": "eslint src test",
"pretest": "npm run build",
"test": "jest",
"test:watch": "npm test -- --watch",
"test:cov": "npm test -- --coverage",
"build": "rollup -c",
"prepare": "npm run clean && npm run format:check && npm run lint && npm test",
"examples:lint": "eslint examples",
"examples:test": "cross-env CI=true babel-node examples/testAll.js"
}
从package.json
当中可以看到redux
的入口文件是lib/redux.js
,这个文件是通过打包出来的。那我们看下打包配置文件rollup.config
{
input: 'src/index.js',
output: { file: 'lib/redux.js', format: 'cjs', indent: false },
external: [
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {})
],
plugins: [babel()]
},
// ...省略
可以看到入口文件应该是src/index.js
我们来看下src/index.js
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'
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'
/*
* This is a dummy function to check if the function name has been altered by minification.
* If the function has been minified and NODE_ENV !== 'production', warn the user.
*/
// 是否压缩代码,如果运行环境在非生成环境但是代码被压缩了,警告用户
function isCrushed() {}
// 判断环境是否是生成环境,如果是生成环境使用此代码就给出警告提示
if (
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 setting mode to production in webpack (https://webpack.js.org/concepts/mode/) ' +
'to ensure you have the correct code for your production build.'
)
}
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes
}
src/index.js
主要是将方法暴露出来,给使用者使用
-
createStore
用于创建store -
combineReducers
用于组合成rootReducers,因为在外部初始化store时,只能传入一个reducers -
bindActionCreators
组装了dispatch方法 -
applyMiddleware
合并多个中间件 -
compose
将中间件(middleware)和增强器(enhancer)合并传入到createStore
中
combineReducers
src/combineReducers.js
export default function combineReducers(reducers) {
// 遍历出reducers的对象名称
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
// 遍历reducers名称
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (process.env.NODE_ENV !== 'production') {
// 如果reducer对应的值是 undefined 输出警告日志
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
// 当这个reducer是函数 则加入到finalReducers对象中
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
// 读取出过滤后的reducers
const finalReducerKeys = Object.keys(finalReducers)
let unexpectedKeyCache
// 开发环境将unexpectedKeyCache设置为空对象
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let shapeAssertionError
try {
// 检查各个reducers是否考虑过defualt的情况,不能返回undefined
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
// combineRducers返回的是一个方法,dispatch最后执行的方法
return function combination(state = {}, action) {
// 如果reducer检查出有问题就会抛出异常
if (shapeAssertionError) {
throw shapeAssertionError
}
// 开发者环境下
if (process.env.NODE_ENV !== 'production') {
// 对过滤后的reducers和初始化的state进行检查
const warningMessage = getUnexpectedStateShapeWarningMessage(
state,
finalReducers,
action,
unexpectedKeyCache
)
// 如果有问题就会输出
if (warningMessage) {
warning(warningMessage)
}
}
let hasChanged = false
const nextState = {}
// 遍历过滤后的reducers
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
// 根据key取出对应reducer
const reducer = finalReducers[key]
// 根据key将state对应的值取出
const previousStateForKey = state[key]
// 执行我们reducer的方法,nextStateForKey就是根据actionType返回的state
const nextStateForKey = reducer(previousStateForKey, action)
// 检查nextStateForKey是否是undefined
if (typeof nextStateForKey === 'undefined') {
// 如果undefined就报错
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
// 将合并后的state赋值到nextState当中
nextState[key] = nextStateForKey
// 如果state的值改变过 则hasChanged置为true
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
// 如果改变过 则返回新的state不然则是原有的state({})
// 如果state不传值就是 空的对象{}
return hasChanged ? nextState : state
}
}
combineReducers
方法会先对传入的reducers进行校验,reducer的类型只能是function
,最后返回的是个方法,这个方法很关键,因为在disptach时,最后执行的就是这个方法。这个方法有2个参数state
和action
,方法内会根据传入的action返回state,最后会比较新旧的state,如果不相等,则会返回新的state,如果相等会返回新的state。
那么如果我们直接对store的state进行操作而不是通过dispatch会发生呢,比如说我们这样
const state = store.getState();
state.name = 'baifann';
我们看一下combineReducers
中的getUnexpectedStateShapeWarningMessage
这个方法,它会检查store中初始化的state的key有没有在各个子reducer当中,如果没有就会报错。
/**
* @inputState 初始化的state
* @reducers 已经过过滤的reducers
* @action 随着combinRecuers传入的action
* @unexpectedKeyCache 开发者环境是一个空的对象,生成环境是undefined
*/
/**
* 检查合法的reducers是否存在
*
*
*/
function getUnexpectedStateShapeWarningMessage(
inputState,
reducers,
action,
unexpectedKeyCache
) {
// 将过滤的reducers的名取出
const reducerKeys = Object.keys(reducers)
const argumentName =
// 如果这个action的type是预制的ActionTypes.INIT
// argumentName就是preloadedState argument passed to createStore
// 不然是previous state received by the reducer
action && action.type === ActionTypes.INIT
? 'preloadedState argument passed to createStore'
: 'previous state received by the reducer'
// 如果过滤后的reducer长度为0
// 则返回字符串告知没有一个合法的reducer(reducer必须是function类型)
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.'
)
}
// 判断输入的state是否是obj对象
// 如果不是 则返回字符串告知inputState不合法
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('", "')}"`
)
}
// 传入的state进行遍历
// 如果state的对象名不包含在reducer中 并且不包含在unexpectedKeyCache对象中
// unexpectedKeyCache在开发者环境是一个空的对象 因此只要state的对象名不包含在reducer中,这个key就会
// 保存到 unexpectedKeys 当中
const unexpectedKeys = Object.keys(inputState).filter(
key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
)
// 将inputState的key全部设置为true
unexpectedKeys.forEach(key => {
unexpectedKeyCache[key] = true
})
// 如果这个action的type是定义的定义中的ActionTypes.REPLACE 就返回不执行
if (action && action.type === ActionTypes.REPLACE) return
// 如果unexpectedKeys中有值,则发出警告
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.`
)
}
}
compose
compose会返回一个方法,这个方法可以将传入的方法依次执行
export default function compose(...funcs) {
// 如果函数方法为0 则
if (funcs.length === 0) {
// 会将参数直接返回
return arg => arg
}
// 如果只传入一个方法则会返回这个方法
if (funcs.length === 1) {
return funcs[0]
}
// a为上一次回调函数返回的值 b为当前值
// 效果就是不断执行数组中的方法最后返回时一个函数
// 这个方法可以将所有数组中的方法执行
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
createStore
我们接下来看下createStore.js
这个文件,它只暴露出了createStore的方法,在createStore
中,初始化了一些参数,同时返回了一个store,store中包括了dispatch
,subscribe
,getState
,replaceReducer
,[$$observable]: observable
import $$observable from 'symbol-observable'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
export default function createStore(reducer, preloadedState, enhancer) {
// 如果初始化的state是一个方法并且enhancer也是方法就会报错
// 如果enhancer是方法如果第4个参数是方法就会报错
if (
(typeof preloadedState === 'function' && typeof enhancer === 'function') ||
(typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
throw new Error(
'It looks like you are passing several store enhancers to ' +
'createStore(). This is not supported. Instead, compose them ' +
'together to a single function'
)
}
// 如果初始化的state是方法,enhancer的参数为undefined
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
// enhancer赋值初始话的stae
enhancer = preloadedState
// preloadedState赋值为undefined
preloadedState = undefined
// 这里是一个兼容2个参数的处理,当参数仅为2个 第二个参数为enhcaner时的处理
}
// 如果enhancer 不是undefined
if (typeof enhancer !== 'undefined') {
// 如果enhancer不是方法会报错
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
// 返回enhancer的方法
return enhancer(createStore)(reducer, preloadedState)
}
// 如果reducer不是方法 则报错
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
// rootReducer赋值到currentReducer当中 实际是一个函数
let currentReducer = reducer
// 当前store中的state 默认是初始化的state
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
// 浅拷贝一个数组 虽然是浅拷贝 但是currentListener不会被nextListener改变
nextListeners = currentListeners.slice()
}
}
function getState() {
// 省略代码...
}
function subscribe(listener) {
// 省略代码...
}
function dispatch(action) {
// 省略代码...
}
function replaceReducer(nextReducer) {
// 省略代码...
}
function observable() {
// 省略代码...
}
// 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 来初始化store中的state
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
看完之后,我们可能在这个地方有一点疑惑,就是这里
// 如果enhancer 不是undefined
if (typeof enhancer !== 'undefined') {
// 如果enhancer不是方法会报错
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
// 返回enhancer的方法
return enhancer(createStore)(reducer, preloadedState)
}
这个返回的是什么呢,我们知道applyMiddleware
返回的其实就是enhancer,那我们结合在一起看一下
applyMiddleware
import compose from './compose'
/**
* 创建一个store的增强器,使用中间件来包装dispath方法,这对于各种任务来说都很方便
* 比如以简洁的方式进行异步操作,或记录每个操作有效负载
*
* 查看`redux-thunk`包,这是一个中间件的例子
*
* 因为中间件可能是异步的,所以应该是对个enhancer传参
*
* 每一个中间件都要提供dispatch和getstate两个方法作参数
*
*/
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
// 创建一个store ...args为reducer, preloadedState
const store = createStore(...args)
// 默认定义disptach方法,是一个抛出的报错
let dispatch = () => {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
)
}
// 中间件的的参数
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
// 将所有的中间件遍历,将参数传入到中间件函数中,返回一个中间件函数的数组
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// dispatch做了包装,会在dispatch的同时同时将中间件的方法也返回
dispatch = compose(...chain)(store.dispatch)
// 返回store中的属性以及新的dispatch方法
return {
...store,
dispatch
}
}
}
如果直接返回了enhancer
那么返回的其实也是store
,但是这个store
中的dispatch
被包装过,当dispatch
被执行时,会将所有中间件也依次执行。
接下来分析一下createStore
中的方法
- getState
- subscribe
- dispatch
- replaceReducer
- observable
getState
很简单,就是返回currentState
/**
* Reads the state tree managed by the store.
*
* @returns {any} The current state tree of your application.
*/
function getState() {
if (isDispatching) {
throw new Error(
'You may not call store.getState() while the reducer is executing. ' +
'The reducer has already received the state as an argument. ' +
'Pass it down from the top reducer instead of reading it from the store.'
)
}
// 返回state
return currentState
}
subscribe
这是将一个回调加入到监听数组当中,同时,它会返回一个注销监听的方法。
function subscribe(listener) {
// listener必须是一个方法
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
if (isDispatching) {
throw new Error(
'You may not call store.subscribe() while the reducer is executing. ' +
'If you would like to be notified after the store has been updated, subscribe from a ' +
'component and invoke store.getState() in the callback to access the latest state. ' +
'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
)
}
let isSubscribed = true
ensureCanMutateNextListeners()
// 把listenr加入到nextListeners的数组当中
nextListeners.push(listener)
// 解除观察
return function unsubscribe() {
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing. ' +
'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
)
}
isSubscribed = false
// 这里做了个拷贝 做的所有操作不影响currentListener
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
// 在nextListener将它去除
nextListeners.splice(index, 1)
}
}
dispatch
dispatch首先会检查参数,随后会执行currentReducer(currentState, action)
,而这个方法实际就是combineReducers
的
function dispatch(action) {
// 如果dispatch的参数不是action
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
// action必须得有type属性,如果没有会报错
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
// 执行reducer 遍历过滤后的reducer,随后依次赋值到state当中
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// 获取当前的监听器
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
// 依次执行监听器回调
listener()
}
// dispatch默认返回action
return action
}
replaceReducer
/**
* 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}
*/
/**
* 替换reducer
*
* 动态替换原有的reducer
*/
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
// 将reducer赋值
currentReducer = nextReducer
// 发送一个dispatch 随后重置store
dispatch({ type: ActionTypes.REPLACE })
}
observable
这里不谈太多observable
这里有个使用例子
const state$ = store[Symbol.observable]();
const subscription = state$.subscribe({
next: function(x) {
console.log(x);
}
});
subscription.unsubscribe();
/**
* Interoperability point for observable/reactive libraries.
* @returns {observable} A minimal observable of state changes.
* For more information, see the observable proposal:
* https://github.com/tc39/proposal-observable
*/
function observable() {
const 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.
*/
/**
* @param {Object} 任何对象都可以当做observer
* observer应该有一个`next`方法
* @returns {subscription} 一个对象,它有`unsubscribe`方法能够
* 用来从store中unsubscribe observable
*/
subscribe(observer) {
// observer必须是一个非空的object
if (typeof observer !== 'object' || observer === null) {
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
}
}
}
bindActionCreators
在讲这个方法前,先看下文档对它的使用说明
Example
TodoActionCreators.js
我们在文件中创建了2个普通的action。
export function addTodo(text) {
return {
type: 'ADD_TODO',
text
}
}
export function removeTodo(id) {
return {
type: 'REMOVE_TODO',
id
}
}
SomeComponent.js
import { Component } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import * as TodoActionCreators from './TodoActionCreators'
console.log(TodoActionCreators)
// {
// addTodo: Function,
// removeTodo: Function
// }
class TodoListContainer extends Component {
constructor(props) {
super(props)
const { dispatch } = props
// Here's a good use case for bindActionCreators:
// You want a child component to be completely unaware of Redux.
// We create bound versions of these functions now so we can
// pass them down to our child later.
this.boundActionCreators = bindActionCreators(TodoActionCreators, dispatch)
console.log(this.boundActionCreators)
// {
// addTodo: Function,
// removeTodo: Function
// }
}
componentDidMount() {
// Injected by react-redux:
let { dispatch } = this.props
// Note: this won't work:
// TodoActionCreators.addTodo('Use Redux')
// You're just calling a function that creates an action.
// You must dispatch the action, too!
// This will work:
let action = TodoActionCreators.addTodo('Use Redux')
dispatch(action)
}
render() {
// Injected by react-redux:
let { todos } = this.props
return <TodoList todos={todos} {...this.boundActionCreators} />
// An alternative to bindActionCreators is to pass
// just the dispatch function down, but then your child component
// needs to import action creators and know about them.
// return <TodoList todos={todos} dispatch={dispatch} />
}
}
export default connect(state => ({ todos: state.todos }))(TodoListContainer)
bindActionCreators.js
我们接下来来看它的源码
/**
*
* 在看`bindActionCreator`方法之前可以先看`bindActionCreators`方法
*
* @param {Function} actionCreator 实际就是 action
*
* @param {Function}
*
* @returns {Function}
*/
function bindActionCreator(actionCreator, dispatch) {
return function() {
// 返回的是dispath
return dispatch(actionCreator.apply(this, arguments))
}
}
// actionCreators是一个包含众多actions的对象
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
// actionCreators是函数就代表他是单一的action方法
return bindActionCreator(actionCreators, dispatch)
}
// actionCreator如果不是object 或者它是空的则报错
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"?`
)
}
// 将action的keys遍历出来
const keys = Object.keys(actionCreators)
const boundActionCreators = {}
for (let i = 0; i < keys.length; i++) {
// 每个action的key
const key = keys[i]
// 将action取出 这是一个方法
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
// bindActionCreator返回的是dispatch的返回值
// 实际是action 所以boundActionCreators是一个dispatch function的对象
// 同时如果key相同会被覆盖
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
使用bindActionCreators实际可以创建一个充满dispatch方法的对象。然后可以将这个对象传递子组件来使用。
总结
看完源码后我们大致了解到为什么reducer必须是function,store中的state为什么会创建和reducer相应的对象名的state,为什么只能通过dispatch来对store进行操作。另外redux的一个核心不可变性,redux本身并不能保证。所以我们在自己写的reducer当中必须要保证不能改变store原有的对象,必须得重新创建。
广而告之
本文发布于薄荷前端周刊,欢迎Watch & Star ★,转载请注明出处。