工具文件
actionTypes.js
const randomString = () =>
Math.random().toString(36).substring(7).split('').join('.')
const ActionTypes = {
INIT: `@@redux/INIT${/* #__PURE__ */ randomString()}`,
REPLACE: `@@redux/REPLACE${/* #__PURE__ */ randomString()}`,
PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`
}
export default ActionTypes
actionTypes文件主要封装了redux内置的actionType,其中ActionTypes.INIT主要用于初始化store。REPLACE PROBE_UNKNOWN_ACTION为替换reducer的actionType。
ex:这里介绍下Math.random().toString(36)使用
语法: number.toString(radix)
定义和用法: 数字的字符串表示。例如,当 radix 为 2 时,NumberObject 会被转换为二进制值表示的字符串。
radix 参数:
规定表示数字的基数,是 2 ~ 36 之间的整数。若省略该参数,则使用基数 10。但是要注意,如果该参数是 10 以外的其他值,则 ECMAScript 标准允许实现返回任意值。
2 - 数字以二进制值显示
8 - 数字以八进制值显示
16 - 数字以十六进制值显示
0-9(10个数字)+a-z(26个英文字母)总共36个,所以redix<=36。
isPlainObject.js
export default function isPlainObject(obj: any): boolean {
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
}
这个函数用于判断传入的对象是否是纯对象(简单对象), redux 要求 action 和 state 是一个纯对象
简单对象(纯对象)是指直接使用对象字面量{}或者new Object()、Object.create(null)所创建的对象,它的构造函数为 Object
ex:Object.getPrototypeOf
Object.getPrototypeOf() 方法返回指定对象的原型,Object.getPrototypeOf(object)
warning.js
export default function warning(message: string): void {
/* eslint-disable no-console */
if (typeof console !== 'undefined' && typeof console.error === 'function') {
console.error(message)
}
/* eslint-enable no-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.
throw new Error(message)
} catch (e) {} // eslint-disable-line no-empty
}
对代码执行过程中所遇到的错误进行统一处理
核心文件
createStore.js
1.校验函数传入的参数
函数执行的第一步是函数校验,如果校验未通过,直接抛出错误。 函数的第一个参数是必传的函数(reducers),函数的第二个参数是可选的除函数外的任意类型参数preloadState,代表着初始状态,参数的第三个参数是可选的函数enhancer,增强器(applyMiddleWare函数返回的函数组成的数组)。也可以传递两个参数reducers和enhancer
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.'
)
}
//由于函数可以支持多个中间件函数,但是多个中间件必须要通过compose函数包装之后在可以传入。
//函数首先对此进行了校验。判断如果函数的第二个和第三个参数都为函数。或者函数的第三个参数和第四个参数都为函数。
//则可以猜测到,开发者可能是为使用compose包装多个中间件,所导致,所以此时函数会抛出错误信息。提示出当前可能的错误原因
// 如果没有传递默认的 state(preloadedState 为函数类型,enhancer 为未定义类型),那么传递的
// preloadedState 即为 enhancer。
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
//当第二个参数preloadedState的类型是Function的时候,并且第三个参数enhancer未定义的时候,此时preloadedState将会被赋值给enhancer,
//preloadedState会替代enhancer变成undefined。
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
// 如果 enhancer 为不为空且非函数类型,报错。
throw new Error('Expected the enhancer to be a function.')
}
// 使用 enhancer 对 createStore 进行处理,引入中间件。注意此处没有再传递 enhancer 作为参数。
//实际上 enhancer 会对 createStore 进行处理,然后返回一个实际意义上的 createStore 用于创建 store 对象,参考 applyMiddleware.js。
return enhancer(createStore)(reducer, preloadedState)
}
// 如果 reducer 不是函数类型,报错。
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
2.在函数内部定义一系列局部变量,用于存储数据
let currentReducer = reducer // 存储当前的 reducer。
let currentState = preloadedState // 用于存储当前的 store,即为 state。
let currentListeners = [] // 用于存储通过 store.subscribe 注册的当前的所有订阅者。当前监听函数数组
let nextListeners = currentListeners // 新的 listeners 数组,确保不直接修改 listeners。
let isDispatching = false // 当前状态,防止 reducer 嵌套调用。
其中变量isDispatching,作为锁来用,我们redux是一个统一管理状态容器,它要保证数据的一致性,所以同一个时间里,只能做一次数据修改,如果两个action同时触发reducer对同一数据的修改,那么将会带来巨大的灾难。所以变量isDispatching就是为了防止这一点而存在的。
ensureCanMutateNextListeners 函数
//确保 nextListeners 可以被修改,当 nextListeners 与 currentListeners 指向同一个数组的时候
// 让 nextListeners 成为 currentListeners 的副本。防止修改 nextListeners 导致 currentListeners 发生变化。
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
⚠️为什么需要nextListeners?
如果在listener被调用期间,进行订阅或者取消订阅,在本次的dispatch( )过程中是不会生效的,然而在下一次的dispatch( )调用中,无论dispatch是否嵌套调用,都使用最近一次的快照订阅者列表 增加nextListener这个副本是为了避免在遍历listeners的过程中由于subscribe或者unsubscribe对listeners进行的修改而引起的某个listener被漏掉了
getState 函数
currentState在每次dispatch得时候都会得到响应的更新,函数首先判断当前是不是处于dispatching状态。是的话直接抛出错误。否则就将函数的局部变量current返回。 为了保证数据的一致性,当在reducer操作的时候,是不可以读取当前的state值的
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
subscribe 函数
- 判断监听者是否为函数
- 是否有reducer正在进行数据修改(保证数据的一致性)
// store.subscribe 函数,订阅 dispatch。
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
// 不允许在 reducer 中进行订阅。
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.'
)
}
//定义局部变量标记是否已经订阅并标记为true
let isSubscribed = true
//以确保nextListeners与currentListeners相同
// 每次操作 nextListeners 之前先确保可以修改。
ensureCanMutateNextListeners()
// 存储订阅者的注册方法。
nextListeners.push(listener)
// 返回一个用于注销当前订阅者的函数。//返回一个新的函数用于取消订阅
return function unsubscribe() {
//通过之前定义的是否订阅标记判断,当前的listener是否还在被订阅,如果已经取消了订阅,那么直接返回
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
// 每次操作 nextListeners 之前先确保可以修改。
ensureCanMutateNextListeners()
// 使用数组的indexOf方法在当前的nextListeners数组中找出对应的索引
const index = nextListeners.indexOf(listener)
//使用数组的splice方法将其删除
nextListeners.splice(index, 1)
}
}
dispatch 函数
// store.dispatch 函数,用于触发 action 修改 state。
function dispatch(action) {
if (!isPlainObject(action)) {
// action 必须是纯对象。
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
if (typeof action.type === 'undefined') {
// 每个 action 必须包含一个 type 属性,指定修改的类型。
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
// reducer 内部不允许派发 action。
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
//执行前isDispatching设置为true,阻止后续的action进来触发reducer操作,
//得到的state值赋值给currentState,
//完成之后再finally里将isDispatching再改为false,允许后续的action进来触发reducer操作
try {
// 调用 reducer 之前,先将标志true。
isDispatching = true
// 调用 reducer,返回的值即为最新的 state。
//更改state,此时便成功的触发了一个action
currentState = currentReducer(currentState, action)
} finally {
// 调用完之后将标志位置 0,表示 dispatch 结束。
isDispatching = false
}
// dispatch 结束之后,依次执行执行所有订阅者的函数。
//它的目的则是确保每次 dispatch 时都可以取到最新的快照
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
// 返回当前所使用的 action,这一步是中间件嵌套使用的关键,很重要。
return action
}
replaceReducer 函数
用于动态加载reducer
//判断所传reducer是否为函数
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
//将nextReducer赋值给currentReducer,以达到替换reducer效果,并触发state更新操作。
// TODO: do this more elegantly
;((currentReducer as unknown) as Reducer<
NewState,
NewActions
>) = nextReducer
// This action has a similar effect to ActionTypes.INIT.
// Any reducers that existed in both the new and old rootReducer
// will receive the previous state. This effectively populates
// the new state tree with any relevant data from the old one.
// 执行默认的 REPLACE 类型的 action。
dispatch({ type: ActionTypes.REPLACE } as A)
observable 函数
订阅 store 的变化
略
容易被忽略的代码,做了初始化
// 初始化时 dispatch 一个 INIT 类型的 action,校验各种情况
//需要拿到所有reducer默认的state,这样后续的dispatch一个action的时候,才可以更新我们的state dispatch({ type: ActionTypes.INIT })
store 对象就是一个纯 JavaScript 对象。包含几个属性 API,而我们的 state 就存储在 createStore 这个方法内部,是一个局部变量,只能通过 getState 方法访问到
combineReducers.js
getUndefinedStateErrorMessage 函数
用于获取错误信息的工具函数,如果调用你所定义的某个 reducer 返回了 undefined,那么就调用这个函数抛出合适的错误信息。
key 你所定义的某个 reducer 的函数名,同时也是 state 的一个属性名
action 调用 reducer 时所使用的 action
getUnexpectedStateShapeWarningMessage 函数
找出state里面没有对应reducer的key,并提示开发者做调整
assertReducerShape
检测finalReducers里的每个reducer是否都有默认返回值
function assertReducerShape(reducers) {
Object.keys(reducers).forEach(key => {
const reducer = reducers[key]
// 获取初始化时的 state。
const initialState = reducer(undefined, { type: ActionTypes.INIT })
//如果不想为这个reducer设置值,要返回null而不是undefined
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.`
)
}
// 如果初始化校验通过了,有可能是你定义了 ActionTypes.INIT 的操作。这时候重新用随机值校验。
//通过未知的action,来检测reducer能否正确处理,即返回的值类型是否为undefined,如果是则抛出错误原因
// 如果返回 undefined,说明用户可能对 INIT type 做了对应处理,这是不允许的。
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.`
)
}
})
}
combineReducers
// 把你所定义的 reducers 对象转化为一个庞大的汇总函数。
// 可以看出,combineReducers 接受一个 reducers 对象作为参数,
// 然后返回一个总的函数,作为最终的合法的 reducer,这个 reducer
// 接受 action 作为参数,根据 action 的类型遍历调用所有的 reducer。
export default function combineReducers(reducers) {
// 获取 reducers 所有的属性名。
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
// 遍历 reducers 的所有属性,剔除所有不合法的 reducer。
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
//如果不是正式环境,那么对于为null的reducer进行提示,如果是正式环境,则忽略类型部位函数的reducer
if (process.env.NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
if (typeof reducers[key] === 'function') {
// 将 reducers 中的所有 reducer 拷贝到新的 finalReducers 对象上。
finalReducers[key] = reducers[key]
}
}
// finalReducers 是一个纯净的经过过滤的 reducers 了,重新获取所有属性名。
const finalReducerKeys = Object.keys(finalReducers)
let unexpectedKeyCache
// unexpectedKeyCache 包含所有 state 中有但是 reducers 中没有的属性。
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let shapeAssertionError
try {
// 校验所有 reducer 的合理性,缓存错误。
//assertReducerShape对finalReducers进行类型校验,并存储错误信息到局部变量shapeAssertionError
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
// 这就是返回的新的 reducer,一个纯函数。每次 dispatch 一个 action,都要执行一遍 combination 函数,
// 进而把你所定义的所有 reducer 都执行一遍。
return function combination(state = {}, action) {
//在新返回的函数中,首先判断shapeAssertionError,如果存在错误就抛出
if (shapeAssertionError) {
// 如果有缓存的错误,抛出。
throw shapeAssertionError
}
if (process.env.NODE_ENV !== 'production') {
// 非生产环境下校验 state 中的属性是否都有对应的 reducer。
// 对于非正式环境,使用getUnexpectedStateShapeWarningMessage进行校验,并提醒错误
const warningMessage = getUnexpectedStateShapeWarningMessage(
state,
finalReducers,
action,
unexpectedKeyCache
)
if (warningMessage) {
warning(warningMessage)
}
}
let hasChanged = false
// state 是否改变的标志位。
const nextState = {}
// reducer 返回的新 state。
for (let i = 0; i < finalReducerKeys.length; i++) { // 遍历所有的 reducer。
const key = finalReducerKeys[i] // 获取 reducer 名称。
const reducer = finalReducers[key] // 获取 reducer。
const previousStateForKey = state[key] // 旧的 state 值。
const nextStateForKey = reducer(previousStateForKey, action) // 执行 reducer 返回的新的 state[key] 值。
if (typeof nextStateForKey === 'undefined') {
// 如果经过了那么多校验,你的 reducer 还是返回了 undefined,那么就要抛出错误信息了。
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
// 把返回的新值添加到 nextState 对象上,这里可以看出来,你所定义的 reducer 的名称就是对应的 state 的属性,所以 reducer 命名要规范!
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
// 检验 state 是否发生了变化。
}
// 根据标志位返回对应的 state。
return hasChanged ? nextState : state
}
}
applyMiddleware.js
applyMiddleware函数主要是与中间件有关的函数,他允许我们在action到达reducer之前对action进行加工处理 函数applyMiddleware的返回就是一个enhancer
import compose from './compose'
// 用于应用中间件的函数,可以同时传递多个中间件。中间件的标准形式为:
// const middleware = store => next => action => { /*.....*/ return next(action); }
export default function applyMiddleware(...middlewares) {
// 返回一个函数,接受 createStore 作为参数。args 参数即为 reducer 和 preloadedState。
return createStore => (...args) => {
// 在函数内部调用 createStore 创建一个 store 对象,这里不会传递 enhancer,因为 applyMiddleware 本身就是在创建一个 enhancer,然后给 createStore 调用。
// 这里实际上是通过 applyMiddleware 把 store 的创建推迟了。为什么要推迟呢?
//因为要利用 middleWares 做文章,先初始化中间件,重新定义 dispatch,然后再创建 store,
//这时候创建的 store 所包含的 dispatch 方法就区别于不传递 enhancer 时所创建的 dispatch 方法了,其中包含了中间件所定义的一些逻辑,这就是为什么中间件可以干预 dispatch 的原因。
const store = createStore(...args) //const store = createStore(reducer, preloadedState)
// 这里对 dispatch 进行了重新定义,不管传入什么参数,都会报错,这样做的目的是防止你的中间件在初始化的时候就
// 作用是执行时的校验,避免在执行函数时dispaching
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 = {
getState: store.getState,
dispatch: (...args) => dispatch(...args) // 注意最后 dispatch 的时候不会访问上面报错的那个 dispatch 函数了,因为那个函数被下面的 dispatch 覆盖了。
}
// 对于每一个 middleware,都传入 middlewareAPI 进行调用,这就是中间件的初始化。
// 初始化后的中间件返回一个新的函数,这个函数接受 store.dispatch 作为参数,返回一个替换后的 dispatch,作为新的
// store.dispatch。
const chain = middlewares.map(middleware => middleware(middlewareAPI))//然后将middlewareAPI作为参数,便利执行中间件数组函数。将返回的结果存储在chain数组
// compose 方法把所有中间件串联起来调用。用最终结果替换 dispatch 函数,之后所使用的所有 store.dispatch 方法都已经是
// 替换了的,加入了新的逻辑。
////store.dispatch作为参数执行chain数组中的每一个函数,具体为首先执行第一个函数,将store.dispatch作为实参传入。
//将其返回的结果作为第二个函数的参数,以此类推,将最后结果赋值给dispatch
dispatch = compose(...chain)(store.dispatch)
// 初始化中间件以后,把报错的 dispatch 函数覆盖掉。
/**
* middle 的标准形式:
* const middleware = ({ getState, dispatch }) => next => action => {
* // ....
* return next(action);
* }
* 这里 next 是经过上一个 middleware 处理了的 dispatch 方法。
* next(action) 返回的仍然是一个 dispatch 方法。
*/
return {
...store,
dispatch // 全新的 dispatch。
}
}
}
- 通过createStore方法创建出一个store
- 定义dispatch,如果在中间件构造过程中调用,抛出错误提示
- 定义middlewareAPI,有两个方法,一个是getState,另一个是dispatch,将其作为中间件调用的store的桥接
- middlewares调用Array.prototype.map进行改造,存放在chain
- 用compose整合chain数组,并赋值给dispatch
- 将新的dispatch替换原先的store.dispatch
通过闭包存储了最新的store值。
通过compose函数,使得每个中间件的next参数指向其后面的中间件函数。
最后一个中间件指向store.dispatch。
当触发action时,action会依次的经过中间件的处理。
在每个中间件中可以通过store.getState()取得最新的state值,通过dispatch可以从第一个中间件触发dispatch()。
通过调用next(action)触发下一个中间件函数
bindActionCreators.js
把 action creator 转化为可以直接使用的函数
bindActionCreators针对于三种情况有三种返回值
function bindActionCreator(actionCreator, dispatch) {
return function() {
return dispatch(actionCreator.apply(this, arguments))
}
}
// 接受一个 actionCreator(或者一个 actionCreators 对象)和一个 dispatch 函数作为参数,
// 然后返回一个函数或者一个对象,直接执行这个函数或对象中的函数可以让你不必再调用 dispatch。
export default function bindActionCreators(actionCreators, dispatch) {
// 如果 actionCreators 是一个函数而非对象,那么直接调用 bindActionCreators 方法进行转换,此时返回
// 结果也是一个函数,执行这个函数会直接 dispatch 对应的 action。
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 是一个对象,那么它的每一个属性就应该是一个 actionCreator,遍历每一个 actionCreator,
// 使用 bindActionCreator 进行转换。
const keys = Object.keys(actionCreators)
const boundActionCreators = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const actionCreator = actionCreators[key]
// 把转换结果绑定到 boundActionCreators 对象,最后会返回它。
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
bindActionCreators(actionCreators, dispatch)返回一个与原函数/对象相同的新函数,通过闭包保存了store.dispatch并且通过apply调整了this的指向。
直接执行返回的函数/对象的属性便可以触发数据的改变,使得我们不在需要将dispatch逐层传递,也使得我们可以像执行普通函数一样来触发action。
compose.js
嵌套调用中间件(middleware),进行初始化,将多个函数组合成一个函数 在源码的applyMiddleware函数中使用了
export default function compose(...funcs: Function[]) {
if (funcs.length === 0) {
// infer the argument type so it is usable in inference down the line
return <T>(arg: T) => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args: any) => a(b(...args)))
}
传入一系列的单参数函数作为参数(funcs 数组),返回一个新的函数,这个函数可以接受多个参数,运行时会将 funcs 数组中的函数从右至左进行调用。
- 新建一个新数组funcs,将arguments里面的每一项一一拷贝到funcs中去
- 当funcs的长度为0时,返回一个传入什么就返回什么的函数
- 当funcs的长度为1时,返回funcs第0项对应的函数
- 当funcs的长度大于1时,调用Array.prototype.reduce方法进行整合
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!