首先来一段 redux 连系 中心件 thunk、logger 的运用demo 相识一下应当怎样运用
const redux = require('redux')
const {
createStore,
combineReducers,
bindActionCreators,
compose,
applyMiddleware
} = redux
const simpleState = { num: 0 }
const complexState = { num: 1 }
// logger中心件担任打印日记
function logger({dispatch, getState}) {
return function(next) {
return function(action) {
console.log('logger start:' + action.type)
const res = next(action)
console.log('logger end:' + action.type)
return res
}
}
}
// thunk中心件担任异步
function thunk({dispatch, getState}) {
return function(next) {
return function(action) {
if (typeof action === 'function') {
return action(dispatch)
}
return next(action)
}
}
}
// 派发的增添要领
function increment(num) {
return {
type: 'increment',
payload: num
}
}
// 派发的削减的要领
function decrement(num) {
return {
type: 'decrement',
payload: num
}
}
// 派发的乘法的要领
function multiply(num) {
return {
type: 'multiply',
payload: num
}
}
// 派发的除法的要领
function divide(num) {
return {
type: 'divide',
payload: num
}
}
// 派发异步事宜要领
function syncDivide() {
return (dispatch) => {
setTimeout(() => {
dispatch(divide(10))
}, 2000)
}
}
// 担任加减法的reducer
function simpleCalcReducer(state = simpleState, action) {
switch (action.type) {
case 'increment':
return Object.assign({}, state, { num: state.num + action.payload })
case 'decrement':
return Object.assign({}, state, { num: state.num - action.payload })
default:
return state
}
}
// 担任乘除法的reducer
function complexCalcReducer(state = complexState, action) {
switch (action.type) {
case 'multiply':
return Object.assign({}, state, { num: state.num * action.payload })
case 'divide':
return Object.assign({}, state, { num: state.num / action.payload })
default:
return state
}
}
const middleware = applyMiddleware(thunk, logger)
const allReducers = combineReducers({
simpleCalcReducer,
complexCalcReducer
})
const store = createStore(allReducers, middleware)
// 一切action
const allActions = {
increment,
decrement,
multiply,
divide,
syncDivide
}
const actions = bindActionCreators(allActions, store.dispatch)
// 派发action
actions.increment(2);
actions.decrement(1);
actions.multiply(10);
actions.divide(5);
actions.syncDivide()
setTimeout(() => {
console.log(store.getState())
}, 2500)
打印效果以下:
上面只是简朴运用了redux的部份api,来只管完成一个范例项目中所运用到的redux的基础操纵流程,下面进入源码的剖析阶段
1.项目组织
第一张图,先来看一下 redux 的项目组织目次
—src
——utils // 东西函数库
———actionTypes.js // redux用来初始化state,自身挪用的action
———isPlainObject.js // 用来检测action 是不是为一个明白的对象
———warning.js // 用来举行正告的大众要领
——applyMiddleware.js // 中心件,担任对store.dispatch要领举行包装
——bindActionCreators.js // 对派发action的语法举行优化,使之可以更好的运用于react或其他
——combineReducers.js // 将星散的reducer兼并成一个对象,并返回一个实行这些reducer的函数
——compose.js // 兼并函数, 将函数参数依据从左向右的递次举行兼并,返回一个新函数
——createStore.js // 中心要领,返回一个store对象,用来猎取state、派发action、增加定阅者subscribe 等
——index.js // 进口文件,对外导出的要领
2.utils 东西要领的剖析
将utils的三个要领放在最前面来剖析,重要是为了先对三个东西要领有个简朴观点和相识,如许在浏览下面的内容时,关于有用到东西要领的处所可以直接明白,不必再打断思路去剖析东西要领,影响团体的浏览体验
① warning.js — 控制台打印异常日记
export default function warning(message) {
if (typeof console !== 'undefined' && typeof console.error === 'function') {
console.error(message)
}
try {
throw new Error(message)
} catch (e) {}
}
总结:
warining 函数异常简朴,当 console.error 要领存在时来打印 毛病的 message,这里的一层推断是为了兼容ie6789浏览器只需在开启控制台的情况下,console对象才会建立,不然会报console未定义而导出顺序没法举行。
至于下面的为何要 try 内里去抛出异常,自身如许做对顺序是没有什么影响的,如许的意义又是什么呢?源码的诠释里诠释道 “This error was thrown as a convenience so that if you enable ‘break on all exceptions’ in your console, it would pause the execution at this line ”,翻译过来就是,抛出这个毛病是为了轻易检察毛病源。只需我们开启了断点异常,那末顺序就会在抛出毛病的那行代码上打上断点,来轻易举行调试和追踪。那末在谷歌内里这个异常怎样开启呢?F12翻开谷歌的开辟者东西,1点击 Sources – 2点击蓝色的pause icon – 3勾选 Pause on caught exceptions,以下图所示
在控制台里测试以下代码
键入回车后,浏览器涌现断点,跳转至sources资本文件,并高亮了抛出毛病的那行代码,异常轻易
② isPlainObject.js — 推断目标是不是为明白的对象
export default function isPlainObject(obj) {
if (typeof obj !== 'object' || obj === null) return false
let proto = obj
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto)
}
return Object.getPrototypeOf(obj) === proto
}
总结:
Object.getPrototypeOf 要领用来返回对象的隐式原型,即组织这个对象的组织函数的原型。
运用 while 轮回来查找目标对象的原型链顶层,因为 Object.prototype.__proto__ === null,所以Object.prototype 就是原型链的顶层,查找到末了一层时,proto 肯定被赋值为 Object.prototype。
如许做的意义是什么? Array数据范例 或许 dom的 document等数据范例的typeof 也是 object, 他们都不是直接由Object 函数来构建的对象,以 Array 数组的组织函数来举例。
var ary = new Array()
var pro = Object.getPrototypeOf(ary)
console.log(pro === ary.__proto__) // true
console.log(ary.__proto__ === Array.prototype) // true
var pro2 = Object.getPrototypeOf(pro)
console.log(pro2 === Object.prototype) // true
Object.prototype.__proto__ === null // true
可见 ary 第一次猎取到的原型是 Array.prototype,而 Array.prototype 自身也是一个对象,必定由 Object 函数建立,所以 Array.prototype.__proto__ 又指向了 Object.prototype,到此轮回终了。终究 pro = Object.prototype。这就造成了终究的 Object.getPrototypeOf(obj) 和 proto 是不等的。
所以这个要领的就很清晰了,只需直接运用 Object 函数建立的对象才会被推断为真,因为只需它原型链存在一层
③ actionTypes.js — redux 内部做初始化state的action
const randomString = () =>
Math.random()
.toString(36)
.substring(7)
.split('')
.join('.')
const ActionTypes = {
INIT: `@@redux/INIT${randomString()}`,
REPLACE: `@@redux/REPLACE${randomString()}`,
PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`
}
export default ActionTypes
总结:
randomString() 要领随机天生一个36进制的数字,然后切割拼接,终究天生 和”5.b.p.3.b.8″ 花样一样的字符串
这个要领 导出了一个对象,对象包括3个key: INIT、REPLACE、PROBE_UNKNOWN_ACTION,前两个是字符串,背面一个是要领,要领也是返回一个拼接好的字符串,实在这三个都是redux内部用来派发action 的 type
3.模块剖析(去掉诠释)
① 进口文件 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'
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
}
总结:
进口文件作用就时将几个模块文件引入和导出,内里有一个空的 isCrushed 函数,这个函数的意义就是推断当前的构建东西是不是为 node 环境,假如是 node 环境而且非生产环境,那末就要推断当前的 redux 文件有无被紧缩。
推断的目标就是愿望开辟者,在非生产环境下不要紧缩代码,假如项目比较大,我们只是修正了一个小小的款式,这时刻假如开启代码的殽杂紧缩,那末我们项目标一切依靠的文件都邑被殽杂紧缩,项目越大被紧缩的内容越多耗时越长,从而致使调试的时刻增添,下降开辟效力。这也恰是redux在 warning 正告里提到的 ‘This means that you are running a slower development build of Redux’,你正在一个迟缓的开辟环境下运用 redux。
② 中心文件 creteStore.js
cretaeStore.js 是redux的中心文件,在这个要领里,redux 向外供应了 dispatch、subscribe、getState、replaceReducer 这四个中心要领。另外另有一个 [$$observable] 要领,这个要领并非很好明白他的作用和意义,放在文章末了来申明。下面是移除了诠释的源代码
import $$observable from 'symbol-observable'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
export default function createStore(reducer, preloadedState, enhancer) {
// 推断1
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.'
)
}
// 推断2
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
// 推断3
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
// 推断4
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
// 内部变量
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
function ensureCanMutateNextListeners() {
// ...
}
function getState() {
// ...
}
function subscribe(listener) {
// ...
}
function dispatch(action) {
// ...
}
function replaceReducer(nextReducer) {
// ...
}
function observable() {
// ...
}
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
createStore 要领吸收3个参数,离别是 reducer,preloadedState,enhancer。
1.reducer 就是一个纯函数用来返回 state
2.preloadedState 是初始化的state,在现实开辟中,很少有通报这个这个参数。实在这个参数就是为了初始化必需的原始数据。另外,假如运用了combineReducer这个要领来组合多个reducer,响应的preloadedState 里的key 也必须要和 combineReducer 中的key相对应
3.enhancer 翻译过来就是加强器,它是一个类似的高阶函数用来包装和加强 creteStore 内部的 dispatch、subscribe、getState 等要领,经由历程这个高阶函数可以完成 中心件、时刻游览、耐久化state等,在redux内只完成了一个enhancer,它就是中心件 applyMIddleware,用来强化 dispatch要领。
讲完三个参数,最早诠释代码
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.'
)
}
推断1:提醒很明白,当运用多个enhancer时,须要运用compose 要领将多个enhancer兼并成一个单一的函数。
// 推断2
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
推断2:个人感觉这里的推断就是为了相符更普通化的开辟需求,preloadedState 这个参数在现实开辟中,真的很少通报。所以在这里做了一个推断,假如开辟中没有用到这个初始的preloadedState,完全可以将它省略掉,直接通报末了一个enhancer函数,redux在内部帮开辟者,完成了这部份参数转换的处置惩罚。
// 推断3
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
推断3:当enhancer 存在而且为函数的时刻,直接将当前creteStore要领作为参数通报给 enhancer。而这个enhancer(createStore),返回的就是 createStore 的加强版可以称他为 creteStore-X,至于怎样加强先放到背面的applyMiddleware这个enhancer来申明。这里先只须要知道,经由历程enhancer包装事后的 createStore,内部的某些要领被加强了。
// 推断4
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
推断4:reducer 必需是一个函数 函数 函数
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
内部变量:
这里离别运用了,currentReducer 和 currentState 来吸收初始通报的 reducer 和 preloadedState,从新赋值是因为 currentReducer 和 currentState 都是可变的,当他们被修正的时刻不会影响初始的reducer 和 preloadedState。
currentListeners 是一个数组用来存储定阅的函数列表,为何还要多定义一个 nextListeners = currentListeners 呢?这个题目放到背面看比较好明白,先擦过
isDispatching 比较好明白,用来推断redux是不是处于派发action的状况中,即是不是在实行reducer。
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
ensureCanMutateNextListeners:返回currentListeners 的浅拷贝
这里的企图不是很明显,当nextListeners 和 currentListeners 全等的时刻,返回一个 currentListeners 的 浅拷贝赋值给 nextListenenrs,意义是什么呢接着向下看。
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:返回当前的currentState
返回当前的currentState,当action还在 派发当中时,假如挪用这个要领会抛出毛病
function subscribe(listener) {
// 被增加的定阅者必需是一个函数
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
// 处于dispatch的时刻,不允许增加定阅者
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 用来示意当前的定阅者已胜利的被
// 增加到了定阅的列表中
let isSubscribed = true
// 当nexListeners 和 currentListeners 全等时刻,返回了一个新的
// nextListeners, 然后将定阅者增加到新的 nextListeners
ensureCanMutateNextListeners() // ?1
nextListeners.push(listener)
// 返回一个 unsubscribe, 用来作废已增加到定阅列表中的定阅者
return function unsubscribe() {
// 假如当前的定阅者已被作废了 直接返回
if (!isSubscribed) {
return
}
// 处于dispatch的时刻,不允许作废定阅者
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.'
)
}
// 将定阅者的状况改成false
isSubscribed = false
// 继承更新 nextListeners
ensureCanMutateNextListeners()
// 将作废的定阅者从 nextListeners 中删除
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
subscribe:用来增加和删除定阅者
如许大略看下来彷佛也没太看清晰这个 ensureCanMutateNextListeners 要领有什么卵用,继承翻看redux 对ensureCanMutateNextListeners 要领的诠释,内里提到
“This makes a shallow copy of currentListeners so we can use nexListeners as a temporary list while dispatching”
“This prevents any bugs around consumers calling subscribe/unsubscribe in the middle of a dispatch”
重要就是背面这一句,这个要领 可以防备用户在实行dispatch中挪用定阅或许作废定阅时涌现任何毛病。照样不明所以,继承向下寻觅答案
function dispatch(action) {
// 推断 action 是不是为明白的 object 对象
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?'
)
}
// 不允许同时实行多个 dispatch
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
// 将实行状况设置为true, 最早实行reudcer, 并吸收 currentState
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
// 不管胜利失败 都将实行状况重置
isDispatching = false
}
// reducer 实行终了以后,最早轮回实行 定阅者数组列表
const listeners = (currentListeners = nextListeners) // ?2
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i] // ?3
listener()
}
return action
}
dispatch:用来派发action,实行reducer
题目2,每次实行定阅者列表的时刻,为何要新定义一个 listeners 来吸收 nextListeners,直接 currentListeners = nextListeners 然后运用 currentListeners 来轮回不行吗?
题目3,轮回内里为何不直接运用 listeners[i]() 来实行挪用,而是定义一个变量来吸收然后再实行?
再连系上面 subscribe 中 ensureCanMutateNextListeners 的究竟有何意义?
实在,这一切的细节都是为相识决一种特别的运用场景,在定阅事宜内部 再次 增加定阅或许作废定阅。如
store.subscribe( () => { store.subscribe(…) })
假如是这类场景,因为是在轮回中去触发 listener,纯真的运用 currentListeners 来存储定阅列表是没法满足的。轮回还没有终了,个中的某个 listener 对 currentListeners 举行了增加或许删除,都邑影响到此次轮回的举行,带来不可预期的毛病。至此这些细节就变得清晰起来了。
援用官方的诠释更加准确,每次dispatch后,轮回运用的是当前nextListeners 的快照,一样也就是 currentListeners,它在轮回终了之前是不会被转变的。设想一下,假如在定阅事宜的内部继承挪用 store.subsrcibe 来增加定阅者,那末就会挪用 ensureCanMutateNextListeners 这个要领,假如currentListeners 和 nextListeners 是完全相称的申明nextListeners 还未被转变,此时浅拷贝一份 currentListenrs 的行列赋值为 nextListeners,nextListeners 就变成了一个全新的定阅行列,然后将 增加的定阅者放到新的 nextListeners,如许就完全不会影响到之前已最早的轮回。当下次disptach 再次提议的时刻,将 currentListeners 同步为最新的 nextListeners 行列。
题目 2 应当怎样明白呢?找到了初期redux的提交纪录以下:
这里还没有对 currentListeners 和 nextListeners 做观点的辨别,只是将每次listeners 浅拷贝了一层用来 平安的实行轮回。所以 const listeners = (currentListeners = nextListeners) 中声明的 listeners并非必需的,他的存在只是为了在以后在轮回中运用 listeners 替代 currentListeners 少打几个字母罢了
题目 2 应当怎样明白呢?实在在这里是为了确保在 listener 当中的 this 与我们对 js 当中的函数内部 this 指向谁的预期保持一致。这里就是将 this 从新绑定为默许的全局对象,假如直接运用 listeners[i]() 来挪用,那末其内部的this 变指向了listeners 这个数组自身。
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.REPLACE })
}
replaceReducer:替代reducer,而且从新 dispatch
replaceReducer 这个要领比较简朴,当我们的项目启用了模块懒加载,我们的最最早的reducer可以只是部份的模块的reducer,这时刻假如要引入新的模块就须要可以动态的替代 reducer 来更新state。官方的诠释里还写道,假如在开辟情势下启用了热更新,一样须要这个函数来举行替代。
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
}
}
}
observable:此要领返回一个 observable 对象,该对象具有 subscribe 的要领来增加一个可视察的对象。那末这个要领究竟时干什么用的呢?诠释里有写道 “For more information, see the observable proposal:https://github.com/tc39/propo…”,翻开这个地点看到这个项目是将 可视察范例引入到ECMAScript范例库中,而redux 这里就是完成了 observable 的视察对象。这里不是很清晰怎样运用 … 略过。
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
末了运用 dispatch({ type: ActionTypes.INIT }) 天生一个初始化的state,这也就是为何preloadedState不须要通报,就可以获得初始化的state了。因为redux内部在实行 createStore 这个要领的时刻,自动实行了一次 disptach。末了将众要领返回
③ combineReducers — 将多个reducer 兼并成一个函数
import ActionTypes from './utils/actionTypes'
import warning from './utils/warning'
import isPlainObject from './utils/isPlainObject'
// 函数1
function getUndefinedStateErrorMessage(key, action) {
// ...
}
// 函数2
function getUnexpectedStateShapeWarningMessage(
inputState,
reducers,
action,
unexpectedKeyCache
) {
// ...
}
// 函数3
function assertReducerShape(reducers) {
// ...
}
// 函数4
export default function combineReducers(reducers) {
// ...
}
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.`
)
}
getUndefinedStateErrorMessage: 要领比较简朴,返回一段提醒信息,提醒 reducer不能返回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'
// 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.'
)
}
// state 为非明白的对象的时刻的提醒
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当中的某个key值没法在reducers当中找到,而且还未被加入到unexpectedKeyCache
// 那末就把这个key挑选出来
const unexpectedKeys = Object.keys(inputState).filter(
key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
)
// 将上一步骤挑选出来的key,存储到unexpectedKeyCache内里
unexpectedKeys.forEach(key => {
unexpectedKeyCache[key] = true
})
// 假如是运用了 replaceReducer 替代了reducer,那末就不须要举行提醒了,因为之前
// 的state 的数据可以和 新的reducer 不能保持一致
if (action && action.type === ActionTypes.REPLACE) return
// 将state内里reducer不能操纵的key打印出来
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.`
)
}
}
getUnexpectedStateShapeWarningMessage: 在非生产环境对finalReducer举行的校验,将state内异常的 key 抛出,进而提醒开辟者。
function assertReducerShape(reducers) {
// 轮回一切reducer, 并运用 ActionTypes.INIT 和 ActionTypes.PROBE_UNKNOWN_ACTION()
// 两个type范例的 action 校验一切reducer 是不是返回了相符范例的 state
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.`
)
}
if (
typeof reducer(undefined, {
type: ActionTypes.PROBE_UNKNOWN_ACTION()
}) === '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.`
)
}
})
}
assertReducerShape:用来断言reducers返回的效果是不是是相符要求的范例,假如不满足推断它会抛出一个error
可以看出在不运用 combineReducers 的时刻,我们编写的唯一的reducer是不必这些校验的,假如我们在个中返回了 undefined 那末必定终究的 currentState 就变成了 undefined。
那末为何 combineReducers 这个要领强迫一切的 reduer 不能返回 undefined 呢?
找到了redux的中文文档:http://cn.redux.js.org/docs/a…
内里提到:”combineReducers 函数设想的时刻有点偏主观,就是为了防止新手犯一些罕见毛病。也因些我们有意设定一些划定规矩,但假如你本身手动编写根 redcuer 时并不须要恪守这些划定规矩“
如许就很清晰了,本来多的这些校验是为了更好的提醒新手用户,reducer的准确运用范例。
这里另有一个题目,运用 {type: ActionTypes.INIT} 来校验 reducer 是不是返回准确的state,ActionTypes.INIT 应当必定是 reducer 内部未知的 action 了。然则为何下面还要用 { type: ActionTypes.PROBE_UNKNOWN_ACTION() } 在反复校验一次吗?难道说只是为了申明两次校验的动身目标不一样?然则如许是不是是过剩了
export default function combineReducers(reducers) {
// 吸收一个对象情势的 reducers 鸠合如 { reducer1: () => {}, reducer2: () => {}, }
const reducerKeys = Object.keys(reducers)
// 终究将要被实行的reducers 鸠合
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
// 推断当前假如黑白天生环境 而且 reducers[key] 不存在时在控制台打印正告信息
if (process.env.NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
// 只需当 reducers[key] 为函数范例时 才增加到 finalReducers 当中
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
// 猎取finalReducers 当中的 key
const finalReducerKeys = Object.keys(finalReducers)
// 用来寄存state中不能被操纵的 key
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
// 为了优化机能只在非生产情势下举行校验
unexpectedKeyCache = {}
}
// 用来校验finalReducers中每一个reducer是不是符合范例
let shapeAssertionError
try {
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
return function combination(state = {}, action) {
// 到这里最早实行 reducer
if (shapeAssertionError) {
// 假如reducer返回了undefined直接抛出毛病
throw shapeAssertionError
}
// 非生产环境举行提醒
if (process.env.NODE_ENV !== 'production') {
const warningMessage = getUnexpectedStateShapeWarningMessage(
state,
finalReducers,
action,
unexpectedKeyCache
)
if (warningMessage) {
warning(warningMessage)
}
}
// 推断 state 有没用被变动
let hasChanged = false
// 从新天生的state
const nextState = {}
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)
if (typeof nextStateForKey === 'undefined') {
// 假如reducer 返回了undefined 抛出毛病
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
// 假如state 内的某个key的数据已被变动过 此处必定是 true
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
// 依据state是不是改动过 返回对应的state
return hasChanged ? nextState : state
}
}
combineReducers:这个要领就是将多个reducers轮回实行后终究返回一个兼并后的state,这个要领照样比较简朴的。
④– bindActionCreators,供应了一种挪用dispatch的其他体式格局,代码自身比较简朴,这里就不在赘述了
function bindActionCreator(actionCreator, dispatch) {
return function() {
return dispatch(actionCreator.apply(this, arguments))
}
}
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
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"?`
)
}
const boundActionCreators = {}
for (const key in actionCreators) {
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
⑤ — compose, 组合函数,将多个函数参数依据传入的递次组合成一个函数,并返回。第二个参数作为第一个函数的参数,第三个参数作为第二个函数的参数,顺次类推。返回的函数吸收的参数,将作为compose 初始参数的末了一个函数的参数,源码以下
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
// 终究 compose 返回的是一个 (...args) => a(b(...args)) 如许的函数
// 那末这个函数内部挪用的递次也就清晰了,实行这个函数的时刻,首先会实行
// var c = b(...args), 然后将c 作为参数通报给 a,假如多个函数的话顺次类推,
// 最早实行最内里的谁人参数,由里向外
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
题目1,先实行了b(…args)函数,不就直接最早从内里实行了吗?demo1
var funcs2 = [
function a() {
console.log('a')
},
function b() {
console.log('b')
},
function c(arg) {
console.log(arg)
console.log('c')
}
]
var t = compose(funcs2)
t('d')
// d c b a
说好的剥洋葱呢,怎样就成半个了?
实在compose这个要领只是供应了组合函数的功用,真正想要完成完全的从外到内这类完全的洋葱模子,还须要对通报到compose参数的每一个函数做处置惩罚,它的每一个函数肯定是返回了一个新的函数,如许才确保它不会被简朴的实行,思索下面的demo
var funcs = [
function a(b) {
return () => {
console.log('a')
return b();
}
},
function b(c) {
return () => {
console.log('b');
return c();
}
},
function c(arg) {
return () => {
console.log('c');
console.log(arg)
}
}
]
var f = compose(funcs)('d');
f()
// a b c d
这就和文章最最早完成的 logger 和 thunk 的中心件很类似了。a 里实行 b , 然后 b 里实行 c,一个中心件的雏形已涌现了。
⑥ — applyMiddleware,redux内唯一完成的enhancer,用来扩大dispatch要领,也是redux中最难明白的处所,一睹真容吧,为了便于对实行历程的明白,这里贴一下中心件redux-thunk源码的简化版
({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
}
import compose from './compose'
export default function applyMiddleware(...middlewares) {
// 这里 applyMiddleware 返回的函数吸收的第一个参数是 creteStore,
// 这就是之前 createStore内直接运用 enhancer(createStore)(reducer, preloadedState)
// 的缘由了。
return createStore => (...args) => {
// store 就是最原始的 store 对象,这里还未对store.dispatch 举行扩大
const store = createStore(...args)
// 声明一个dispatch的要领,注重这里运用的时let,示意这个dispatch将要会被转变
// 而转变事后的dispatch 就是加入了中心价加强版的 dispatch
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
// middlewareAPI和字面意义一样就是,中心件须要用到的原始的store对象的要领,,
// 这里供应了两个要领,一个getState, 一个是dispatch。等等这里彷佛有点不一样的东西
// 为何这里 要写成 dispatch: (...args) => dispatch(...args),而不是直接
// dispatch: dispatch 先留下继承向下看。
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
// 这里参考上面的redux-thunk的源码 chain终究返回的效果就是
/*
chain = [ next => action => {
if (typeof action === 'function') {
return action(dispatch, getState);
// 此处的dispatch 和 getState 即为 middlewareAPI的dispatch 和 getState
}
return next(action);
} ]
*/
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 连系之前已剖析过的compose,compose(...chain)末了一个参数来吸收 store.dispatch
// 因为这里只通报了一个thunk中心件,所以,这里的 dispatch 就变成了
/*
dispatch = (action) => {
if (typeof action === 'function') {
return action(dispatch, getState);
// 此处的dispatch 和 getState 依旧为 middlewareAPI的dispatch 和 getState
}
// 因为next被赋值为store.dispatch, 此处现实为
return store.dispatch(action);
}
*/
// 也因为这里dispatch 被从新赋值,致使middlewareAPI内dispatch属性
// 里运用到的 dispatch 也变了,不再是抛出error毛病的谁人函数了。
// 这就是为何肯定要写成 dispatch: (...args) => dispatch(...args) 的缘由了
// 假如写成 dispatch: dispatch, 相当于只是初始声清楚明了这个要领,后续dispatch的修正就与它无关了
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
// 返回的dispatch 替代掉了 store 内部初始的 dispatch, dispatch被扩大了
}
}
总结:
经由历程对源码的翻阅,相识到了全部redux的实行流程和机制。经由历程createStore来初始化state,当须要运用异步action的时刻,可以运用 applyMiddleware 将redux-thunk redux-saga 等中心件对store.dispatch 举行扩大。每次挪用 dispatch 都邑实行一切的reducer,reducer实行终了后,会更新一切的定阅事宜。