Preface
The react function component is very flexible, and the data transfer is very convenient, but if you don’t have a deep understanding of react, you will encounter many problems, such as the data changes and the view does not change, the state of the parent component changes and the state of the child components is not updated in time, etc. , For complex components, there may be more problems, and messy code is more likely to appear.
As I stepped on more pits, I became more and more aware of the importance of the reasonable design of the data state for React components. Most common problems are caused by the confusion of data transfer and modification.
According to the development experience and official documents, I have made some conclusions on the state design of react components, hoping to help you rationalize your ideas.
Data and status of components
Before talking about component state design, let’s talk about component state and data. Because the focus is on component state design, I will only briefly introduce some of the pre-knowledge. If I want to learn more, I also have recommended articles in the article for interested students. You can browse by yourself.
The data status sources in the component are state
and props
. state
is maintained by the component itself and can be modified by setState
props
is passed in from outside and is read-only. If you want to modify it, you can only pass in the method of props
Component state
When we call setState to modify the state, it will trigger the re-rendering of the component and synchronize the data and view. In this process, we can think about several questions:
- data synchronized to the view?
- state of 161834cc0b7dad state saved?
- Why is the state not reset to the initial value after re-rendering?
1. How is the data synchronized to the view?
First understand fiber
In react16 before, react the virtual dom tree is tree , the algorithm depth-first principle, recursive traversal tree, find the changed nodes, part of the operation for changing the native dom. Because it is recursively traverse, synchronized drawback is that this process can not be interrupted, and because JS is single-threaded , a large number of logic will occupy the main thread for too long, the browser is no time for redraw rearrangement , there will be rendering Caton problem.
React16 , the framework was optimized, and the mechanism of time slice and task scheduling was introduced. js logic processing only takes up the specified time . When the time is over, regardless of whether the logic is processed or not, the main thread must be controlled. Return it to the browser rendering process for redrawing and rearranging.
And asynchronous interruptible update requires a certain data structure in the memory to save the information of the work unit, this data structure is Fiber . [1] (The link to the quoted article is at the end of the article, recommended)
The fiber tree linked list structure. Each fiber node saves enough information and the tree comparison process can be interrupted. The current fiber tree is current fiber
. The fiber tree generated in the renderer stage is called workInProgress fiber
. Between the trees, the node is connected by the alternate
pointer. React will diff and compare the two trees, and finally reuse, add, delete and move nodes.
setState
after 161834cc0b82ee called 061834cc0b82ec?
- First, the call function is generated to generate an update object, which has task priority, fiber instance, and so on.
- Put this object in the update queue and wait for coordination.
- React will call methods in order of priority, create a Fiber tree, and generate a list of side effects.
- At this stage, it will first determine whether the main thread has time, if so, first
workInProgress tree
and traverse it. - Then enter the tuning stage, compare
workInProgress tree
withcurrent Fiber
, and update the real dom.
2. How is the state of state saved?
On the fiber node, memoizedState
is saved, that is, the linked list formed by the hooks of the current component in the order of execution. This linked list useState
, the value is the current state
.
(Tips: In-depth understanding of the principle of react hooks, recommended reading "React Hooks Principle" )
Every function components state
change heavy rendering, is a new function, the only constant has its own state
value, that memoizedState
saved on the corresponding state
value. ( capture value feature).
This is also the reason why the setState
is clearly not available for state
. The rendering occurs before the state is updated, so the state is the value when the function is executed. This problem setState
by the callback of ref
2. Why is the state not reset to the initial value after re-rendering?
Why are the components re-rendered and the data is not re-initialized?
You can understand from the business point of view. For example, the initial values of the two select components are not selected. After select_A selects the option, select_B is selected again, select_A will not be reset to unselected, and the data status will only be reloaded when the refresh page component is reloaded Initialized as unselected.
After knowing how the state
state is saved, it is actually easy to understand.
make the point-161834cc0b8608 re-rendering ≠ reloading, the component is not uninstalled, and the state
still exists in the fiber node. And useState
will only initialize the value of state when the component is first loaded.
When encountering a scene where the component is not updated normally, some friends often wonder, the parent component will re-render the child component and the child component will be re-rendered, but why the status value of the child component is not updated? Just because renderer is just renderer, not overloading, you don't update its state
, how can it reset/update?
ps: In the face of some uncontrolled components that do not update their state, we can solve the problem by changing the key value of the component and reloading it.
Component props
When the props
component changes, re-rendering will occur, and its sub-components will also be re-rendered.
[Picture 1-1]
As shown in Figure 1-1, the 061834cc0b8704 and props.data
state
as the props
subcomponent 1. When the props of component A change, the props
subcomponent 1 also changes and will be re-rendered, but subcomponent 2 is not Controlled component, after parent component A is re-rendered, it will be re-rendered. However, it does not need to be re-rendered if the data state has not changed, which causes waste. For such components or states that do not need to be re-rendered, there are many ways to optimize components, such as the official React.Memo,pureComponent,useMemo,useCallback
and so on.
React component state design
Data and view affect each other. In complex components, there are often props
and state
. How to specify whether the data used by the component should be the state of the component itself, or whether it is transmitted from the outside to props
, and how to plan the state of the component is the key to writing elegant code.
How to design data types? props? state? constant?
Let's first look at the paragraph in the official react document:
By asking yourself the following three questions, you can check whether the corresponding data belongs to the state one by one:
- Is the data passed by the parent component through props? If it is, then it should not be state.
- Can you calculate the value of the data based on other state or props? If it is, then it is not state either.
- Does this data stay the same over time? If it is, then it should not be state either. [2]
Let's visualize these rules one by one with code, and talk about the pitfalls that may arise from code that does not follow the rules.
1. Is the data passed by the parent component through props? If it is, then it should not be state.
// 组件Test
import React, {useState} from 'React'
export default (props: {
something: string // *是父组件维护的状态,会被修改
}) => {
const [stateOne, setStateOne] = useState(props.something)
return (
<>
<span>{stateOne}</span>
</>
)
}
useState
in this code is estimated to be written by many novice friends, passing in something
as props
, and then re-assigning something
as the initial value to the state of the component Test stateOne
.
What are the problems with this?
- We said
props
change of 061834cc0b8e89 will trigger the re-rendering of the component and its subcomponents. However, re-rendering is not equal to re-rendering.useState
state
when the component is first loaded. When re-rendering, yes The value cannot be re-assigned, and the current fiber tree still saves the data of hooks, that is, the current state state value, So no matterprops.something
stateOne
displayed on the page will not change accordingly. It is always the currentstateOne
of the component Test. value.
means that something
from controlled to out of control. It violates our original intent to pass value.
What is the solution? useEffect
monitor the changes of props.something
through 061834cc0b8f01 in the component Test, setState
.
// 组件Test
import React, {useState} from 'React'
export default (props: {
something: string
}) => {
const [stateOne, setStateOne] = useState()
useEffect(() => {
setStateOne(props.something) // 如果没有别的副作用,加一层state是不是看起来很多余
}, [props.something])
return (
<>
<span>{stateOne}</span>
</>
)
}
Some friends may say, "I took the value of props and used it directly. The data of props needs to be modified before use. Doesn't this require an intermediate variable to receive? Isn't it right to use state?"
Here we introduce the second rule- "Can you calculate the value of the data based on other state or props? If it is, then it is not state."
2. Can you calculate the value of the data based on other state or props? If it is, then it is not state either.
state
compared props
biggest difference is that state
is modifiable, props
is read-only, so we want to props
time data to make some changes before use, it may naturally think of using state
as an intermediate variable to cache, However, using useState
this scenario is overkill, because only when props
changes, you need to use setState
to reset the state
. There is no other operation that requires setState
. At this time, we don't need to use state
.
Therefore, in this scenario, you can directly use the variable to receive the data recalculated result, or, a better way is to use useMemo
, and use the new variable to receive the calculated value of props.something
// 组件Test
import React, {useState} from 'React'
export default (props: {
something: number[]
}) => {
// 方式一 每次重渲染都会重新计算
// const newSome = props.something.map((num) => (num + 1))
// 方式二 props.something变化时会重新计算
const newSome = useMemo(() = {
return props.something.map((num) => (num + 1))
}, [props.something])
return (
<>
<span>
{newSome.map((num) => num)}
</span>
</>
)
}
In another case, props
is used as a simple constant instead of the state maintained by the parent component, which means that it will not be updated again. The child component rendering needs these data and will manipulate the data. At this time, you can use state
to go. Received.
// 组件Test
import React, {useState} from 'React'
export default (props: {
something: string // 在父组件中表现为不会改变的常量
}) => {
const [stateOne, setStateOne] = useState()
return (
<>
<span>{stateOne}</span>
</>
)
}
There is also a more complicated situation. The child component needs the state A of the parent component. The data is reorganized according to A, and these new data need to be changed. The parent component also has its own effect on the state A and cannot be directly used by the child component. Change the data required for the sub-component. In this case, state
can also be used to receive, because the sub-component needs to modify state
, not only relying on props
to get a new value.
// 父组件
export default (props) => {
const [staffList, setStaffList] = useState([])
// 异步请求后setStaffList(请求结果)
return (
<div>
{/* <div>{staffList相关的展示}</div> */}
<Comp staffList={[{name: '小李'}, {name: '小刘'}, {name: '小明'}]} />
</div>
)
}
// 子组件
const Comp = ({staffList}) => {
const [list, setList] = useState(staffList)
useEffect(() => {
const newStaffList = staffList.map((item) => ({
...item,
isShow: true
}))
setList()
}, [staffList])
const onHide = useCallBack((index) => {
// ... 为 克隆list隐藏下标为index项后的数据
setList(...)
}, []) // 写的时候别忘记填入依赖
return (
<div>
{
list.map((staff, index) => (
staff.isShow && <div onClick={() => onHide(index)}>{staff.name}</div>
))
}
</div>
)
}
3. Does the data remain unchanged over time? If it is, then it should not be state either.
This one is very easy to understand. It remains unchanged over time, which means that the value is the same from when the component is loaded to when the component is unloaded. For such data, we use a constant to declare it. , It is not a big problem if it is placed outside the module, and the module is wrapped useMemo
// 组件Test
import React, {useState} from 'React'
const writer = '蔚蓝C'
export default () => {
// const writer = useMemo(() => '蔚蓝C', [])
return (
<>
<span>
{writer}
</span>
</>
)
}
One thing to add is that friends who use React should have some understanding of the concept of controlled components (if you don’t understand, go to the documentation). In short, from my understanding, when the data in the component is controlled by the parent component ( The source of the data and the way of modification are provided by the parent component and passed in as props), which is a controlled component. On the contrary, when the data of the component is completely maintained by itself, the parent component does not provide data and cannot affect the change of the data. The component is an uncontrolled component.
This is a very good concept. I think it’s better to understand that the granularity of the "controlled object" can be subdivided into a single variable, because complex components often have more than one type of state, and they are passed from the parent. There are also self-maintained ones. Sometimes when thinking about the state of a component, you often think about whether this state should be controlled.
React's data transfer is one-way, that is, from top to bottom. Compared with the parent-child component, only the child component can be controlled. The child component needs to modify the data of the parent component, and the parent component must provide it to modify the data. method.
By the way, I recommend a hook that allows both parent and child components to control the same state, Ali’s hooks library-ahooks, which contains useControllableValue
.
At which level of component should the state be placed? The component itself? The parent component? Ancestor component?
Regarding which level of component the state state should be placed in, in the official react document, there are the following two paragraphs:
In React, the shared state can be realized by moving the state that needs to be shared among multiple components to their nearest common parent component. This is the so-called "status promotion". [3]
For each state in the application:
- Find all components that are rendered according to this state.
- Find their common owner component (above all components that need this state at the component level).
- The co-owner component or a higher-level component should own the state.
- If you can't find a suitable location to store the state, you can directly create a new component to store the state, and place this new component at a higher level than the co-owner component level. [2]
The description is very detailed and easy to understand. Because the react data flow is one-way, the communication of the brother components must use the parent component as the "middle layer". When the brother components need to use the same state, it is troublesome to notify each other through the parent instead of maintaining the state. Of course, the way to stay away from near and far is to mention the common state of the component to the nearest common parent component, and the parent component will manage the state.
React's Context
is a state management solution for complex components with more levels. The state is raised to the top level so that each level of components can obtain the data and methods passed by the top level.
Regarding this subtitle, I strongly recommend reading the React document-React Philosophy , which is very novice friendly and suitable for reviewing and sorting out ideas, so I won't repeat it.
postscript
I used to look at the code of my colleagues in the same group, and I always felt like I was good at it (now it is also a manual dog head), and I didn't think I had to plan the code like this. But in fact, when we write code, we often can’t do it in one step. We write and think while improving as required. For example, in the early stage of a complex component, it is a simple component, and the transfer of state may be one or two levels. As the packaged components increase and the levels increase, we will naturally consider the use of Context
... Even the big guys are the same.
I have personally stepped on many of the pits in the article, and I have taken notes myself. The content of this compilation, in fact, the focus is on the state management of react function components. How to write code is more standardized, which can avoid performance to a certain extent. The waste of data and avoid possible hidden dangers, such as a request that is executed twice, the data interaction is extremely complicated, and it is difficult to maintain.
It's all about sharing experience. If you find it helpful, I hope everyone will like it, collect it and forward it~
that's all, thank u~ See you in the next article~
Quote
[1] react source code analysis 7. Fiber architecture
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。