redux安装
初始化项目
npm init
复制代码
下载redux
npm install redux --save
复制代码
代码目录
redux的目录结构很简单,如下图:
源码阅读
createStore.js:
/**
* Creates a Redux store that holds the state tree.
* The only way to change the data in the store is to call `dispatch()` on it.
*
* There should only be a single store in your app. To specify how different
* parts of the state tree respond to actions, you may combine several reducers
* into a single reducer function by using `combineReducers`.
*
* @param {Function} reducer A function that returns the next state tree, given
* the current state tree and the action to handle.
*
* @param {any} [preloadedState] The initial state. You may optionally specify it
* to hydrate the state from the server in universal apps, or to restore a
* previously serialized user session.
* If you use `combineReducers` to produce the root reducer function, this must be
* an object with the same shape as `combineReducers` keys.
*
* @param {Function} [enhancer] The store enhancer. You may optionally specify it
* to enhance the store with third-party capabilities such as middleware,
* time travel, persistence, etc. The only store enhancer that ships with Redux
* is `applyMiddleware()`.
*
* @returns {Store} A Redux store that lets you read the state, dispatch actions
* and subscribe to changes.
*/
export default function createStore(reducer, preloadedState, enhancer) {
// 对createStore的选填参数进行判断
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
// enhancer和preloadedState进行互换
enhancer = preloadedState
preloadedState = undefined
}
// 如果传参enhancer,那么enhancer必须是方法
// 通过enhancer把createStore包装一次,并传入reducer,preloadedState,
// 这是传了中间件的情况
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
// 验证reducer
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
let currentReducer = reducer // 当前的reducer函数
let currentState = preloadedState // 当前状态树
let currentListeners = [] // 当前监听列表
let nextListeners = currentListeners // 当前监听列表的引用
let isDispatching = false // 是否正在分发
// 如果nextListeners和currentListeners的引用地址相同,则nextListeners拷贝一份currentListeners
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
/**
* Reads the state tree managed by the store.
*
* @returns {any} The current state tree of your application.
*
*/
//当isDispatching为false时,返回当前state树
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.'
)
}
return currentState
}
// getState 方法,返回当前状态树
/**
* Adds a change listener. It will be called any time an action is dispatched,
* and some part of the state tree may potentially have changed. You may then
* call `getState()` to read the current state tree inside the callback.
*
* You may call `dispatch()` from a change listener, with the following
* caveats:
*
* 1. The subscriptions are snapshotted just before every `dispatch()` call.
* If you subscribe or unsubscribe while the listeners are being invoked, this
* will not have any effect on the `dispatch()` that is currently in progress.
* However, the next `dispatch()` call, whether nested or not, will use a more
* recent snapshot of the subscription list.
*
* 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) {
// 参数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.'
)
}
// 设置私有变量isSubscribed为true
let isSubscribed = true
ensureCanMutateNextListeners()
// 将新的listener添加到nextListeners里
nextListeners.push(listener)
// 返回取消监听方法unsubscribe,通过判断判断当前监听是否存在等可注销当前监听。
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
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
function dispatch(action) {
// 通过isPlainObject判断是否为对象
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.')
}
// 通过reducer返回一个新的state座位当前的state
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()
}
// 最后返回action
return action
}
// nextReducer必须是方法,重新初始化状态树
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.REPLACE })
}
// 用于观察者模式
function observable() {
const outerSubscribe = subscribe
return {
subscribe(observer) {
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
}
}
}
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
复制代码
createStore接受三个参数 :
reducer、preloadedState、enhancer
其中reducer是必传后面两个是选传
复制代码
reducer:这是一个函数,接受两个参数(stsate和action),reducer在dispatch中被调用。
preloadedState: 初始的state。
enhancer: enhancer也是一个函数,是一个增强的createStore,是使用applyMiddleware来生成的。
createStore的内部环境及返回值 :
createStore方法形成了一个闭包其中包含了几个重要的数据:
reducer、currentState(简称state)、currentListeners、nextListeners(简称listeners,其元素简称listener)
复制代码
返回的是一个对象,这个对象中包含四个方法:
dispatch、subscribe、getState、replaceReducer
复制代码
下面我们分别对这四个方法进行分析
dispatch:redux中唯一改变state树的方法,它可以分发一个action然后通过reducer改变state树,同时需要一个reducer执行结束才能进行下一次reducer,最后执行一次listener,因此每次state发生变化都会被监听到。
subscribe:用于订阅监听,为listeners增减就是通过它,最后返回销毁该listener的方法。
getState:获取当前state树。
replaceReducer: 替换当前store中的reducer,并初始化。
createStore通过闭包在内部创建了state状态树实现持久存储,然后通过暴露相应的方法实现对state的增删改查 compose.js:
export default function compose(...funcs) {
// 传入的参数是一个数组,数组长度为0时返回一个 arg => arg的函数
if (funcs.length === 0) {
return arg => arg
}
// 当数组为一时返回这个数组的第一个元素
if (funcs.length === 1) {
return funcs[0]
}
// 当数组为大于1的数时,返回一个从左至有依次嵌套的的函数,内层的函数的返回值作为外层函数的参数
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
复制代码
reduce方法在平时也会有使用,在平时经常使用reduce对数组求和,比如:
[1,2,3,4].reduce((a,b)=>a+b)
复制代码
在这里的用法:
[funOne,funTwo,funThree].reduce((a, b) => (...args) => a(b(...args)))
//相当于 function(...args) { return funOne(funTwo(funThree(...args)))}
复制代码
从左至右依次嵌套,这样就可以理解compose的作用了
applyMiddleware.js:
export default function applyMiddleware(...middlewares) {
// 返回一个函数并将createStore作为参数然后返回一个新的函数,新的函数再将之前的reducer和preloadedState作为参数传入
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = () => {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
)
}
// 建立新的变量middlewareAPI
const middlewareAPI = {
// getStates属性
getState: store.getState,
// dispatch属性
dispatch: (...args) => dispatch(...args)
}
// 传入middlewareAPI并执行每一个中间件,返回一个数组
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 重写dispatch
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
复制代码
applyMiddleware就是用于创建createStore中enhancer函数的。
bindActionCreator.js:
// bindActionCreator接受两个参数actionCreator,dispatch,
// 将action与dispatch结合并传入
function bindActionCreator(actionCreator, dispatch) {
return function() {
return dispatch(actionCreator.apply(this, arguments))
}
}
export default function bindActionCreators(actionCreators, dispatch) {
// 如果action是以函数形式传进来的,那么就调用bindActionCreator方法
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
// actionCreators必须为对象类型
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"?`
)
}
// 将actionCreators所有的属性名存入keys中
const keys = Object.keys(actionCreators)
const boundActionCreators = {}
// 给actionCreators的每一个成员都绑定dispatch方法生成新的方法,
// 然后注入新的对象中,新方法对应的key即为原来在actionCreators的名字
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
复制代码
bindActionCreators的作用是将action与dispatch绑定,这里面又分为两种情况,如果 bindActionCreators的第一个参数是函数的,则直接绑定。如果第一个参数是对象则将其对象中的每一个action方法绑定。
combineReducers.js:
import ActionTypes from './utils/actionTypes'
import warning from './utils/warning'
import isPlainObject from './utils/isPlainObject'
// 通过key和action生成错误信息
function getUndefinedStateErrorMessage(key, action) {
const actionType = action && action.type
const actionDescription =
(actionType && `action "${String(actionType)}"`) || 'an action'
return (
`Given ${actionDescription}, reducer "${key}" returned undefined. ` +
`To ignore an action, you must explicitly return the previous state. ` +
`If you want this reducer to hold no value, you can return null instead of undefined.`
)
}
//一些警告信息
function getUnexpectedStateShapeWarningMessage(
inputState,
reducers,
action,
unexpectedKeyCache
) {
const reducerKeys = Object.keys(reducers)
const 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('", "')}"`
)
}
const unexpectedKeys = Object.keys(inputState).filter(
key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
)
unexpectedKeys.forEach(key => {
unexpectedKeyCache[key] = true
})
if (action && action.type === ActionTypes.REPLACE) return
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.`
)
}
}
// 检测reducer是否符合redux的规定
function assertReducerShape(reducers) {
Object.keys(reducers).forEach(key => {
const reducer = reducers[key]
const 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. If you don't want to set a value for this reducer, ` +
`you can use null instead of undefined.`
)
}
const 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, but can be null.`
)
}
})
}
/**
*
* reducer function. It will call every child reducer, and gather their results
* into a single state object, whose keys correspond to the keys of the passed
* reducer functions.
*
* @param {Object} reducers An object whose values correspond to different
* reducer functions that need to be combined into one. One handy way to obtain
* it is to use ES6 `import * as reducers` syntax. The reducers may never return
* undefined for any action. Instead, they should return their initial state
* if the state passed to them was undefined, and the current state for any
* unrecognized action.
*
* @returns {Function} A reducer function that invokes every reducer inside the
* passed object, and builds a state object with the same shape.
*/
export default function combineReducers(reducers) {
// 获取所有reducer的按键名
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
// 过滤reducer
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (process.env.NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
// finalReducers保存最后过滤过后的reducer集合
// 存储过滤后的reducer的键名
const finalReducerKeys = Object.keys(finalReducers)
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
// 检测每个reducer是否是符合标准的reducer
let shapeAssertionError
try {
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
//
return function combination(state = {}, action) {
if (shapeAssertionError) {
throw shapeAssertionError
}
// 如果不是生产环境的一些警告处理
if (process.env.NODE_ENV !== 'production') {
const warningMessage = getUnexpectedStateShapeWarningMessage(
state,
finalReducers,
action,
unexpectedKeyCache
)
if (warningMessage) {
warning(warningMessage)
}
}
let hasChanged = false
// 下一个state树
const nextState = {}
// 每发起一次dispatch都会遍历所有的reduce
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)
// reducer返回的数据是undefined抛出错误
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
// 将相应的值赋给nextState相应的属性
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
//如果当前action对应的reducer方法执行完后,该处数据没有变化,则返回原来的流程树
return hasChanged ? nextState : state
}
}
复制代码
combineReducers接受一个参数:
属性为reducer的对象
复制代码
combineReducers方法从字面就能理解,它的作用是将所有的reduce合并,通过每一次发起dispatch改变状态。