在以前的文章redux从入门到实践当中对redux
的使用进行了说明,此次就来看下它的源码,从而进一步的熟悉它。node
相关git地址react
git clone https://github.com/reduxjs/redux.git
复制代码
构建文档是CONTRBUTING.md
webpack
"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
git
{
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
github
咱们来看下src/index.js
web
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
主要是将方法暴露出来,给使用者使用npm
createStore
用于建立storecombineReducers
用于组合成rootReducers,由于在外部初始化store时,只能传入一个reducersbindActionCreators
组装了dispatch方法applyMiddleware
合并多个中间件compose
将中间件(middleware)和加强器(enhancer)合并传入到createStore
中src/combineReducers.js
json
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。redux
那么若是咱们直接对store的state进行操做而不是经过dispatch会发生呢,好比说咱们这样api
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会返回一个方法,这个方法能够将传入的方法依次执行
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.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,那咱们结合在一块儿看一下
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
中的方法
很简单,就是返回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
}
复制代码
这是将一个回调加入到监听数组当中,同时,它会返回一个注销监听的方法。
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首先会检查参数,随后会执行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
}
复制代码
/** * 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
这里有个使用例子
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
}
}
}
复制代码
在讲这个方法前,先看下文档对它的使用说明
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 * */
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
原有的对象,必须得从新建立。