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:

  1. React Routing and lazy loading
  2. React CSS Module
  3. React Use: Hooks and data display handling

Asynchronous action

  1. Why asynchronous Actions?

    The simplest way to do this is to request the component asynchronously and thendispatchUpdate, but once multiple components are used and the state needs to be updated, the logic needs to be written in multiple places…

  2. 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 asredux-thunkMake 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?

Data persistence (localStorage/sessionStorage)

  1. 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
  1. 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

    1. Reducer (Function): Receive two parameters, the current state tree and the action to be processed, and return a new state tree.

    2. [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.

    3. 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 parameterdispatch.returnAn object, and this object will bemergepropsOn;
  • The general isstoreEvent function of
  • The object’skeyThe value of theta is generally zerodispatchaactionthenreducerInternal 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:

  1. It doesn’t need to be set separatelyaction.typeBy the typeModule name + '/' + Reducers keyAutomatically generated,

    { type: 'countRematch/increment' }
  2. effects:{}Properties: Receiving methods such as asynchronous actions with async/await eliminates the need for libraries such as redux-thunk
  3. redux:{}Property: Redux compatible
  4. Init initializes a store using reduxcreateStoreGenerated store some methods, such asstore.subscribeEvery time can monitor the change of the state, then can do data persistence sessionStorage/localStorage
  5. 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) => newStateReducer, 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>valueProp 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 providerContext 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 passesexport 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