At the beginning

For a project and a complex logic, I think state management is particularly important. Whether state management is good or not directly reflects whether the logic, readability and maintenance of a project are clear, easy to read and efficient.

From the earliest class components using this.state and this.setState to manage state, to publish and subscribe redux, subscribe, dispatch, redux has faced repetitive and heavy reducers. Made me a Ctrl CV engineer. Then comes DVA, a data flow scheme based on REdux and Redux-Saga. The global state is managed by fragmentation through model, and the connect method is used to pass state to the required deep-level components.

Since react hooks came out, there have been a number of implementations that manage their own state, based on hooks encapsulation, where each module has a state store based on its own hooks. It is really a good solution to the state management of function components, and the internal state management of the module itself, but it still can not solve the problem that the structure becomes complicated and tedious because of the state dependence passed layer by layer in the global component. How can we communicate across components without any management tools?

Why not?

Not that we don’t use dVA management tools? I’m not saying dVA is bad, but I don’t think it’s necessary sometimes. I think he’s too heavy.

What did you learn?

After reading this article, you can learn about useMemo, useContext, useImmer, etc., even if you think my management style is not good.

react context

Context-React

// Context allows us to pass values deep into the component tree without explicitly traversing each component.
// Create a context for the current theme (" light "is the default).
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // Use a Provider to pass the current theme to the following component tree.
    // Any component, no matter how deep, can read this value.
    // In this case, we pass "dark" as the current value.
    return( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); }} // Intermediate components no longer have to specify the theme to pass down. function Toolbar(props) { return ( <div> <ThemedButton /> </div> ); } class ThemedButton extends react.ponent {// Specify contextType to read the current theme context. // React will find the nearest theme Provider and use its value. // In this case, the current theme value is "dark". static contextType = ThemeContext; render() { return <Button theme={this.context} />; }}Copy the code

CreateContext Outlines the process for implementing cross-component communication

constMyContext = React.createContext(defaultValue); < myContext. Provider value={/* some value */}> <App>... There is a Goods component <Goods /> </App> </ myContext.provider > // a child child of Goods < myContext.consumer > {value => /* based */} </ myContext.consumer >Copy the code

Specific practical case

app.js

import {ThemeContext, themes} from './theme-context';
import ThemeTogglerButton from './theme-toggler-button';

class App extends React.Component {
  constructor(props) {
    super(props);

    this.toggleTheme = (a)= > {
      this.setState(state= > ({
        theme:
          state.theme === themes.dark
            ? themes.light
            : themes.dark,
      }));
    };

    // State also contains the update function, so it is passed into the context provider.
    this.state = {
      theme: themes.light,
      toggleTheme: this.toggleTheme,
    };
  }

  render() {
    // The entire state is passed into the provider
    return (
      <ThemeContext.Provider value={this.state}>
        <Content />
      </ThemeContext.Provider>
    );
  }
}

function Content() {
  return (
    <div>
      <ThemeTogglerButton />
    </div>
  );
}

ReactDOM.render(<App />, document.root);
Copy the code
// Theme context. The default Theme is the "light" value
const ThemeContext = React.createContext('light');

// The user logs into the context
const UserContext = React.createContext({
  name: 'Guest'});class App extends React.Component {
  render() {
    const {signedInUser, theme} = this.props;

    // The App component that provides the initial context value
    return( <ThemeContext.Provider value={theme}> <UserContext.Provider value={signedInUser}> <Layout /> </UserContext.Provider> </ThemeContext.Provider> ); } } function Layout() { return ( <div> <Sidebar /> <Content /> </div> ); } // A component may consume more than one context function Content() {return (< themecontext.consumer > {theme => (< userContext.consumer > {user => ( <ProfilePage user={user} theme={theme} /> )} </UserContext.Consumer> )} </ThemeContext.Consumer> ); }Copy the code

Encapsulate your own approach to cross-component management

. / connect. Js file

Encapsulate connect method

Using Connect is also based on the React-Redux idea, encapsulating it as a method. Calling connect returns a higher-order component. In addition, the CONNECT method supports passing in a function to filter and filter the state required by the child components, and also facilitates maintenance of rerender, etc

