This is the second day of my participation in the August Challenge. For more details, see:August is more challenging
The starting
When developing projects using React, you always need a state management tool. In VUE, there is Vuex, while in React, I believe many companies will choose Redux, but Redux is not designed for React. This is not as convenient for us to use, so there are react-Redux, DVA and other solutions.
When using DVA, there is a relatively complete set of solutions, but not all projects will use DVA. With React-Redux, we need to add various middleware for Redux, such as Redux-Thunk, Redux-Promise…… This article mainly implements redux, React-Redux, Redux-Thunk, redux-Promise. The common API of these libraries. Before this, you need to have some reserve knowledge. React and the API implemented in the article must be mastered in common use. In addition, you need to master functional programming.
Redux implementation
Before implementing Redux, we first need to take a look at our usual usage to determine the implementation requirements.
import {createStore, applyMiddleware, combineReducers} from '.. /redux/index'
export function countReducer(state = 0, action) {
// console.log('countReducer', state, action);
switch (action.type) {
case "ADD":
return state + (action.payload || 1);
case "MINUS":
return state - (action.payload || 1);
default:
returnstate; }}const store = createStore(combineReducers({counter: countReducer}), applyMiddleware(thunk, redux_promise));
export default store;
Copy the code
On the page we use getState to get the state, Dispatch to change the state, and subscribe to the execution after the change.Copy the code
For Redux we first need to implement createStore, GetState, Diapatch, and SUBSCRIBE are required for the createStore export instance, and applyMiddleWare and combineReducers are also required for the enhanced Store
createState
In this createStore, save and expose state, dispatch the caller to the reducer rules, and subscribe to the update.
const createStore = (reducer) = > {
// Store the state variables
let currentState
// As a dependency collection, the function iterates through the array to notify page updates
let currentListens = []
/ / getState implementation
const getState = () = > {
return currentState
}
// Implement the dispatch function
const dispatch = (action) = > {
// Each time we use Dispatch, we call the rules we define in real time to get the output
currentState = reducer(currentState, action)
// The state change notification component
currentListens.forEach(fn= > fn())
}
// Subscribe to the implementation
const subscribe = (callback) = > {
currentListens.push(callback)
let length = currentListens.length - 1;
Return a function that goes out to unsubscribe when the component is unloaded.
return () = > {
currentListens.splice(length, 1); }}// getState is null at this time, so we need to manually trigger the initial value
dispatch({type:"REDUX/XXXXXXXXXX"})
return {
getState,
dispatch,
subscribe
}
};
export default createStore
Copy the code
By writing here, you can get a redux without any bonus, which can be used normally. The page can be used normally with the original getState, Diapatch and subscribe, and no middleware can be added at this time.
Middleware applyMiddleware
When using Diapstch, only one object can be passed in to match the reducer defined by us. Therefore, the middleware is an enhancement of Dispatch, so that it can handle a function or promise, and the intermediate effect is simple. For example, redux-Thun only handles functions and redux-Pro Mise only deals with promises, and it is important that each middleware does not contaminate the other. We’re going to do aggregation and currying of programming with functions.
You first need an aggregate function that takes the return value of each function as an argument to the next function, returning a function to be executed. If you don’t understand this function, write a couple of demos to execute and log, and you’ll find it useful (called combinator in the link above).
function compose(. args) {
if(args.length === 0) {
return (arg) = > arg
}
if(args.length === 1) {
return args[0]}return args.reduce((a, b) = > {
return (. args) = >a(b(... args)) }) }Copy the code
The createStore function needs to be re-created first, because in use, the middleware accepts the parameter as the second parameter of the createStore function, so it is added at the top of the createStore function
const createStore = (reducer, enhancer) = > {
// If the middleware is not passed in, we can execute as usual
if(enhancer) {
// We need to use the original createStore function in the middleware function to get the normal store,dispatch.... And the reducer
return enhancer(createStore)(reducer)
}
......
}
Copy the code
There may be more than one piece of middleware in applyMiddleWare, so let them work separately and return the results to the next piece of middleware without any interaction.
const applyMiddleware = (. middlewares) = > {
Middlewares is the middleware we passed into the createStore
return createStore= > reducer= > {
/ / get a store (with getState in store, dispatch, subscribe)
const store = createStore(reducer);
let dispatch = store.dispatch;
const midApi = {
getState: store.getState,
// Since there is more than one middleware, incoming dispatches may interfere with each other
// Passing it in as a function causes the dispatch to be followed by a layered dispatch with no interaction between the middleware
dispatch: (action) = > {
return dispatch(action)
}
}
const middlewareChain = middlewares.map(middleware= > middleware(midApi))
// Reassign a function because there is more than one middleware so it needs to be executed like the Onion model. The last argument received by the middleware will be the store.dispatch passed indispatch = compose(... middlewareChain)(store.dispatch)// Make Dispatch a processed aggregate function.
return {
...store,
dispatch
}
}
}
Copy the code
For middleware writing is relatively simple, mainly for functional programming understanding, can use the official and this article written middleware and redux project mix.
redux-thunk
export default function thunk({getState, dispatch}) {
return next= > action= > {
console.log('thunk', next, action);
if(typeof action === 'function') {
// After the action is called here, the actions will call dispatch internally and cause all middleware to re-execute
return action(dispatch, getState)
}
returnnext(action); }}Copy the code
redux-promise
export default function redux_promise({getState, dispatch}) {
// Each time you use the compose aggregation function, you pass in the next middleware action to be performed
return next= > action= > {
console.log('promise', next, action);
// After calling action.then, the actions will call Dispatch internally and cause all middleware to re-execute
return isPromise(action)? action.then(dispatch): next(action)
}
}
function isPromise(action) {
return action instanceof Promise;
}
Copy the code
Performing next() at the top indicates that the middleware has no processing and goes to the next middleware. If there is processing, the diapatch change action is called, so you need to go through all the middleware again to process.
combineReducers
In addition, combineReducers is often used when it is used. Students who suddenly forget can search it. I believe that the memory will come to mind in a flash.
This API is relatively simple, I believe a look to understand, but also can be used normally.
export default function combineReducers(reducers) {
return (state = {}, action) = > {
let nextState = {}
let hasChanged = false
Object.keys(reducers).forEach(item= > {
letreducer = reducers[item] nextState[item] = reducer(state[item], action) hasChanged = hasChanged || nextState[item] ! == state[item] })returnnextState; }}Copy the code
React-redux: React-Redux: Redux: Redux: Redux: Redux: Redux: Redux: Redux: Redux: Redux: Redux: Redux: Redux: Redux: Redux
react-redux
As before, analyze what we need to implement before writing. First of all, we wrap our component with provider in React-Redux to ensure that our component can use the state before using it.
The connect high-level component is used when using a class component. MapStateToPrpos and mapDispatchToProps are accepted for embedding into props.
The function component uses useSelector to get the state and useDispatch to get the function that modifiers the state.
Provider
The first step is to implement the Provider. It accepts a store parameter. The store is the state created using the createStore, and the Context is used to manage the state inside the Provider.
// Pass the store by context
const Context = createContext()
export const Provider = ({ store, children }) = > {
return (
<Context.Provider store={store} >
{children}
</Context.Provider>)}Copy the code
connect
MapStateToPrpos and mapDispatchToProps. MapStateToPrpos and mapDispatchToProps. MapStateToPrpos and mapDispatchToProps Component returns.
export const bindActionCreators = (creators, dispatch) = > {
let obj = {}
// Core logic
for (const key in creators) {
obj[key] = bindActionCreator(creators[key], dispatch)
}
return obj;
}
const bindActionCreator = (creator, dispatch) = > {
// Return a function that passes arguments to the actual Dispatch for execution
return (. args) = > dispatch(creator(args))
}
// forceUpdate website in mock components recommends that the first parameter defined is executed every time the second function is called (rule)
const useForceUpdate = () = > {
const [, forceUpdate] = useReducer(x= > x + 1.0)
return forceUpdate;
}
export const connect = (mapStateToProps, mapDispatchToProps) = > (Cmp) = > props= > {
// Refresh is called every time the store is updated by passing in store.subscribe
const forceUpdate = useForceUpdate()
const store = useContext(Context)
const { getState, dispatch, subscribe } = store;
// Get the required state
const stateProps = mapStateToProps(getState())
// Retrieve the data returned by mapDispatchToProps
// mapDispatchToProps can be used in either function or object functions
// In the object is needed to take the object out and return each item with the dispatch package
let dispatchProps = {}
if (typeof mapDispatchToProps === 'object') { dispatchProps = { dispatch, ... bindActionCreators(mapDispatchToProps, dispatch) } }else if (typeof mapDispatchToProps === 'function') { dispatchProps = { dispatch, ... mapDispatchToProps(dispatch) } }// Subscribe to updates
useLayoutEffect(() = > {
const unSubscribe = store.subscribe(() = > {
forceUpdate()
})
return () = > {
// Unsubscribe when the component is uninstalled
if (unSubscribe) {
unSubscribe()
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [store])
// Output the output after the injection of the receiving component propr
return (
<Cmp {. props} {. stateProps} {. dispatchProps} / >)}Copy the code
So connect is done, and then there are the last two functions, useSelector and useDispatch.
useSelector/useDispatch
export const useDispatch = () = > {
const store = useContext(Context);
// Just return the dispatches in the store
return store.dispatch;
}
export const useSelector = (selector) = > {
const forceUpdate = useForceUpdate()
const store = useContext(Context)
const {getState} = store
// Subscribe to update this is easy to implement. If you use the useSelecot multiple times within a component, you will have repeated refresh problems
useLayoutEffect(() = > {
const unSubscribe = store.subscribe(() = > {
forceUpdate()
})
return () = > {
if (unSubscribe) {
unSubscribe()
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [store])
// Execute the passed function as an argument, and return the status
const state = selector(getState())
return state;
}
Copy the code
Here is the completion of the implementation, after testing can be mixed with the official library, to this end, I hope to help you. Source code in the implementation of more exciting, waiting for your exploration, refueling.
Online experience site click to arrive.
The last
I am 007 front cutters, thank you for reading. This article is all aspects of personal understanding after learning, if there are mistakes and flaws, thank you for giving corrections. Helpful words please ❤️ follow + like + collect + comment + forward ❤️