preface

The react function components of writing is very flexible, data transfer is also very convenient, but if the understanding of the react not enough in-depth, will encounter many problems, such as data changed view hasn’t changed, the parent component state changed child components form did not update, etc., for complex components, may be more problems, chaotic code were also more likely to appear.

As I tread more and more holes, I become more and more aware of the importance of proper design of data states for the React component. Most common problems are caused by chaotic data transfer and modification.

React component state design I’ve made some comments about react component state design based on my experience and official documentation.

Component data and state

Before talking about component state design, let’s talk about component state and data first, because the focus is component state design, part of the pre-knowledge will only make a brief introduction, if you want to understand in detail, I also have recommended articles in the article, interested students can browse by themselves.

The data status of a component can be obtained from state and props. State is maintained by the component itself and can be modified by calling setState. Props is passed in as read-only, and only methods that can be modified can be passed in as props.

Components of the state

When we call setState to change state, it triggers a re-rendering of the component, synchronizing the data and view. In this process, we can consider several questions:

  1. How is data synchronized to the view?
  2. How is the state stored?
  3. Why state was not reset to its initial value after rerendering?

How is data synchronized to the view?

Take a look at Fiber

Before React16, the React virtual DOM tree was a tree structure. The algorithm recursively traversed the tree based on the depth-first principle to find the changed nodes and operate the native DOM for the changed parts. Because it is recursive traversal, the disadvantage is that this process synchronization can not be interrupted, and because JS is a single thread, a large number of logical processing will occupy the main line too long, the browser does not have time to redraw and rearrange, there will be the problem of rendering lag.

React16 optimized the framework, introduced the mechanism of time slice and task scheduling, JS logic processing only takes up the specified time, when the time is over, regardless of whether the logic processing is finished, the control of the main thread should be returned to the browser rendering process, redraw and rearrange.

Asynchronous interruptible updates require a certain data structure in memory to store the unit of work information, and this data structure is Fiber. [1] (Reference article link at the end of the article, recommended)

Fiber tree stores the information of element nodes in a linked list structure, and each fiber node stores enough information so that the tree comparison process can be interrupted. The current fiber tree is current Fiber, and the fiber tree generated in renderer stage is called workInProgress Fiber. React diff compares the two trees and finally reuses, adds, deletes, and moves nodes.

callsetStateWhat happened after that?
  1. The call function is first generated to generate an update object with the task’s priority, fiber instance, and so on.
  2. This object is then placed in the update queue, waiting for coordination.
  3. React calls methods in priority order, creates a Fiber tree, and generates a list of side effects.
  4. At this stage, the main thread is determined whether there is time, if there isworkInProgress treeAnd walk through it.
  5. And then into the conditioning phase, willworkInProgress treewithcurrent FiberCompare and manipulate to update the real DOM.

How is the state stored?

On the Fiber node, there is memoizedState, which is a linked list of hooks of the current component in order of execution. This list contains information about hooks, which have different values for each type, and in the case of useState, the current state.

(Tip: Read the React Hooks Principle for an in-depth understanding of the Principle.)

Each time a function component’s state changes and is rerendered, it is a new function that has its own unique constant state value, the corresponding state value saved on memoizedState. (Capture Value).

Rendering takes place before state update, so state is the value when the next function is executed. This problem can be solved by setState callback or ref feature.

Why is state not reset to its initial value after rerendering?

Why is the data not reinitialized when components are rerendered?

For example, two select components, the initial value is not selected, select_A check option, select_B check, select_A does not reset to not selected, only refresh the page component overload, data state will be initialized to not selected.

Once you know how the state is saved, it’s easy to understand.

Rerender ≠ overload, the component is not unloaded, and the state value still exists in the Fiber node. And useState only initializes the value of state when the component is first loaded.

When a component is not updated normally, the parent component will be re-rendered and the child component will also be re-rendered, but why is the state value of the child component not updated? Because rerender is just rerender, not reload, and you don’t manually update its state, how can it reset/update?

Ps: If some uncontrolled components do not update their state, we can change the key value of the component to overload it.

Components of the props

When a component’s props changes, it is also re-rendered, and its children are also re-rendered.

[] figure 1-1

