Redux fundamental analysis
preface
What will you learn from this article?
- Redux design ideas, basic principles and implementation.
- React-redux: React-Redux: React-Redux: React-Redux
- Design idea, basic principle and implementation of applyMiddleWare.
1. Design ideas of Redux
Redux knowledge brief
We know that Redux’s previous life is Flux, which mainly involves four aspects of things:
- action
- dispatch
- reducer
- store
The three features of Redux are:
- A single Store
- The state read-only
- Reducer is a pure function
Data flow in Redux is one-way data flow, meaning that data state changes can only be initiated through dispatch actions. Actions go to reducer for final processing, obtain the new store, and further trigger View update of the View.
To understand the design of Redux, first we need to know what problems Redux solves for us.
React, as a large component-based development framework, involves the communication of many components, such as parent-child communication and cross-level communication. Naturally, we need a place to store some common states. Redux is designed to solve this problem.
When it comes to state management, it is natural to use a separate file to manage state. Import can be implemented wherever these states are needed, so why redux?
Simple imports have several drawbacks
- Status is easily modified by mistake.
- Manipulation of states is difficult to understand.
How does Redux solve these problems? Closures make the state private and not directly modifiable, and provide a method dispatch to change the state so that the change of state is manageable. When the state changes, the component is notified that the state has changed and an attempted update is required.
XXX changes, inform XXX. Does that sound familiar? Yeah, that’s the observer model.
At its core, Redux exposes several APIS in a createStore method: getState, Dispatch, and Subscribe.
export const createStore = (reducer, preloadedState, enhancer) = > {
/ / property
let currentState = preloadedState || {};
let currentReducer = reducer;
let listeners = [];
// Higher order function to enhance createStore
if (enhancer) {
return enhancer(createStore)(reducer);
}
/ / method
function getState() {}
function dispatch() {}
function subscribe() {}
/ / initialization
dispatch({ type: "@@Redux.INIT" });
/ / the return value
return {
getState,
dispatch,
subscribe,
};
};
Copy the code
Let’s implement each of these methods in turn
1. getState()
Very simple, just return state
function getState() {
return currentState;
}
Copy the code
2. dispatch()
Ideas:
- The action was passed into the Reducer function for state processing, and the new state was obtained for overwriting and saving.
- State change notifies observer.
function dispatch(action) {
currentState = currentReducer(action);
for (let i = 0; i < listeners.length; i++) {
listeners();
}
return action;
}
Copy the code
3. subscribe()
Saves the observer and returns an unsubscribe function
function subscribe(listener) {
let isSubscribed = true;
listeners.push(listener);
return function unsubscribe() {
isSubscribed = false;
const index = listeners.indexOf(listener);
listeners.splice(index, 1);
};
}
Copy the code
End of redux implementation, isn’t that easy?
The total code:
export const createStore = (reducer, preloadedState, enhancer) = > {
/ / property
let currentState = preloadedState || {};
let currentReducer = reducer;
let listeners = [];
// Higher order function to enhance createStore
if (enhancer) {
return enhancer(createStore)(reducer);
}
/ / method
function getState() {
return currentState;
}
function dispatch(action) {
currentState = currentReducer(action);
for (let i = 0; i < listeners.length; i++) {
listeners();
}
return action;
}
function subscribe(listener) {
let isSubscribed = true;
listeners.push(listener);
return function unsubscribe() {
isSubscribed = false;
const index = listeners.indexOf(listener);
listeners.splice(index, 1);
};
}
/ / initialization
dispatch({ type: "@@Redux.INIT" });
/ / the return value
return {
getState,
dispatch,
subscribe,
};
};
Copy the code
2. React-redux design
React-redux has two core apis: Provider and Connect. How do they work?
- Provider puts a store into a Context for connect to retrieve.
- Connect is responsible for attaching state and Dispatch to the component’s props and allowing the component to observe state changes.
Both API implementations rely on a common file: Context, which is used to create Context objects.
1. Implementation of Provider
The Provider implementation uses the React Context implementation. Wrap with context.provider, passing store as the value for connect to use.
import React from "react";
import Context from "./Context";
export default function Provider({ store, children }) {
return <Context.Provider value={store}>{children}</Context.Provider>;
}
Copy the code
2. Implementation of CONNECT
- Connect gets a store on the Context, calls subscribe on the Store to observe the changes being observed, and updates the UI.
- Data is injected into props through higher-order functions
// connect returns a higher-order function that takes a component and returns a higher-order component.
import React from "react";
import Context from ".. /redux-core/Context.ts";
import { store } from ".. /write-simple-redux/src/redux";
export default function connect(mapStateToProps, mapDispatchToProps) {
return function (WrapperComponent) {
class MyConnect extends React.Component {
componentDidMount() {
console.log("context".this.context);
// Get the store from context and subscribe to updates
this.context.subscribe(this.handleStoreChange.bind(this));
}
handleStoreChange() {
// Update the UI view, simplified only
// In fact, it depends on the situation, will check whether the need to update etc
this.forceUpdate();
}
render() {
return (
<WrapperComponent
{. this.props}
{. mapStateToProps(this.context.getState()} {. mapDispatchToProps(this.context.dispatch)} / >
);
}
}
MyConnect.contextType = Context;
return MyConnect;
};
}
Copy the code
One small detail:
To review how connect is called: Connect (mapStateToProps, mapDispatchToProps)(App) Connect (mapStateToProps, mapDispatchToProps,App), it seems that there is no need to return a function to pass in the App. Why is react-Redux designed this way?
In fact, connect design is the implementation of the decorator pattern, the so-called decorator pattern is simply a wrapper on the class, dynamically expand the function of the class. Connect and HoC in React are implementations of this pattern. There is also a more immediate reason: the design is compatible with ES7 decorators, which allows us to simplify the code by using @connect. See react-Redux for the use of @connect decorators:
// Connect for normal use
class App extends React.Component {
render() {
return <div>hello</div>; }}function mapStateToProps(state) {
return state.main;
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(action, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
Copy the code
// Use decorators to simplify
@connect(
(state) = > state.main,
(dispatch) = > bindActionCreators(action, dispatch)
)
class App extends React.Component {
render() {
return <div>hello</div>; }}Copy the code
So that’s what the designers meant!
Redux, react-redux, react-redux, reacts-redux, reacts-redux, reacts-redux, reacts-redux, reacts-redux, reacts-redux
Github :write-simple-redux
Implementation of applyMiddleware and middleware
Redux middleware, which we can think of as an interceptor, is essentially an enhancement of Dispatch capabilities. It intercepts the dispatch submission to the Reducer, where some additional processing is done to increase capacity.
Taking logging middleware as an example, we want to output a call log every time we call dispatch. How to achieve this?
- The simplest shuttle is to manually print the log after each call to Dispatch
store.dispatch({ type: "xxx".payload: "xxx" });
console.log("Log:", store.getState());
Copy the code
- Encapsulate a logging method
function LogAfterDispatch(store, action) {
store.dispatch(action);
console.log("Log:", store.getState());
}
Copy the code
We need to manually import this method in every file that calls dispatch. It’s not elegant
- Replace the dispatch
let next = store.dispatch;
store.dispatch = function LogAfterDispatch(action) {
const res = store.dispatch(action);
console.log("Log:", store.getState());
return res;
};
Copy the code
- Modular middleware
A single piece of middleware is easy to maintain, but if you need to add functionality before, during, and after dispatch, the middleware will swell to the point where it is difficult to maintain. We want to achieve a freely assembled, independently pluggable middleware, this time we need to modular split.
function patchStoreToAndLog(store) {
let next = store.dispatch;
store.dispatch = function dispatchAndLog(action) {
const res = next(action);
console.log("after log", store.getState());
return res;
};
}
function patchStoreToCrashReport(store) {
let next = store.dispatch;
store.dispatch = function dispatchAndCrash(action) {
try {
next(action);
} catch (error) {
console.error("catch a error", error);
throwerror; }}; }// Combinable, pluggable middleware
patchStoreToAndLog(store);
patchStoreToCrashReport(store);
Copy the code
- Functional programming: composition, centralized insertion of middleware
We used dispatch overrides above, and we can return dispatches one at a time, using a combination of functional programming to connect
function applyMiddleware(. middlewares) {
return (createStore) = > (reducer) = > {
/ / create a store
const store = createStore(reducer);
const { getState, dispatch } = store;
const params = {
getState,
dispatch: (action) = > dispatch(action),
};
// Middleware store-> Dispatch -> Action
// The currization of the function fixes the first argument
const middlewares = middlewares.map((middleware) = > middleware(params));
// Functional programming: compositiondispatch = compose(... middlewares)(dispatch);// Return a new store
return { ...store, dispatch };
};
}
function compose(. fns) {
if (fns.length === 0) return () = > {};
if (fns.length === 1) return fns[0];
return fns.reducer(
(res, fn) = >
(. args) = >res(fn(... args)) ); }// Middleware format function currization
const LogAfterDispatch = (store) = > (next) = > (action) = > {
let res = next(action);
console.log("Log after dispatch", store.getState());
return res;
};
Copy the code
conclusion
Redux source is relatively short, very suitable for novice friends to read. As we read, we found that the source code used many programming ideas and design paradigms, such as: Observer pattern, decorator pattern, middleware mechanism, function currization, functional programming, etc. Reading the source code is not the end, but a beginning, and applying these ideas and design paradigms we have learned to our real projects is the ultimate meaning of technology.
Issue review
Going back to the beginning of the article, we asked three questions. Did you find the answers to these questions?
- Redux design ideas, basic principles and implementation.
Redux uses the observer mode for implementation. The component of the state in Redux is an observer, and the listeners add methods to update their views. When the state changes, event callbacks are triggered to update the listeners’ views.
- React-redux: React-Redux: React-Redux: React-Redux
The two core apis of React-Redux are Provider and Connect. They are essentially implemented using features of the React Context component.
- Create the Context object using a common file.
- Introduce the Context in the Provider, wrap the child component with context. Provider, and pass store as a value for Connect to fetch.
- Get the data from the Context in connect and subscribe to the View’s attempt to update method to the observer queue. State and Dispatch are injected into props through higher-order functions to display data.
- ApplyMiddleWare design ideas, the basic principle of implementation.
ApplyMiddleWare takes a variety of ideas: currization of functions, combinations of functional programming, and so on. ApplyMiddleWare is the ability to add middleware to dispatch. Redux middleware, which can be understood as an interceptor, intercepts the process from dispatch to reducer, and increases the capability of dispatch in this process. ApplyMiddleWare takes advantage of function currization to accept multiple parameters: middleware list, createStore method, reducer function, createStore, accept reducer parameters, etc. The store parameters of the middleware list were fixed by corritization, and then the combination of functional programming was used to combine all middleware to form an “Onion circle model”, which enhanced dispatch’s capability. Finally, a new store is returned.