import React, { createContext } from 'react';
import { useImmer } from 'use-immer';
// useImmer is recommended at the end of the article

const ctx = createContext();
const { Consumer, Provider } = ctx

const useModel = (initialState) = > {
  const [state, setState] = useImmer(initialState);
  return [
    state,
    setState
  ];
}

const createProvider = (a)= > {
  function WrapProvider(props) {
    const { children, value } = props;
    const [_state, _dispatch] = useModel(value)
    return (
      <Provider value={{
        _state._dispatch,
      }}>
        {children}
      </Provider>)}return WrapProvider
}

export const connect = fn= > ComponentUi => (a)= > {
  return (
    <Consumer>
      {
        state => {
          const {_state, _dispatch} = state
          const selectState = typeof fn === 'function' ? fn(_state) : _state;
          return <ComponentUi _state={selectState} _dispatch={_dispatch} />}}</Consumer>)}; export default createProvider;Copy the code

use

import React from 'react';
import Header from './layout/Header.jsx';
import Footer from './layout/Footer.jsx';
import createProvider from './connect';

const Provider = createProvider()

const initValue = { user: 'xiaoming'.age: 12 }
function App() {
  return (
    <Provider value={initValue}>
        <Header />
        <Footer />
    </Provider>)}export default App;
Copy the code

Header.jsx

import React from 'react';
import { Select } from 'antd';
import { connect } from '.. /connect';
const { Option } = Select;

function Head({ _state: { user, age }, _dispatch }) {
  return (
    <div className="logo" >
      <Select defaultValue={user} value={user} onChange={(value)= > {
        _dispatch(draft => {
          draft.user = value
        })
      }}>
        <Option value='xiaoming'>Xiao Ming</Option>
        <Option value='xiaohong'>The little red</Option>
      </Select>
      <span>{age age}</span>
    </div>)}export default connect()(Head);
Copy the code

Footer.jsx

import React, { Fragment } from 'react';
import { Select } from 'antd';
import { connect } from '.. /.. /connect';

const { Option } = Select;
function Footer({ _state, _dispatch }) {
  const { user, age } = _state;
  return (
    <Fragment>
      <p style={{marginTop: 40}} >User: {user}</p>
      <p>{age age}</p>
      <div>
        <span>Change the user:</span>
        <Select
          defaultValue={user}
          value={user}
          onChange={(value)= > {
            _dispatch(draft => {
              draft.user = value
            })
          }}>
          <Option value='xiaoming'>Xiao Ming</Option>
          <Option value='xiaohong'>The little red</Option>
        </Select></div>
      <div>
        <span>Change your age:</span>
        <input onChange={(e)= >{// the reason for using persist can be seen at the end of this article. _dispatch(draft => { draft.age = e.target.value }) }} /></div>
    </Fragment>
  )
}

export default connect()(Footer);
Copy the code

Using useContext

We all know that useContext came out after React 16.8, so we can optimize the connect method by using useContext

// useContext is not used
export const connect = (fn) = > (ComponentUi) => (a)= > {
  const state = useContext(ctx)
  console.log(state);
  return( <Consumer> { state => { const { _state, _dispatch } = state const selectState = typeof fn === 'function' ? fn(_state) : _state; return <ComponentUi _state={selectState} _dispatch={_dispatch} /> } } </Consumer> ) }; // useContext export const connect = fn => ComponentUi => () => {const {_state, _dispatch} = useContext(CTX); const selectState = typeof fn === 'function' ? fn(_state) : _state; return <ComponentUi _state={selectState} _dispatch={_dispatch} />; };Copy the code

Note: Components that call useContext are always rerendered when the context value changes. If rerendering components is expensive, you can learn how to optimize them by unnecessarily rerendering expensive components recommended at the end of this article.

The last

Making the address

The 4-step code runs

git clone https://github.com/zouxiaomingya/blog
cd blog
npm i
npm start
Copy the code

The whole article, if there are mistakes or not rigorous place, please be sure to give correction, thank you!

Reference:

  • The react useContext document

  • Unnecessary rerender of expensive components