As shown in Figure 1-1, the state and props. Data of component A are passed in as the props of subcomponent 1. When the props of component A changes, the props of subcomponent 1 also changes and will be re-rendered. However, it does not need to be re-rendered because the data state does not change, which causes waste. There are many ways to optimize components for components or states that don’t need to be rendered repeatedly, such as react. Memo, pureComponent, useMemo, useCallback, etc.

React component status design

Data and view interact with each other. Complex components often have props and state. How to specify whether the data used by the component should be its own state or that passed to it from outside, and how to plan the state of the component are the keys to write elegant code.

How do you design data types? Props? The state? Constant?

Here’s the passage from the React document:

You can check whether the data is state one by one by asking yourself three questions:

  1. Is this data passed by the parent component via props? If so, it should not be state.
  2. Can you calculate the value of this data based on other states or props? If it is, it’s not state.
  3. Has the data remained the same over time? If so, it should not be state either. [2]

Let’s visualize each of these rules in code, and talk about the pitfalls of writing code that doesn’t follow them.

1. Is this data passed by the parent component through props? If so, it should not be state.

/ / component Test
import React, {useState} from 'React'

export default (props: {
    something: string // * is the state maintained by the parent component and will be modified= > {})const [stateOne, setStateOne] = useState(props.something)

    return (
        <>
            <span>{stateOne}</span>
        </>)}Copy the code

The use of useState in this code is probably written by many newbie friends. Pass something as props, and then assign something as the initial value to the stateOne of the component Test.

What’s the problem with that?

  • As we said earlier,propsChanges cause a rerendering of the component and its children, but rerendering is not the same as reloading,useStateThe initial value is assigned only when the component is first loadedstate, rerender cannot be reassigned, and the current Fiber tree still contains hooks data.So whateverprops.somethingHow does it change, as shown on the pagestateOneThe value does not change and remains the current Test componentstateOneThe value of the.

That is to say,somethingIt went from controlled to out of control. It goes against what we’re trying to do.

So what’s the solution? In the Test component, useEffect to listen for props. Something changes and reset setState.

/ / component Test
import React, {useState} from 'React'

export default (props: {
    something: string
}) => {

    const [stateOne, setStateOne] = useState()
    
    useEffect(() = > {
        setStateOne(props.something) // If there are no other side effects, adding a layer of state does not seem redundant
    }, [props.something])

    return (
        <>
            <span>{stateOne}</span>
        </>)}Copy the code

Some people might say, “I didn’t just use the props value, but the props data needs to be modified before I use it. I need an intermediate variable to receive it. Is it wrong to use state?”

Here we introduce the second rule — “Can you evaluate this data against other states or props? If it is, it’s not state.”

2. Can you calculate the value of this data based on other states or props? If it is, it’s not state.

The biggest difference between state and props is that state is modifiable, while props is read-only. Therefore, when we want to modify the data of props before using it, we might naturally want to cache state as an intermediate variable. However, useState is too small for this scenario. Because you only need setState to reset state when props is changing, there is no other operation that requires setState, so we don’t need state.

So the scenario could use variables directly to receive the results of the recalculation of the data, or, better yet, useMemo, a new variable to receive the values of props. Something.

/ / component Test
import React, {useState} from 'React'

export default (props: {
    something: number[]
}) => {
    // the method is recalculated every time it is re-rendered
    // const newSome = props.something.map((num) => (num + 1))

    // method 2 props. When something changes, it is recalculated
    const newSome = useMemo(() = {
        return props.something.map((num) = > (num + 1))
    }, [props.something])

    return (
        <>
            <span>
                {newSome.map((num) => num)}
            </span>
        </>)}Copy the code

In another case, the props pass data as pure constants, rather than as a state maintained by the parent component. That is, it will not be updated again. The child component needs the data for rendering and will manipulate it.

/ / component Test
import React, {useState} from 'React'

export default (props: {
    something: string // is represented as a constant in the parent component= > {})const [stateOne, setStateOne] = useState()

    return (
        <>
            <span>{stateOne}</span>
        </>)}Copy the code

In A more complicated case, the child component needs the state A of the parent component to reorganize the data according to A, and needs to change the new data. The parent component also has its own role in state A, and cannot directly change the component to the data required by the child component. This case can also be received with state, because the child component needs to modify state, not just depend on the value of the props to reach the new value.

/ / the parent component
 export default (props) => {

    const [staffList, setStaffList] = useState([])

    // after asynchronous request setStaffList(request result)

    return (
       <div>{/ *<div>{staffList related presentation}</div>* /}<Comp staffList={[{name:'Xiao Li '}, {name:'Liu '}, {name:}]} />
       </div>)}/ / child component
const Comp = ({staffList}) = > {

    const [list, setList] = useState(staffList)

    useEffect(() = > {
        const newStaffList = staffList.map((item) = > ({
            ...item,
            isShow: true
        }))
        setList()
    }, [staffList])

    const onHide = useCallBack((index) = > {
        / /... Hide the data subscript index for the clone list
        setList(...)
    }, []) // Don't forget to include dependencies when writing

    return (
        <div>
            {
                list.map((staff, index) => (
                    staff.isShow && <div onClick={()= > onHide(index)}>{staff.name}</div>))}</div>)}Copy the code

3. Has the data remained constant over time? If so, it should not be state either.

The value is the same from the time the component is loaded to the time the component is unloaded. For such data, we can declare it as a constant. It is not a problem to put it outside the component and inside the component, which is wrapped with useMemo.

/ / component Test
import React, {useState} from 'React'

const writer = 'the blue C'

export default() = > {// const writer = useMemo(() => 'blue C', [])

    return (
        <>
            <span>
                {writer}
            </span>
        </>)}Copy the code

Add that use the react friend should how to understand this concept has controlled components (don’t understand it to see the document), from my understanding is, in short, when the component of data under the control of the parent component (the source of the data and modified ways are provided by the parent component, as props incoming), is a controlled components, Conversely, when the data of the component is completely maintained by itself, the parent component neither provides the data nor can affect the change of the data, such a component is an uncontrolled component.

This is a very good concept, and I think it’s easier to understand that the granularity of a “controlled object” can be broken down into individual variables, because complex components often have more than one type of state, both passed from the parent and maintained by themselves. Sometimes when you think about component state, you often think about whether the state should be controlled.

React data transfer is one-way, that is, from top to bottom. Compared with parent and child components, only the child components can be controlled. The child components need to modify the data of the parent component, and the parent component must provide the method to modify the data.

Hooks: useControllableValue, which allows parent and child components to control the same state.

Which level of component should state be placed in? The component itself? The parent components? Ancestor component?

React: State: state: state: state: state: state: state: state: state: state: state: state: state: state: state

In React, shared state is achieved by moving the states that need to be shared between multiple components up to their nearest common parent. This is known as a “state boost.” [3]

For each state in the application:

  • Find all components rendered according to this state.
  • Find their common owner components (above all components that require this state at the component level).
  • The co-owner component or a higher level component should have this state.
  • If you can’t find a good place to store the state, you can simply create a new component to store the state and place the new component higher than the co-owner component. [2]

The description is very detailed and easy to understand. Because the react data flow is one-way, brothers components of communication must be to use the parent component as a “middle tier”, when brother component needs to use the same condition, compared with their respective maintenance state to notice that with each other by the parent, cut the method of further trouble, of course, is the component state mentioned recent parent components together, by the parent component to manage state.

React Context is a state management scheme for complex components with multiple layers. The state is raised to the top layer so that components at each level can obtain data and methods from the top layer.

The React Philosophy is a great guide for newcomers, and it’s a great way to review and sort out ideas.

Afterword.

I used to look at my colleagues’ code and feel like I liked it (and still do, manual) and wonder why I didn’t think of it. For example, when a complex component starts out as a simple component, the transfer of state may be only one or two layers. As the number of encapsulated components increases and the number of layers increases, we naturally consider using Context… Even the big shots are the same.

Many of the problems in this article are my own experience, and I have also taken notes. In fact, this article focuses on the state management of react function components. How to write code is relatively standard, which can avoid performance waste to a certain extent and avoid possible hidden dangers. The same request is executed twice, the data interaction becomes extremely complex and difficult to maintain, and so on.

All are experiences to share, if you feel helpful, I hope you read more likes and favorites forward ~

And welcome to bug-catching. See you in the next article

reference

[1] React source code

[2]React Philosophy

[2]React React status improved