preface
State management:
- Redux: Operations are synchronous, and asynchronous actions require plug-ins
- Rematch: Redux-based state management library
- UseReducer: Simple state management using React Hook
- Others: unstated-next: state management library using the React API;
Code: the react – test
Previous notes:
- React Routing and lazy loading
- React CSS Module
- React Use: Hooks and data display handling
Asynchronous action
-
Why asynchronous Actions?
The simplest way to do this is to request the component asynchronously and then
dispatch
Update, but once multiple components are used and the state needs to be updated, the logic needs to be written in multiple places… -
When to use asynchronous Action?
- The asynchronous request and dispatch function can be wrapped separately, and the components needed are simply introduced into the wrapped function (action), plus plug-ins such as
redux-thunk
Make it possible for Dispatch to receive function arguments, and then dispatch that function - I don’t know the actual application scenarios. Thank you, big guyThis articleWhy do asynchronous actions in action?
- The asynchronous request and dispatch function can be wrapped separately, and the components needed are simply introduced into the wrapped function (action), plus plug-ins such as
Data persistence (localStorage/sessionStorage)
- The easiest way to write this without using plug-ins:
In fact, it’s best to choose only some if possibleRequired fieldsCache –
// src/store/index.js
// omit other code
// A list of things to cache
const cacheList = ['numReducer'.'countReducer'];
let stateCache = sessionStorage.getItem('store');
// Initializing state
const initState = (stateCache && JSON.parse(stateCache)) || {};
// stroe: { subscribe, dispatch, getState, replaceReducer }
const store = createStore(reducers, initState, applyMiddleware(ReduxThunk));
// Listen for each state change
store.subscribe((a)= > {
const state = store.getState();
let stateData = {};
Object.keys(state).forEach(item= > {
if (cacheList.includes(item)) stateData[item] = state[item];
});
sessionStorage.setItem('store'.JSON.stringify(stateData));
});
Copy the code
- Plug-in redux – persist
1, the story/react – story
1.1 installation
npm i -S redux
Copy the code
npm i -S react-redux
Copy the code
1.2 redux
-
1.2.1 combineReducers
Merge multiple Reducer
// src/store/index.js
import { combineReducers, createStore } from 'redux';
import countReducer from './count-reducer.js';
import numReducer from './num-reducer.js';
// Reducers
const reducers = combineReducers({
countReducer,
numReducer
});
// stroe: { subscribe, dispatch, getState }
const store = createStore(reducers);
// ...
export default store;
Copy the code
-
1.2.2 createStore
Const store = reducer (Reducer, [preloadedState], enhancer); Parameters: Extracted from Store
-
Reducer (Function): Receive two parameters, the current state tree and the action to be processed, and return a new state tree.
-
[preloadedState] (any): indicates the initial state. In homogeneous applications, you can decide whether to post a state hydrate from the server, or recover one from a previously saved user session. If you create a Reducer using combineReducers, it must be a normal object with the same structure as the keys passed in. Otherwise, you are free to pass in anything that the Reducer can understand.
-
Enhancer (Function): Store enhancer is a higher order Function that combines Store Creator and returns a new enhanced Store creator. This is similar to Middleware in that it also allows you to change the Store interface through composite functions. ApplyMiddleware is one such implementation
Store has four methods:
{ subscribe, dispatch, getState, replaceReducer }
- store.subscribe
Subscribe: here can do persistent storage (localStorage/sessionStorage), vuex is the place to do in the store
// You can subscribe to updates manually let unsubscribe = store.subscribe((a)= > { const state = store.getState(); console.log(state); }); // Remove the listener unsubscribe(); Copy the code
- store.dispatch
Send/dispatch: The only way to change the internal state is to dispatch an action. You can call it right here and change the state
store.dispatch({ type: 'INCREMENT' }); Copy the code
- store.getState
Get the initial state
// Get the initial state const state = store.getState(); Copy the code
-
Project Entrance:
// src/index.js
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import store from '@/store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>.document.getElementById('root'));Copy the code
1.3 the react – story
-
1.3.1 the Provider
// src/index.js
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import store from '@/store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>.document.getElementById('root'));Copy the code
-
1.3.2 the connect
Associate the React component with Redux;
Format: connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
- MapStateToProps: Injected state status
- MapDispatchToProps: Inject the Dispatch method
- MergeProps: merging properties, complex, simple scene, no need to…
- Options: Customize the behavior of the connector.
// src/components/redux-1.jsx
import { connect } from 'react-redux';
import React from 'react';
import { connect } from 'react-redux';
// omit other code
class ReduxTest2 extends React.Component {... }function mapStateToProps(state) {
return {
count: state.count
};
}
export default connect(mapStateToProps)(ReduxTest2);
Copy the code
// src/components/redux-2.jsx
// omit other code
class ReduxTest1 extends React.Component {... }function mapDispatchToProps(dispatch) {
return {
dispatch,
Add: (a)= > {
return dispatch({ type: 'INCREMENT' });
},
Todo: todo= > dispatch({ type: 'TODO_LIST'.todoList: todo })
};
}
// Inject only dispatches, do not listen to stores
export default connect(
null.// If only dispatch is required and state is not required, a placeholder is required
mapDispatchToProps
)(ReduxTest1);
Copy the code
1.4 Preparation before use
1.4.1 reducer
Form :(state, action) => state; Specifies how changes in application state should be sent to the store in response to actions. Actions are only descriptions of triggered events and Reducer is the executor. Return a new state
-
1.4.1.1 reducer concentration
// src/store/index.js
import { combineReducers, createStore } from 'redux';
import countReducer from './count-reducer.js';
import numReducer from './num-reducer.js';
// Reducers
const reducers = combineReducers({
countReducer,
numReducer
});
// stroe: { subscribe, dispatch, getState }
const store = createStore(reducers);
// ...
export default store;
Copy the code
-
1.4.1.2 reducer module
After multiple reducer combineReducers are used, the result of the reducer operation is reflected in the state of the respective module, such as state.countreducer. If multiple reducers have the same action. Type, dispatch will be called after the reducer is executed and reflected to their respective states
Click count++ twice:
Click the todo:
// src/components/redux-test/redux-2.jsx. render() {return (
<React.Fragment>
<div>todo: {this.props.todoList}</div>
<div>countReducer: {this.props.count}</div>
<div>numReducer: {this.props.count1}</div>
</React.Fragment>); }... function mapStateToProps(state) { return { todoList: state.countReducer.todoList, count: state.countReducer.count, count1: state.numReducer.count, json: state.countReducer.json }; }...Copy the code
// src/components/redux-test/redux-1.jsx. render() {return (
<React.Fragment>
<div>
<button
onClick={()= >This.props.Todo(' what '+ new Date().getTime())} > Todo</button>
</div>
<div>
<button onClick={this.props.Add}>count++</button>
</div>
</React.Fragment>); }... function mapDispatchToProps(dispatch) { return { dispatch, Add: () => dispatch({ type: 'INCREMENT' }), Todo: todo => dispatch({ type: 'TODO_LIST', todoList: todo }) }; }...Copy the code
// src/store/reducers/count-reducer.js
import { INCREMENT, TODO_LIST, JSON_DATA } from '.. /types';
// The default value of the parameter is lower than the initial initState of createState
function countReducer(state = { count: 0 }, action) {
switch (action.type) {
case INCREMENT:
return {
...state,
count: state.count + 1
};
case TODO_LIST:
return {
...state,
todoList: action.todoList
};
case JSON_DATA:
return {
...state,
json: action.data
};
default:
returnstate; }}export default countReducer;
Copy the code
// src/store/reducers/num-reducer.js
import { INCREMENT } from '.. /types';
// The default value of the parameter is lower than the initial initState of createState
function numReducer(state = { count: 0 }, action) {
switch (action.type) {
case INCREMENT:
return {
...state,
count: state.count + 2
};
default:
returnstate; }}export default numReducer;
Copy the code
1.5 dispatch
A parameter to mapDispatch2Props to call a reducer (as determined by action); The only way to change the internal state is to dispatch an action
1.6 the action
You must use a type field within the action as a string to indicate the action to be performed, typically as an argument to Dispatch
1.6.1 Asynchronous Actionredux-thunkThe plug-in
Enable Dispatch to support function arguments; If both components use the same state and need to update the state after an asynchronous operation, use redux-thunk and dispatch to pass in the asynchronous action function to avoid writing the same logic in both places
The following code is not very standard, usually asynchronous action, should have three action.type corresponding to the request, success, failed, lazy, use the same action
Note: The dispatch of an asynchronous action is passed in as a custom asynchronous action function, and then dispatched inside the action function!! This is essentially the same as asynchronous request followed by manual dispatch, but it avoids repeating code in multiple places and is more readable
- action
// src/store/actions/index.js
// Request data asynchronously
export function asyncAction({ url = './manifest.json', type }) {
return dispatch= > {
dispatch({ type: type, data: 'loading' });
return fetch(url)
.then(res= > res.json())
.then(json= > {
return dispatch({ type: type, data: json });
})
.catch(err= > {
return dispatch({ type: type, data: err });
});
};
}
Copy the code
- reducer
// src/store/reducers/count-reducer.js
// omit other code
// The default value of the parameter is lower than the initial initState of createState
function countReducer(state = { count: 0 }, action) {
switch (action.type) {
...
case 'JSON_DATA':
return {
...state,
json: action.data
};
default:
returnstate; }}export default countReducer;
Copy the code
- dispatch
// src/store/index.js
// omit other code
import { asyncAction } from '@/store/actions';
store.dispatch(
asyncAction({
url: './manifest.json'.type: 'JSON_DATA'}));Copy the code
1.7 Starting Use
Store directory structure:
D:\code\react-t1\ SRC \store ├─ exercises index.js // store import ├─ reducers // reducer Manu-reduce.js ├ ─ 07.txt // manu-reduce.txt // manu-reduce.txt // manu-reduce.txtCopy the code
1.7.1 Project Entrance
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './reducers';
import App from './App';
import * as serviceWorker from './serviceWorker';
import './index.css';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>.document.getElementById('root'));// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
Copy the code
1.7.2 mapState2Props
Receives an argument, state, and returns an object, which is merged to the props, usually referencing the store.state value
// src/components/redux-test/redux-2.jsx
import React from 'react';
import { connect } from 'react-redux';
class ReduxTest2 extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<React.Fragment>
<div>todo: {this.props.todoList}</div>
<div>countReducer: {this.props.count}</div>
<div>numReducer: {this.props.count1}</div>
</React.Fragment>
);
}
}
function mapStateToProps(state) {
return {
todoList: state.countReducer.todoList,
count: state.countReducer.count,
count1: state.numReducer.count,
json: state.countReducer.json
};
}
export default connect(mapStateToProps)(ReduxTest2);
Copy the code
1.7.3 mapDispatch2Props
- Receiving a parameter
dispatch
.return
An object, and this object will bemerge
到props
On; - The general is
store
Event function of - The object’s
key
The value of theta is generally zerodispatch
aaction
thenreducer
Internal processing returns a new onestate
, see”1.4.1 reducer“
// src/components/redux-test/redux-1.jsx
import React from 'react';
import { connect } from 'react-redux';
import { asyncAction } from '@/actions';
/ / redux exercises
class ReduxTest1 extends React.Component {
constructor(props) {
super(props);
}
componentDidMount = (a)= > {
/ / asynchronous action
this.props.dispatch(
asyncAction({ url: './manifest.json'.type: 'JSON_DATA'})); }; render() {return (
<React.Fragment>
<div>
<button
onClick={()= >This.props.Todo(' what '+ new Date().getTime())} > Todo</button>
</div>
<div>
<button onClick={this.props.Add}>count++</button>
</div>
</React.Fragment>); } } function mapDispatchToProps(dispatch) { return { dispatch, Add: () => { return dispatch({ type: 'INCREMENT' }); }, Todo: todo => dispatch({ type: 'TODO_LIST', todoList: todo }) }; } // Store export default connect(null) There must be a placeholder mapDispatchToProps (ReduxTest1);Copy the code
2. Use of rematch
Rematch is Redux best practice without boilerplate. There is no redundant Action types, Action Creators, switch statements, or Thunks. It’s not that different from Redux, but it’s a bit less code, cleaner and more comfortable to use
There are a few points:
- It doesn’t need to be set separately
action.type
By the typeModule name + '/' + Reducers key
Automatically generated,
如{ type: 'countRematch/increment' }
effects:{}
Properties: Receiving methods such as asynchronous actions with async/await eliminates the need for libraries such as redux-thunkredux:{}
Property: Redux compatible- Init initializes a store using redux
createStore
Generated store some methods, such asstore.subscribe
Every time can monitor the change of the state, then can do data persistence sessionStorage/localStorage- Read the documentation for the rest
Rematch directory structure:
D: \ code \ react - t1 \ SRC \ store - rematch ├ ─ index. The js └ ─ models ├ ─ countRematch. Js └ ─ index, jsCopy the code
Ex. :
npm install @rematch/core
Copy the code
import { init, dispatch, getState } from '@rematch/core'
Copy the code
2.1 models
// src/store-rematch/models/index.js
import countRematch from './countRematch.js';
export { countRematch };
Copy the code
// src/store-rematch/models/countRematch.js
let models = {
state: {
count: 0.JSON_DATA: ' '
},
reducers: {
increment(state) {
return {
...state,
count: state.count + 1
};
},
setJSON_DATA(state, data) {
return {
...state,
JSON_DATA: data }; }},effects: {
async getJsonData() {
await fetch('./manifest.json')
.then(res= > res.json())
.then(json= > {
this.setJSON_DATA(json);
})
.catch(err= > {
this.setJSON_DATA(err); }); }}};export default models;
Copy the code
2.2 store
// src/store-rematch/index.js
import { init } from '@rematch/core';
import * as models from './models';
// A list of things to cache
const cacheList = ['countRematch'];
const stateCache = sessionStorage.getItem('store-rematch');
// Initializing state
const initialState = (stateCache && JSON.parse(stateCache)) || {};
const store = init({
models: {
...models
},
redux: {
initialState: initialState
}
});
// Listen for each state change
store.subscribe((a)= > {
const state = store.getState();
let stateData = {};
Object.keys(state).forEach(item= > {
if (cacheList.includes(item)) stateData[item] = state[item];
});
sessionStorage.setItem('store-rematch'.JSON.stringify(stateData));
});
export default store;
Copy the code
2.3 components
// src/components/rematch-test/rematch-1.jsx
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
function Rematch(props) {
useEffect((a)= >{ props.getJsonData(); } []);return (
<div>
<button onClick={props.Add}>count++</button>
<div>countRematch: {props.count}</div>
</div>
);
}
function mapStateToProps(state) {
return {
count: state.countRematch.count,
JSON_DATA: state.countRematch.JSON_DATA
};
}
function mapDispatchToProps(dispatch) {
return {
Add: (a)= > dispatch({ type: 'countRematch/increment' }),
getJsonData: (a)= > dispatch.countRematch.getJsonData()
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Rematch);
Copy the code
3. Use useReducer/useContext
- useReducer:An alternative to useState. It receives a message of the form
(state, action) => newState
Reducer, and return the current state and its matching dispatch method. - useContext:Receive a context object (
React.createContext()
The return value of the) and returns the current value of the context. The current context value is the closest from the upper component to the current component<MyContext.Provider>
的value
Prop the decision.
When the component is closest to the upper layer<MyContext.Provider>
When updated, the Hook triggers a rerender and uses the latest pass toMyContext provider
Context value of.
Usage Scenarios:
- It is good to do state management within components
- Other…
3.1 Simple Use:
// src/components/useReducer-test/useReducer1.jsx
import React, { useReducer, useContext } from 'react';
export function reducer(state, action) {
switch (action.type) {
case 'increment':
return {
...state,
count: state.count + 1
};
case 'decrement':
return {
...state,
count: state.count - 1
};
default:
returnstate; }}export const UseReducer1Dispatch = React.createContext(null);
// Dispatches passed by the child through the parent
export function Child1(props) {
// Where UseReducer1Dispatch is the return value of the parent component cerateContext()
const dispatch = useContext(UseReducer1Dispatch);
function handleClick() {
dispatch({ type: 'increment' });
}
return (
<div>
<button onClick={handleClick}>Child1 count+</button>
</div>
);
}
export default function UseReducer1({ initialState = { count: 1}}) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<UseReducer1Dispatch.Provider value={dispatch}>
{state.count}
<button onClick={() => dispatch({ type: 'increment' })}>count+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>count-</button>
<hr />
<Child1 dispatch={UseReducer1Dispatch} />
</UseReducer1Dispatch.Provider>
);
}
Copy the code
3.2 Cooperate with useContext to realize dispatch transmission
- The parent component passes
export const UseReducer1Dispatch = React.createContext(null);
then<UseReducer1Dispatch.Provider value={dispatch}>... <UseReducer1Dispatch.Provider />
Wraps itself, passing dispatches to child components instead of callback functions - UseReducer1Dispatch, the createContext return value generated by the child through the parent,
useconst dispatch = useContext(UseReducer1Dispatch);
To obtaindispatch
/ /... The code is the same as 3.1
export const UseReducer1Dispatch = React.createContext(null);
// Dispatches passed by the child through the parent
export function Child1(props) {
// Where UseReducer1Dispatch is the return value of the parent component cerateContext()
const dispatch = useContext(UseReducer1Dispatch);
function handleClick() {
dispatch({ type: 'increment' });
}
return (
<div>
<button onClick={handleClick}>Child1 count+</button>
</div>
);
}
export default function UseReducer1({ initialState = { count: 1}}) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<UseReducer1Dispatch.Provider value={dispatch}>
{state.count}
<button onClick={() => dispatch({ type: 'increment' })}>count+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>count-</button>
<hr />
<Child1 dispatch={UseReducer1Dispatch} />
</UseReducer1Dispatch.Provider>
);
}
Copy the code
More hooks: Hook API index
reference
- Redux Chinese document
- Asynchronous action redux-thunk
- State management library Rematch based on Redux encapsulation
- UseReducer: Simple state management using React Hook