Redux concept
At its core, Redux is a publish-subscribe model.
The three basic principles of Redux:
- Single data source. There is usually only one store in Redux, which is used to store data.
- Read-only state. The only way to change the state is to trigger an action, an object that describes the information about the modified behavior. Because actions are simple objects, they can be recorded, serialized, stored, and debugged later.
- Use pure function operations to change state. To describe how an action can modify the state, use the Reducer function. The Reducer function accepts the previous state and action and returns the new state instead of changing the previous state. As long as the same state and action are passed in, the reducer will always return the same result no matter how many times it is called.
Redux source analysis
createStore
function createStore(reducer, preloadedState, enhancer) {
// Deleted some parameter correction code
if (typeofenhancer ! = ='undefined') {
if (typeofenhancer ! = ='function') {
throw new Error('Expected the enhancer to be a function.')}// Use of store enhancer, i.e. use of incoming middleware
// Enhancer is the applyMiddleware function mentioned later
return enhancer(createStore)(reducer, preloadedState)
}
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
// This method is used to keep the nextListeners and currentListeners in sync
NextListeners are shallow copies of currentListeners.
// Subscribe to nextListeners to prevent subscribe/unsubscribe bugs at Dispatch
// Simultaneous currentListeners and nextListeners (currentListeners = nextListeners)
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
// Obtain the current currentState
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
}
// Subscribe to a listener that operates on nextListeners
// Return a function to delete the subscribed listener, also operating on nextListeners
// Delete subscriptions will not take effect at the current dispatch, but will take effect at the next dispatch
function subscribe(listener) {
if (typeoflistener ! = ='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}}/ / triggers the action
// 1. Run the reducer command to update the state
// 2. Execute the subscribed listener
// Return the action passed in to debug the record
function dispatch(action) {
if(! isPlainObject(action)) {throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.')}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.')}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()
}
return action
}
// Replace the current reducer
function replaceReducer(nextReducer) {
if (typeofnextReducer ! = ='function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
// Triggers an internal REPLACE action
dispatch({ type: ActionTypes.REPLACE })
}
// Observable /reactive libraries
function observable() {
const outerSubscribe = subscribe
return {
subscribe(observer) {
if (typeofobserver ! = ='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}}}// Triggers an INIT action to create the initial state tree
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
Copy the code
ApplyMiddleware,
function applyMiddleware(. middlewares) {
// The function returned is the enhancer called in the createStore
return (createStore) = > (. args) = > {
// Create a store that can get dispatch and getState
conststore = createStore(... args)let dispatch = () = > {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.')}// Encapsulate the incoming middleware API
const middlewareAPI = {
getState: store.getState,
dispatch: (. args) = >dispatch(... args), }// Bind the middleware API to dispatch and getState
const chain = middlewares.map((middleware) = > middleware(middlewareAPI))
// Middleware is used to enhance dispatches. It's used to do something after you dispatch an action
// Compose is essentially used for concatenating middleware implementations
// This dispatch also overwrites the dispatches called middlewareAPIdispatch = compose(... chain)(store.dispatch)return {
...store,
dispatch,
}
}
}
Copy the code
Break downdispatch = compose(... chain)(store.dispatch)
The essence of compose, which combines multiple functions for concatenating middleware execution
const compose =(. middlewares) = > middlewares.reduce((f1, f2) = > (. args) = >f1(f2(... args)))Copy the code
Look for standard middleware first
({ dispatch, getState }) => (next) = > (action) = > {
// Do something
const state = next(action)
// Do something
return state
}
Copy the code
Dispatch = compose(… Chain (store.dispatch) can be understood as:
const middlewares = [ (next) => (action) => { console.log('middleware 1') next(action) console.log('middleware 1 after') }, (next) => (action) => { console.log('middleware 2') next(action) console.log('middleware 2 after') }, (next) => (action) => { console.log('middleware 3') next(action) console.log('middleware 3 after') }, ]Copy the code
Call the chain above
const compose = middlewares.reduce((f1, f2) = > (. args) = >f1(f2(... args)))// For example, if the middleware 1 is in the form of 'next', the middleware 2 is in the form of 'next', and the middleware3 is in the form of 'next'
// Next for Middleware 3 is the original dispatch passed in after calling compose
const dispatch = compose((action) = > {
console.log('origin dispatch', action)
})
// The bound Dispatch is executed, which is the same as the execution of each middleware. Each middleware passes action arguments to the original Dispatch
// The core principle of redux-thunk is to execute the passed action if it is of a function type
dispatch({ a: 3 })
/ / the log as below
middleware 1
middleware 2
middleware 3
origin dispatch {a: 3}
middleware 3 after
middleware 2 after
middleware 1 after
Copy the code
The essence of applyMiddleware is to bind middleware function parameters one by one to enhance the execution of store dispatches
combineReducers
combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
// Collect all the reducer functions passed in
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
// The combination function is executed at dispatch,
// Traverse all reducer functions. If one of the Reducer functions returns a new state, mark hasChanged to true.
All reducer functions will be executed once
// hasChanged ? Return the new state: Return the original state
return function combination(state = {}, action) {
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
}
}
Copy the code