管理不断变化的 state 非常困难。如果一个 model 的变化会引起另一个 model 变化,那么当 view 变化时,就可能引起对应 model 以及另一个 model 的变化,依次地,可能会引起另一个 view 的变化。直至你搞不清楚到底发生了什么。state 在什么时候,由于什么原因,如何变化已然不受控制。 当系统变得错综复杂的时候,想重现问题或者添加新功能就会变得举步维艰。
* 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 () {}
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.' ) }
/** * 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. */ exportdefaultfunctioncreateStore(reducer, preloadedState, enhancer) { // 传参判断 if ( (typeof preloadedState === 'function' && typeof enhancer === 'function') || (typeof enhancer === 'function' && typeofarguments[3] === 'function') ) { thrownewError( 'It looks like you are passing several store enhancers to ' + 'createStore(). This is not supported. Instead, compose them ' + 'together to a single function.' ) } // preloadedState默认是undefined if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState preloadedState = undefined }
if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { thrownewError('Expected the enhancer to be a function.') } /// 函数柯里化 传入createStore作为参数 return enhancer(createStore)(reducer, preloadedState) }
if (typeof reducer !== 'function') { thrownewError('Expected the reducer to be a function.') } // 存储当前的reducer let currentReducer = reducer // 存储当前的state let currentState = preloadedState let currentListeners = [] let nextListeners = currentListeners // 判断是否正在分发事件 let isDispatching = false
/** * This makes a shallow copy of currentListeners so we can use * nextListeners as a temporary list while dispatching. * * This prevents any bugs around consumers calling * subscribe/unsubscribe in the middle of a dispatch. */ functionensureCanMutateNextListeners() { if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } }
/** * Reads the state tree managed by the store. * * @returns {any} The current state tree of your application. */ // 返回当前的state functiongetState() { if (isDispatching) { thrownewError( '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 }
/** * 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. */ functionsubscribe(listener) { if (typeof listener !== 'function') { thrownewError('Expected the listener to be a function.') }
if (isDispatching) { thrownewError( '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 // 浅拷贝一份当前的listener ensureCanMutateNextListeners() nextListeners.push(listener)
returnfunctionunsubscribe() { if (!isSubscribed) { return }
if (isDispatching) { thrownewError( '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) } }
/** * Dispatches an action. It is the only way to trigger a state change. * * The `reducer` function, used to create the store, will be called with the * current state tree and the given `action`. Its return value will * be considered the **next** state of the tree, and the change listeners * will be notified. * * The base implementation only supports plain object actions. If you want to * dispatch a Promise, an Observable, a thunk, or something else, you need to * wrap your store creating function into the corresponding middleware. For * example, see the documentation for the `redux-thunk` package. Even the * middleware will eventually dispatch plain object actions using this method. * * @param {Object} action A plain object representing “what changed”. It is * a good idea to keep actions serializable so you can record and replay user * sessions, or use the time travelling `redux-devtools`. An action must have * a `type` property which may not be `undefined`. It is a good idea to use * string constants for action types. * * @returns {Object} For convenience, the same action object you dispatched. * * Note that, if you use a custom middleware, it may wrap `dispatch()` to * return something else (for example, a Promise you can await). */ functiondispatch(action) { if (!isPlainObject(action)) { thrownewError( 'Actions must be plain objects. ' + 'Use custom middleware for async actions.' ) }
if (typeof action.type === 'undefined') { thrownewError( 'Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?' ) }
if (isDispatching) { thrownewError('Reducers may not dispatch actions.') }
const listeners = (currentListeners = nextListeners) for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() }
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} */ // 用作code splitting 或者 dynamical load reducer functionreplaceReducer(nextReducer) { if (typeof nextReducer !== 'function') { thrownewError('Expected the nextReducer to be a function.') }
currentReducer = nextReducer
// This action has a similiar 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. dispatch({ type: ActionTypes.REPLACE }) }
/** * 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 */ functionobservable() { 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) { thrownewTypeError('Expected the observer to be an object.') }
functionobserveState() { if (observer.next) { observer.next(getState()) } }
// 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({ type: ActionTypes.INIT })
functiongetState() { if (isDispatchi 大专栏阅读分析Redux源码ng) { thrownewError( '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.' ) }
/** * @param {any} obj The object to inspect. * @returns {boolean} True if the argument appears to be a plain object. */ exportdefaultfunctionisPlainObject(obj) { if (typeof obj !== 'object' || obj === null) returnfalse
let proto = obj while (Object.getPrototypeOf(proto) !== null) { proto = Object.getPrototypeOf(proto) }
functionsubscribe(listener) { if (typeof listener !== 'function') { thrownewError('Expected the listener to be a function.') }
if (isDispatching) { thrownewError( '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 // 浅拷贝一份当前的listener ensureCanMutateNextListeners() nextListeners.push(listener)
returnfunctionunsubscribe() { if (!isSubscribed) { return }
if (isDispatching) { thrownewError( '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) } }
functionreplaceReducer(nextReducer) { if (typeof nextReducer !== 'function') { thrownewError('Expected the nextReducer to be a function.') }
currentReducer = nextReducer
// This action has a similiar 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. dispatch({ type: ActionTypes.REPLACE }) }
// 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 }
if (typeof initialState === 'undefined') { thrownewError( `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' ) { thrownewError( `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.` ) } }) }
/** * Composes single-argument functions from right to left. The rightmost * function can take multiple arguments as it provides the signature for * the resulting composite function. * * @param {...Function} funcs The functions to compose. * @returns {Function} A function obtained by composing the argument functions * from right to left. For example, compose(f, g, h) is identical to doing * (...args) => f(g(h(...args))). */
store enhancer,可以翻译成store的增强器,顾名思义,就是增强store的功能。一个store enhancer,实际上就是一个高阶函数,它的参数是创建store的函数(store creator),返回值是一个可以创建功能更加强大的store的函数(enhanced store creator),这和React中的高阶组件的概念很相似。
一般来说一个store enhancer的代码结构如下:
1 2 3 4 5 6
functionenhancerCreator() { returncreateStore =>(...args) => { // do something based old store // return a new enhanced store } }
/** * Creates a store enhancer that applies middleware to the dispatch method * of the Redux store. This is handy for a variety of tasks, such as expressing * asynchronous actions in a concise manner, or logging every action payload. * * See `redux-thunk` package as an example of the Redux middleware. * * Because middleware is potentially asynchronous, this should be the first * store enhancer in the composition chain. * * Note that each middleware will be given the `dispatch` and `getState` functions * as named arguments. * * @param {...Function} middlewares The middleware chain to be applied. * @returns {Function} A store enhancer applying the middleware. */ exportdefaultfunctionapplyMiddleware(...middlewares) { returncreateStore =>(...args) => { // 创建一个新的store const store = createStore(...args) let dispatch = () => { thrownewError( 'Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.' ) }