The upper part is the English version, and the lower part is the Chinese version, with the same content. If there are any wrong, or you have anything hard to understand, pls feel free to let me know.many thx.
Overview
Analyze the principle and source code of Redux
1. Redux data flow
Action: plain javascript object,define what happened
Reducer: fn(state,action), return new state
State: {},define state of ui component
Store: manage action and reducer and state
export default function createStore(reducer, preloadedState, enhancer) {
// first,validate passed params
...
// define internal variables
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
//copy currentListeners into nextListeners
//subcribe/unsubcribe uses nextListeners,dispatch uses currentListeners
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
//read state
function getState() {
...
return currentState
}
//triggered when state changes
function subscribe(listener) {
...
//add into nextListeners
nextListeners.push(listener)
return function unsubscribe() {
//remove the listener from nextListeners
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
function dispatch(action) {
//first,validate passed params)
...
try {
isDispatching = true
//exec reducer
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
//triger every listener
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
//1. accept a reducer to replace the current reducer
//2. then execute dispatch({ type: ActionTypes.REPLACE}) to initialize the state of the store
function replaceReducer(nextReducer) {
...
currentReducer = nextReducer
dispatch({ type: ActionTypes.REPLACE })
}
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable,
}
}
dispatch (publish), getState (get state), subscribe (listen) three core APIs
The unsubscribe method is the return value of the subscribe method
subscribe: add callback function to nextListeners, unsubscribe: delete from nextListeners
dispatch: execute reducer->execute each listener
A state update process of redux: dispatch trigger -> execute reducer -> execute all listeners (executing a subscribe will add a listener to the nextListeners array)
export default function combineReducers(reducers) {
...
return function combination(state = {}, action) {
...
// each reducer will actually be executed
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)
...
}
...
}
> Generally, one module has one reducer, and multiple reducers are combined into a large reducer by combineReducers
> combineReducers will still execute all reducers every time, [but the official said that there will be no performance problems in doing so](https://redux.js.org/faq/performance#wont-calling-all-my-reducers-for-each-action-be-slow)
#### 3.4 bindActionCreators.js
[bindActionCreators source code](https://github.com/reduxjs/redux/blob/4.x/src/bindActionCreators.js)
//After the action is executed, dispatch is called automatically
function bindActionCreator(actionCreator, dispatch) {
return function () {
return dispatch(actionCreator.apply(this, arguments))
}
}
export default function bindActionCreators(actionCreators, dispatch) {
...
const boundActionCreators = {}
const boundActionCreators = {}
//iterate the object and generate a function that wraps the dispatch according to the corresponding key
for (const key in actionCreators) {
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
> bind actionCreator and dispatch together
Example using bindActionCreators
// using in business code as bellow
this.props.todo({})
//note: only use mapDispatchToProps and pass in the Actions object, the effect is the same as above, so not used much
const mapDispatchToProps = {
...todoActions
}
> applyMiddleware.js, compose.js are placed below for separate analysis
### 4. Redux middleware
#### 4.1 Invoke flow after adding middleware
![call2](https://raw.githubusercontent.com/mominger/MyPublicFiles/master/img/20220217201830.png)
>Middleware is used to handle side effects, such as asynchronous requests
#### 4.2 How to use
// add a middleware
const store = createStore(reducer, applyMiddleware(middlewareA));
export default function applyMiddleware(...middlewares) {
return (createStore) => (...args) => {
const store = createStore(...args)
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
//define the parameters passed to the middleware: {getState,dispatch}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args),
}
//pass to each middleware: {getState,dispatch}
const chain = middlewares.map((middleware) => middleware(middlewareAPI))
//pass to each middleware: {store.dispatch}
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch,
}
}
export default function applyMiddleware(...middlewares) {
return (createStore) => (...args) => {
const store = createStore(...args)
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
The upper part is the English version, and the lower part is the Chinese version, with the same content. If there are any wrong, or you have anything hard to understand, pls feel free to let me know.many thx.
Overview
1. Redux data flow
2. Invoke flow
3. Source code
source code
3.1 Entrance
index.js source code
3.2 createStore.js
createStore source code
//After the action is executed, dispatch is called automatically function bindActionCreator(actionCreator, dispatch) { return function () { return dispatch(actionCreator.apply(this, arguments)) } }
export default function bindActionCreators(actionCreators, dispatch) { ...
}
const mapStateToProps = (state: any) => ({ todos: state.todos })
const mapDispatchToProps = (dispatch: Dispatch) => bindActionCreators( todoActions, dispatch ) export default connect(mapStateToProps, mapDispatchToProps)(App)
// using in business code as bellow this.props.todo({})
//note: only use mapDispatchToProps and pass in the Actions object, the effect is the same as above, so not used much const mapDispatchToProps = { ...todoActions }
// add a middleware const store = createStore(reducer, applyMiddleware(middlewareA));
export default function applyMiddleware(...middlewares) { return (createStore) => (...args) => { const store = createStore(...args) let dispatch = () => { throw new Error( 'Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.' ) }
}
export default function compose(...funcs) { if (funcs.length === 0) { return (arg) => arg }
}
//for understanding the principle of applyMiddleware const applyMiddleware = (middlewares) => { middlewares.forEach((middleware) => { const dispatch = store.dispatch; store.dispatch = middleware(store)(dispatch); }); };
//execute compose const dispatch = compose(f1, f2, f3); dispatch(args);
//is same with f1(f2(f3()))
const logMiddleware = (store) => { const next = store.dispatch;
};
// ({ dispatch, getState }) corresponds to const chain = middlewares.map(middleware => middleware(middlewareAPI)) // next parameter corresponds to dispatch = compose(...chain)(store.dispatch),which is each middleware(rewritten dispatch)
const logMiddleware = ({ dispatch, getState }) => (next) => (action) => {
next(action);
console.log('time:', new Date().getTime());
};
export default function createStore(reducer, preloadedState, enhancer) { //首先验证传进来的参数 ...
}
export default function combineReducers(reducers) { ... return function combination(state = {}, action) { ... // 实际会执行每一个reducer 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) ... } ... }
//执行action后,自动dispatch function bindActionCreator(actionCreator, dispatch) { return function () { return dispatch(actionCreator.apply(this, arguments)) } }
export default function bindActionCreators(actionCreators, dispatch) { ...
}
const mapStateToProps = (state: any) => ({ todos: state.todos })
const mapDispatchToProps = (dispatch: Dispatch) => bindActionCreators( todoActions, dispatch ) export default connect(mapStateToProps, mapDispatchToProps)(App)
// 在业务代码里如下调用 this.props.todo({})
//注意: 只使用mapDispatchToProps,传入Actions对象,效果和上面一致, 所以用得不多 const mapDispatchToProps = { ...todoActions }
// 添加一个中间件 const store = createStore(reducer, applyMiddleware(middlewareA));
export default function applyMiddleware(...middlewares) { return (createStore) => (...args) => { const store = createStore(...args) let dispatch = () => { throw new Error( 'Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.' ) }
}
export default function compose(...funcs) { if (funcs.length === 0) { return (arg) => arg }
}
//用于理解applyMiddleware的原理 const applyMiddleware = (middlewares) => { middlewares.forEach((middleware) => { const dispatch = store.dispatch; store.dispatch = middleware(store)(dispatch); }); };
//调用compose const dispatch = compose(f1, f2, f3); dispatch(args);
//相当于 f1(f2(f3()))
const logMiddleware = (store) => { const next = store.dispatch;
};
// ({ dispatch, getState }) 对应 const chain = middlewares.map(middleware => middleware(middlewareAPI)) // next 参数 对应 dispatch = compose(...chain)(store.dispatch),是每个Middleware(重写后的dispatch)
const logMiddleware = ({ dispatch, getState }) => (next) => (action) => {
next(action);
console.log('time:', new Date().getTime());
};