一、 Redux 怎么使用?举个🌰
// 1. 设置默认数据
const initState = {
name: '',
age: -1,
};
// 2. 创建一个reducer
function reducer(state = initState, action) {
switch (action.type) {
case 'SET_NAME':
return { ...state, name: action.data };
case 'SET_AGE':
return { ...istate, age: action.data };
default:
return state;
}
}
// 3. 创建store
const store = createStore(reducer);
// 4. 分发action修改state
store.dispatch({ type: 'SET_AGE', data: 22 });
// 5. 订阅 store,传递一个函数,只要 store 数据改变,这个函数就会被执行
store.subscribe(() => {
console.log(store.getState());
});
二、文件结构
三、源码分析
- index.js
这个文件是整个Redux的入口文件,主要暴露了一些redux API
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
}
- 最核心的createStore.js
import $$observable from 'symbol-observable'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
export default function createStore(reducer, preloadedState, enhancer) {
// preloadedState必须是一个对象,enhancer必须是一个function
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.'
)
}
// 如果preloadedState为function且enhancer为undefined, 说明没有传初始state,但是存在一个enhancer,此时把preloadedState变为enhancer
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
// 如果传入的参数符合enhancer的执行条件,就会执行enhancer(增强函数),其实就是执行中间件,其需要与applyMiddleWare函数配合。这里先不讨论,后续说到applyMiddleWare函数时再详细说明,我们现在只知道它做了一些处理然后重新调用了createStore并且enhancer参数为undefined
return enhancer(createStore)(reducer, preloadedState)
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
let currentReducer = reducer // 当前reducer
let currentState = preloadedState // 当前的State
let currentListeners = [] // 当前订阅的监听函数队列
let nextListeners = currentListeners // 浅拷贝当前的监听函数队列
let isDispatching = false // 标识是否正在进行dispatch
// 对 currentListeners 做了浅拷贝以便于可以在 dispatch 的时候使用 nextListeners 作为临时的 listener。这样可以防止使用者在 dispatch 的时候调用 subscribe/unsubscribe 出现 bug。避免相互影响
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
// store.getState() 获取store 当前的state
function getState() {
// 如果当前正在进行dispatch,不可以读取state, 这样做是为了确保获取到的state是最新的
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
}
// store.substribe 用来设置监听函数,当调用dispatch改变state后,会执行所有的监听函数
function subscribe(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#subscribelistener for more details.'
)
}
let isSubscribed = true
ensureCanMutateNextListeners()
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#subscribelistener for more details.'
)
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
// dispatch 函数,通过传入一个action来修改state,这也是外部修改state的唯一方式(ps: 当然也可以通过store.getState()获取到state的引用,然后直接修改也是可以的(不会自动调用监听函数),但强烈建议不要这样做,)
function dispatch(action) {
// action 必须要是一个普通对象,既action.__proto__ == Object.protptype
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.')
}
try {
isDispatching = true
// 执行reducer返回一个新的state,然后重新赋值给currentState,这就是为什么dispatch能改变state的原因
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// dispatch过后执行所有的监听函数
// 将nextListeners赋值给currentListeners和listeners
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
// replaceReducer 函数用于计算替换当前store中的reducer
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
// 重新发送一个dispatch初始化state
dispatch({ type: ActionTypes.REPLACE })
}
// 目前没有用过这个函数,这里先不解释
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.
*/
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
}
}
}
// 当我们创建了一个store之后,state并没有被初始化,所以发送一个init dispatch返回我们reducer中定义的初始化state
dispatch({ type: ActionTypes.INIT })
// 暴露出去的store api
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
- combineReducers.js
顾名思义,这个函数是用来合并多个reducer作为一个reducer的,当我们业务很复杂时,通常想分出多个reducer,每个业务场景给一个单独的reducer,但是createStore又只能传入一个reducer,这个时候就需要combinereducers
import ActionTypes from './utils/actionTypes'
import warning from './utils/warning'
import isPlainObject from './utils/isPlainObject'
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.`
)
}
}
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.`
)
}
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.`
)
}
})
}
export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
// 将传入的 reducers 做一个浅拷贝,并且剔除不是 Function 的 reducer。finalReducers为最终有效的reducers
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]
}
}
const finalReducerKeys = Object.keys(finalReducers)
// This is used to make sure we don't warn about the same
// keys multiple times.
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
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
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') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
hasChanged =
hasChanged || finalReducerKeys.length !== Object.keys(state).length
return hasChanged ? nextState : state
}
}
-
compose
这是五个
API里唯一一个能单独拿出来用的函数,就是函数式编程里常用的组合函数,和redux本身没有什么多大关系。先了解下函数式编程的一些概念:
纯函数是这样一种函数,即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用。
其实compose函数想要做的事就是把 const res = fn1(fn2(fn3(fn4(x)))) 这个嵌套的调用方式改为 const res = compose(fn1, fn2, fn3, fn4)(x)
举个🌰
import { compose } from 'redux'
const x = 1;
const fn1 = x => x + 1;
const fn2 = x => x + 2;
const fn3 = x => x + 3;
const fn4 = x => x + 4;
// 如果这里我想求得这样的值
const a = fn1(fn2(fn3(fn4(x)))); // 1 + 4 + 3 + 2 + 1 = 11
// 可以直接使用compose
const b = compose(fn1, fn2, fn3, fn3)(1) // b同样等于11,与上面是一样的效果
compose函数的实现主要使用了Array.prototype.reduce
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
// redux实现compose的方式比较优雅,可能你难以理解,我们可以写个容易理解的版本
function compose(...funcs) {
return (...args) => {
let res;
for (let i = funcs.length - 1; i >= 0; i--) {
res = funcs[i](...args);
}
return res;
}
}
- applyMiddenWare
applyMiddleware用来添加中间件,在修改数据的时候redux通过改造dispatch来实现中间件。 applyMiddleWare接收多个middleware作为参数,redux中间件有规定的函数签名,即:
const middleware = ({ dispatch, getState }) => next => action => next(action)
import compose from './compose'
export default function applyMiddleware(...middlewares) {
// 当我们调用applyMiddleware来使用中间件时,会把之前的creteStore函数传到这里,args就是reducer和preloadedState
return createStore => (...args) => {
// 创建最初的store
const store = createStore(...args)
// 定义一个dispatch函数,调用它会throw error,在middleware创建时,不能调用
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
// 定义一个middlewareAPI对象,这个对象中有getState和dispatch两个属性,将做为中间件的参数
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
// 将用户传入的中间件依次执行,并将middrewareAPI作为参数传给各个middleware
// 此时chain的值为各个middleware返回值组成的数组
// [next => action => { do something return next(action) }, next => action => { do something return next(action) }]
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 更新dispatch, 通过compose形成一个调用链,返回一个加强版dispatch,next指代下一个函数的注册, 这就是中间件的返回值要是next(action)的原因,如果执行到了最后,next就是原始的store.dispatch方法
dispatch = compose(...chain)(store.dispatch)
// 返回一个加强版的store
return {
...store,
dispatch
}
}
}
看一下redux-thunk源码(Redux不支持异步dispatch,thunk中间件用来解决此问题):
// 使用它thunk中间件之后,在业务代码中我们可以dispatch一个函数
function getData(dispatch) {
const data = await new Promise(resolve => {
setTimeout(() => {
resolve([1])
}, 1000)
});
dispatch({ type: 'SET_LIST', payload: data });
}
useEffect(() => {
dispatch(getData)
}, [])
// 源码
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
// 如果dispatch的是一个函数(可以是异步的),执行这个函数,且传入dispatch和getState参数,
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
我们来实现一个超级简单的logger中间件, 在dispatch前后打印日志
export default function logger() {
return ({ getState }) => next => action => {
console.log('will dispatch', action, getState())
const res = next(action)
console.log('did dispatch', getState())
return res
}
}
怎么使用
import thunkMiddleware from 'redux-thunk'
import { createStore, applyMiddleware } from 'redux'
const store = createStore(reducer, applyMiddleware(thunkMiddleware, logger))