Exploration of three underlying mechanisms of React global state management

Exploration of three underlying mechanisms of React global state management

Preface

Modern front-end frameworks all develop pages in a component-based way. Divide the page into different components according to the logical relationship, develop different components separately, and then assemble them layer by layer. Pass the root component into ReactDOM.render or Vue's $mount method, and the entire component tree will be traversed and rendered into the corresponding DOM.

Components support passing some parameters for customization, and can also save some interactive states internally, and will automatically re-render the corresponding part of the DOM after the parameters and states change.

Although they are logically divided into different components, they are all different parts of the same application and inevitably need to communicate and cooperate with each other. If components in more than one layer communicate through parameters, the components in the middle layer must pass these parameters transparently. Parameters are originally used to customize components, and meaningless parameters should not be added for communication.

Therefore, for component communication, it is generally not through the layer-by-layer transmission of component parameters, but through the placement of them in a global place, from which both parties access them.

There may be many specific solutions for global state management, but their underlying mechanisms are nothing more than three: props, context, and state.

Next, let's explore how these three methods store and transfer global states.

props

We can communicate through a global object, where one component stores data and the other component retrieves it.

Writing code in components to retrieve data from the store is rather intrusive. You can't add this code to every component that uses the store. We can extract this logic into a higher-order component and use it to connect components and stores. Data is injected into components through parameters, so that the source is transparent to the components.

This is what react-redux does:

import { connect } from 'react-redux';

function mapStateToProps(state) {
    return { todos: state.todos }
}
  
function mapDispatchToProps(dispatch) {
    return bindActionCreators({ addTodo }, dispatch)
}
  
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)

In addition, redux also provides a middleware mechanism that can intercept the action sent by the component to the store to execute a series of asynchronous logic.

The more popular middlewares include redux-thunk, redux-saga, and redux-obervable, which support different ways to write and organize asynchronous processes, encapsulate and reuse asynchronous logic.

Similar global state management libraries, such as mobox, reconcil, etc., also inject global state into components through props.

context

Does cross-layer component communication require a third-party solution? No, React itself also provides a context mechanism for this kind of communication.

React.createContext's api returns Provider and Consumer, which are used to provide state and get state respectively, and are also transparently passed to the target component through props. (The Consumer here can also be replaced with the useContext API, which has the same effect. Class components use Provider, and function components use useContext)

It looks like there is basically no difference from the redux solution, but the main difference is that context does not have middleware to execute asynchronous logic.

Therefore, the context solution is suitable for global data communication without asynchronous logic, while redux is suitable for organizing complex asynchronous logic.

The case code is as follows:

const themes = {
  light:
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark:
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

I wonder if you have ever thought about this: it is normal to re-render components when props and state change, but how does a change in context trigger rendering?

In fact, react has done some processing internally. If the context value is changed, it will traverse all child components, find the component that uses the context value, and trigger its update.

Therefore, props, state, and context can all trigger re-rendering.

state

The redux and context solutions, one is a third-party one and the other is built-in. Both pass in values ​​through props or get values ​​through hooks, but they are both external to the component, while the state is internal to the component. How to share global state through state?

In fact, the state of class components cannot do this, but the state of function components can, because it is created through the hooks api of useState, and useState can be extracted into custom hooks and then introduced into different function components for use.

import React, { useState } from 'react';

const useGlobalState = (initialValue) => {
    const [globalState, setGlobalState] = useState(initialValue);
    return [globalState, setGlobalState];
}

function ComponentA() {
    const [globalState, setGlobalState] = useGlobalState({name: 'aaa'});
    
    setGlobalState({name: bbb});
    return <div>{globalState}</div>
}

function ComponentA() {
    const [globalState, setGlobalState] = useGlobalState({name: 'aaa'});
 
    return <div>{globalState}</div>
}

Can the above code share global state?

It is indeed not possible, because now each component puts a new object in its own fiber.memorizedState, and modifications also modify their own.

Wouldn't it be fine to point the initial values ​​of these two useState to the same object?

In this way, multiple components can operate on the same data.

The above code needs to be modified as follows:

let globalVal = {
    name: ''
}

const useGlobalState = () => {
    const [globalState, setGlobalState] = useState(globalVal);

    function updateGlobalState(val) {
        globalVal = val;
        setGlobalState(val);
    }

    return [globalState, updateGlobalState];
}

In this way, the state created by each component points to the same object, and global state can also be shared.

But there is a premise here, that is, you can only modify the properties of the object, but not the object itself.

Summarize

The current way to develop front-end pages is to split the page into components according to logic, develop each component separately, and then assemble them layer by layer, and pass them to ReactDOM.render or Vue's $mount for rendering.

Components can be customized via props and state to store interaction state, which will automatically re-render if changed. In addition, when the context changes, the child components that use the contxt data will be found to trigger re-rendering.

Components cooperate with each other, so communication is inevitable. Props are used to customize components and should not be used to pass meaningless props, so they must be transferred through the global object.

React itself provides a context solution. CreateContext will return Provider and Consumer, which are used to store and read data respectively. In function components, you can also use useContext instead of Provider.

Although context can share global state, it does not have an execution mechanism for asynchronous logic. When there is complex asynchronous logic, you still have to use redux, which provides a middleware mechanism for organizing asynchronous processes and encapsulating and reusing asynchronous logic. For example, in redux-saga, asynchronous logic can be encapsulated into saga for reuse.
Both context and redux support injecting data into components through props, which is transparent and non-intrusive to the components.

In fact, custom hooks encapsulated by useState can also achieve the purpose of global data sharing by pointing the initial value to the same object, but there are limitations. You can only modify the properties of the object, not the object itself. In fact, it is better to use context than this, but I just want to mention that you can do this.

To sum it up briefly: context and redux can both be used for global state management, one is built-in and the other is third-party. Use context if there is no asynchronous logic and use redux if there is asynchronous logic.

This concludes this article on the three underlying mechanisms of React global state management. For more relevant React global state management content, please search for previous articles on 123WORDPRESS.COM or continue to browse the following related articles. I hope everyone will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • Summary of three rules for React state management
  • Example of using Vue's state management method in React
  • Interpretation and usage of various React state managers

<<:  Detailed tutorial on building a private Git server on Linux

>>:  Full analysis of MySQL INT type

Recommend

Solve the problem of Nginx returning 404 after configuring proxy_pass

Table of contents 1. Troubleshooting and locating...

jQuery to achieve the barrage effect case

This article shares the specific code of jQuery t...

Detailed explanation of Nginx process management and reloading principles

Process structure diagram Nginx is a multi-proces...

Analysis and practice of React server-side rendering principle

Most people have heard of the concept of server-s...

Alpine Docker image font problem solving operations

1. Run fonts, open the font folder, and find the ...

How to decrypt Linux version information

Displaying and interpreting information about you...

Solve the problem of Docker starting Elasticsearch7.x and reporting an error

Using the Docker run command docker run -d -p 920...

A QQ chat room based on vue.js

Table of contents Introduction The following is a...

7 skills that great graphic designers need to master

1》Be good at web design 2》Know how to design web p...

Tutorial on installing jdk1.8 on ubuntu14.04

1. Download jdk download address我下載的是jdk-8u221-li...

How to configure anti-hotlinking for nginx website service (recommended)

1. Principle of Hotlinking 1.1 Web page preparati...