preface
π‘ Why is data flow management important? React UI=render(data) UI=render(data) UI=render(data) UI=render(data) UI=render(data) UI=render(data) UI=render(data) UI=render(data) UI=render(data) UI=render(data) UI=render(data) UI=render(data) UI
In this article, we will briefly introduce the data flow management in React, from its own context to the concepts related to the third-party library Redux, and the implementation of redux dependencies.
Before the main article, a brief introduction to the concepts of data and state. React uses reusable components to build interfaces. Components are essentially finite state machines that can remember the current state of components and perform operations based on state changes. In React, this state is defined as state. React manages components by managing state. When state changes, React automatically performs the corresponding operation.
Data not only refers to the data returned by the server layer to the front end, but also the state in React. When the data changes, we need to change the state to cause the interface to change.
React’s own data flow scheme
Unidirectional data flow based on Props
React is a top-down one-way data flow. Container components and presentation components are the most common React component designs. The container component handles the complex business logic and data, and the presentation component handles the UI layer. Usually, we pull out the presentation component for reuse or encapsulation of the component library. The container component itself manages the state through state, setState updates the state to update the UI, and passes its state to the presentation component through props for communication
For simple communication, it is flexible to concatenate parent and sibling components based on props.
However, for the nested deep data flow component, AβBβCβDβE, the data of A needs to be passed to E, so we need to add this data to all the props of B/C/D, resulting in the introduction of some attributes that do not belong to the B/C/D of the intermediate component
Use the Context API to maintain global state
Context API is a component tree global communication method provided by React
Context is based on the producer-consumer pattern, corresponding to the three concepts in React: React. CreateContext, Provider, and Consumer. Create a set of providers by calling createContext. As the Provider of data, the Provider can send data to consumers at any level in its component tree, and consumers can not only read the data sent by the Provider but also read the subsequent updated value of the data
const defaultValue = {
count: 0.increment: () = >{}};const ValueContext = React.createContext(defaultValue);
<ValueContext.Provider value={this.state.contextState}>
<div className="App">
<div>Count: {count}</div>
<ButtonContainer />
<ValueContainer />
</div>
</ValueContext.Provider>
<ValueContext.Consumer>
{({ increment }) => (
<button onClick={increment} className="button">increment</button>
)}
</ValueContext.Consumer>
Copy the code
Usage before 16.3, createContext usage after 16.3, useContext usage
A simple illustration of Context workflow:
Not recommended prior to V16.3 due to various limitations
- The code is not simple and elegant enough: producers need to be defined
childContextTypes
andgetChildContext
Consumers need to defineChildTypes
To be able to accessthis.context
Access the data provided by the producer - Data cannot be synchronized in time: available in class component
shouldComponentUpdate
Return false orPureComponent
None of the subsequent components will be updated, which violates the Context mode setting and causes the producer and consumer to fail to synchronize in time
Since v16.3, shouldComponentUpdate can still propagate through components even if it returns false, changing the declaration to be more semantic, making Context a viable communication solution
Context is also managed by a container component, but Consumer and Provider are one-to-one. When the project complexity is high, there may be multiple providers and consumers, or even one Consumer needs to correspond to multiple providers
When the business logic of a component becomes very complex, more and more code is written, because we can only control the data flow within the component. This results in both Model and View in the View layer, and the business logic and UI implementation are in the same layer, which is difficult to maintain
So you need a real data flow management tool that takes all the data out of the UI layer and lets React focus on the View layer
Redux
Redux is a state container for JS applications, providing predictable state management
Redux’s three principles
- Single data source: The state of the entire application is stored in a single tree that exists only in a single store
- State is read-only: changes to state only trigger actions
- Make changes using pure functions: Reducer generates a new state based on the old state and incoming actions (similar to reduce, accept the previous state and the current action and calculate a new value)
Redux workflow
Immutability
To be mutable means to be able to change, immutability means to use unalterable
By default, JS objects and arrays are mutable, and it is possible to create an object or array by changing its contents
const obj = { name: 'FBB'.age: 20 };
obj.name = 'shuangxu';
const arr = [1.2.3];
arr[1] = 6;
arr.push('change');
Copy the code
To change an object or array, the address of the reference in memory has not changed, but the content has changed
To update immutably, the code must copy the original object/array, updating its copy body
const obj = { info: { name: 'FBB'.age: 20 }, phone: '177xxx' }
constcloneObj = { ... obj,info: { name: 'shuangxu'}}Shallow copy, deep copy
Copy the code
Redux expects all states to be immutable.
react-redux
React-redux is the React binding provided by Redux to help use Redux in React projects
Its API is simple, including a component Provider and a higher-order function connect
Provider
β Why does a Provider only pass a store when all the components it wraps can access the store’s data?
What does Provider do?
- To create a
contextValue
containsredux
The incomingstore
And according to thestore
To create thesubscription
, publish and subscribe aresubscription
do - through
context
The contextcontextValue
Transitive subcomponent
Connect
What does β Connect do?
Use the store provided by the context by the container component, and pass the state and dispatch returned by mapStateToProps and mapDispatchToProps to the UI component
The component depends on the state of redux, which maps to the props of the container component. A change in state triggers a change in the props of the container component, which triggers the container component component to update its view
const enhancer = connect(mapStateToProps, mapDispatchToProps)
enhancer(Component)
Copy the code
React-redux beggar version implementation
mini-react-redux
Provider
export const Provider = (props) = > {
const { store, children, context } = props;
const contextValue = { store };
const Context = context || ReactReduxContext;
return <Context.Provider value={contextValue}>{children}</Context.Provider>
};
Copy the code
connect
import { useContext, useReducer } from "react";
import { ReactReduxContext } from "./ReactReduxContext";
export const connect = (mapStateToProps, mapDispatchToProps) = > (
WrappedComponent
) = > (props) = > {
const { ...wrapperProps } = props;
const context = useContext(ReactReduxContext);
const { store } = context; // Deconstruct store
const state = store.getState(); / / to the state
// Use useReducer to get a force update function
const [, forceComponentUpdateDispatch] = useReducer((count) = > count + 1.0);
// Subscribe to state changes and execute callbacks when state changes
store.subscribe(() = > {
forceComponentUpdateDispatch();
});
// Execute mapStateToProps and mapDispatchToProps
conststateProps = mapStateToProps? .(state);constdispatchProps = mapDispatchToProps? .(store.dispatch);// Assemble the final props
const actualChildProps = Object.assign(
{},
stateProps,
dispatchProps,
wrapperProps
);
return <WrappedComponent {. actualChildProps} / >;
};
Copy the code
redux Middleware
“It provides a third-party extension point between Dispatching an action, And the moment it reaches the reducer. “– Dan Abramov
The Middleware middleware provides the opportunity to sort through actions, examining each one and picking out specific types of actions
Middleware example
Print log
store.dispatch = (action) = > {
console.log("this state", store.getState());
console.log(action);
next(action);
console.log("next state", store.getState());
};
Copy the code
Monitoring error
store.dispatch = (action) = > {
try {
next(action);
} catch (err) {
console.log("catch---", err); }};Copy the code
The two become one
store.dispatch = (action) = > {
try {
console.log("this state", store.getState());
console.log(action);
next(action);
console.log("next state", store.getState());
} catch (err) {
console.log("catch---", err); }};Copy the code
Extracting loggerMiddleware/catchMiddleware
const loggerMiddleware = (action) = > {
console.log("this state", store.getState());
console.log("action", action);
next(action);
console.log("next state", store.getState());
};
const catchMiddleware = (action) = > {
try {
loggerMiddleware(action);
} catch (err) {
console.error("Error report:", err); }}; store.dispatch = catchMiddlewareCopy the code
LoggerMiddleware is dead in catchMiddleware, and next(Store.dispatch) is dead in loggerMiddleware, so it needs to be flexible enough to accept the dispatch parameter
const loggerMiddleware = (next) = > (action) = > {
console.log("this state", store.getState());
console.log("action", action);
next(action);
console.log("next state", store.getState());
};
const catchMiddleware = (next) = > (action) = > {
try {
/*loggerMiddleware(action); * /
next(action);
} catch (err) {
console.error("Error report:", err); }};/*loggerMiddleware becomes a parameter */
store.dispatch = catchMiddleware(loggerMiddleware(next));
Copy the code
Accepting a store in Middleware lets you extract the above methods into a separate function file
export const catchMiddleware = (store) = > (next) = > (action) = > {
try {
next(action);
} catch (err) {
console.error("Error report:", err); }};export const loggerMiddleware = (store) = > (next) = > (action) = > {
console.log("this state", store.getState());
console.log("action", action);
next(action);
console.log("next state", store.getState());
};
const logger = loggerMiddleware(store);
const exception = catchMiddleware(store);
store.dispatch = exception(logger(next));
Copy the code
Each Middleware needs to take the Store argument to optimize the call function
export const applyMiddleware = (middlewares) = > {
return (oldCreateStore) = > {
return (reducer, initState) = > {
// Get the old store
const store = oldCreateStore(reducer, initState);
//[catch, logger]
const chain = middlewares.map((middleware) = > middleware(store));
let oldDispatch = store.dispatch;
chain
.reverse()
.forEach((middleware) = > (oldDispatch = middleware(oldDispatch)));
store.dispatch = oldDispatch;
return store;
};
};
};
const newStore = applyMiddleware([catchMiddleware, loggerMiddleware])(
createStore
)(rootReducer);
Copy the code
Redux offers applyMiddleware to load the middleware, applyMiddleware accepts three parameters, middlewares array/Redux createStore/reducer
export default function applyMiddleware(. middlewares) {
return createStore= > (reducer, ... args) = > {
Create a store by createStore and Reducer
conststore = createStore(reducer, ... args)let dispatch = store.dispatch
var middlewareAPI = {
getState: store.getState,
dispatch: (action, ... args) = >dispatch(action, ... args) }// Pass getState/dispatch to middleware,
// Map lets each middleware get the middlewareAPI parameter
// Form an array of chain anonymous functions [f1,f2,f3...fn]
const chain = middlewares.map(middleware= > middleware(middlewareAPI))
//dispatch=f1(f2(f3(store.dispatch)))dispatch = compose(... chain)(store.dispatch)return {
...store,
dispatch
}
}
}
Copy the code
ApplyMiddleware fits the onion model
conclusion
The purpose of this article is to explain react data flow management. Start with the data flow mode provided by React
- Based on the
props
The concatenation of parent and sibling components is very flexible, but for deeply nested components, the intermediate components will add unnecessary componentsprops
data - Use Context to maintain global state, which covers pre-v16.3 / post-v16.3 /hooks
context
As well as versions prior to V16.3context
The disadvantages of. - Redux, a third-party state container, and the React-Redux API(Provider/ Connect) analysis and hack implementation are introduced. Finally, how redux’s powerful middleware can rewrite the Dispatch method
Refer to the connection
- React state management and solution comparison
- React Context
- Redux middleware,
- Handwritten react – story