[英]How does React re-use child components / keep the state of child components when re-rendering the parent component?
In React, every time a component is rendered/re-rendered, it regenerates all of it's child nodes/components using createElement
.在 React 中,每次渲染/重新渲染组件时,它都会使用
createElement
重新生成它的所有子节点/组件。 How does React know when to persist the components state between re-renders? React 如何知道何时在重新渲染之间保留组件 state?
As an example, consider the following code:例如,考虑以下代码:
class Timer extends Component {
constructor(props) {
super(props);
this.state = { seconds: 0 };
}
tick() {
this.setState(state => ({ seconds: state.seconds + 1 }));
}
componentDidMount() {
this.interval = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return createElement('div', null,
'Seconds: ',
this.state.seconds
);
}
}
class Button extends Component {
constructor(props) {
super(props);
this.state = { clicks: 0 };
}
click() {
this.setState(state => ({ clicks: state.clicks + 1 }));
}
render() {
return createElement('button', { onClick: () => this.click() },
createElement(Timer, null),
'Clicks: ',
this.state.clicks
);
}
}
render(createElement(Button, null), document.getElementById('root'));
You can try this code with the Preact REPL here .您可以在此处使用 Preact REPL 尝试此代码。
Notice that when the button is pressed and the clicks value is updated, the state of the Timer
component persists and is not replaced.请注意,当按下按钮并更新 clicks 值时,
Timer
组件的 state 保持不变,不会被替换。 How does React know to re-use the component instance? React 如何知道重用组件实例?
While this may seem like a simple question at first, it becomes more complex when you consider stuff like changing the props passed to a child component or lists of child components.虽然一开始这似乎是一个简单的问题,但当您考虑更改传递给子组件的道具或子组件列表等事情时,它会变得更加复杂。 How does React handle changing the props of a child component?
React 如何处理更改子组件的 props? Does the child component's state persist even though it's props have changed?
即使子组件的道具已更改,子组件的 state 是否仍然存在? (In Vue, the state of a component does persist when it's props change) How about lists?
(在 Vue 中,组件的 state 在其 props 更改时确实存在)列表怎么样? What happens when an entry in the middle of a list of child components is removed?
当子组件列表中间的条目被删除时会发生什么? A change to a list like that would obviously generate very different VDOM nodes, yet the state of the components still persists.
对这样的列表进行更改显然会生成非常不同的 VDOM 节点,但组件的 state 仍然存在。
createElement
vs render
vs mount createElement
vs render
vs 挂载When a React Component such as your Button
is rendered, a number of children are created with createElement
.当渲染一个 React 组件(例如您的
Button
)时,会使用createElement
创建许多子组件。 createElement(Timer, props, children)
does not create an instance of the Timer
component, or even render it, it only creates a "React element" which represents the fact that the component should be rendered. createElement(Timer, props, children)
不会创建Timer
组件的实例,甚至不会渲染它,它只会创建一个“React 元素”,它表示应该渲染组件的事实。
When your Button
is rendered, react will reconcile the result with the previous result, to decide what needs to be done with each child element:当您的
Button
被渲染时, react 会将结果与之前的结果进行协调,以决定需要对每个子元素执行什么操作:
Button
is rendered for the first time, all of the children will be new (because there is no previous result to match against).Button
时,所有的孩子都将是新的(因为没有以前的结果可以匹配)。An element "matches" another one if React compares them and they have the same type.如果 React 比较它们并且它们具有相同的类型,则一个元素“匹配”另一个元素。
The default way for React to compare children, is to simply iterate over both lists of children at the same time, comparing the first elements with each other, then the second, etc. React 比较子元素的默认方式是简单地同时遍历两个子元素列表,将第一个元素相互比较,然后再比较第二个,等等。
If the children have key
s, then each child in the new list is compared to the child in the old list that has the same key.如果孩子有
key
s,则将新列表中的每个孩子与旧列表中具有相同密钥的孩子进行比较。
See the React Reconciliation Docs for a more detailed explanation.有关更详细的说明,请参阅React Reconciliation Docs 。
Your Button
always returns exactly one element: a button
.你的
Button
总是只返回一个元素:一个button
。 So, when your Button
re-renders, the button
matches, and its DOM element is re-used, then the children of the button
are compared.因此,当您的
Button
重新渲染时,该button
匹配,并且其 DOM 元素被重新使用,然后比较该button
的子项。
The first child is always a Timer
, so the type matches and the component instance is reused.第一个孩子总是一个
Timer
,所以类型匹配并且组件实例被重用。 The Timer
props did not change, so React might re-render it (calling render
on the instance with the same state), or it might not re-render it, leaving that part of the tree untouched. Timer
props 没有改变,所以 React 可能会重新渲染它(在具有相同状态的实例上调用render
),或者它可能不会重新渲染它,从而保持树的那部分保持不变。 Both of these cases would result in the same result in your case - because you have no side-effects in render
- and React deliberately leaves the decision of when to re-render as an implementation detail.这两种情况都会在你的情况下产生相同的结果——因为你在
render
中没有副作用——并且 React 故意将何时重新渲染的决定作为实现细节。
The second child is always the string "Clicks: "
so react leaves that DOM element alone too.第二个子元素始终是字符串
"Clicks: "
,因此 react 也只保留了该 DOM 元素。
If this.state.click
has changed since the last render, then the third child will be a different string, maybe changing from "0"
to "1"
, so the text node will be replaced in the DOM.如果
this.state.click
自上次渲染后发生了变化,那么第三个子节点将是一个不同的字符串,可能从"0"
变为"1"
,因此文本节点将在 DOM 中被替换。
If Button
s render
were to return a root element of a different type like so:如果
Button
的render
要返回不同类型的根元素,如下所示:
render() {
return createElement(this.state.clicks % 2 ? 'button' : 'a', { onClick: () => this.click() },
createElement(Timer, null),
'Clicks: ',
this.state.clicks
);
}
then in the first step, the a
would be compared to the button
and because they are different types, the old element and all of its children would be removed from the DOM, unmounted, and destroyed.然后在第一步中,将
a
与button
进行比较,因为它们是不同的类型,旧元素及其所有子元素将从 DOM 中删除、卸载和销毁。 Then the new element would be created with no previous render result, and so a new Timer
instance would be created with fresh state, and the timer would be back at 0.然后新元素将在没有先前渲染结果的情况下创建,因此将使用新的 state 创建一个新的
Timer
实例,并且计时器将返回 0。
Timer matches? ![]() Timer 匹配? |
previous tree![]() |
new tree![]() |
---|---|---|
no match![]() |
<div><Timer /></div> |
<span><Timer /></span> |
match![]() |
<div>a <Timer /> a</div> |
<div>b <Timer /> b</div> |
no match![]() |
<div><Timer /></div> |
<div>first <Timer /></div> |
match![]() |
<div>{false}<Timer /></div> |
<div>first <Timer /></div> |
match![]() |
<div><Timer key="t" /></div> |
<div>first <Timer key="t" /></div> |
Never used Vue, but this is my take.从未使用过 Vue,但这是我的看法。
Does the child component's state persist even though it's props have changed?
即使子组件的道具已更改,子组件的 state 是否仍然存在? (In Vue, the state of a component does persist when it's props change)
(在 Vue 中,组件的 state 在其 props 更改时确实存在)
This depends on how you handle the props in your child.这取决于您如何处理孩子身上的道具。
This child will re-render every time you change (mutate) your props.每次您更改(变异)道具时,这个孩子都会重新渲染。
const Child = (props) => {
return <div>{ props.username }</div>;
};
This child will not re-render when props change since the return value is dependent on the local state, and not the props.由于返回值取决于本地 state,而不是道具,因此当道具更改时,此子项不会重新渲染。
const Child = (props) => {
const [state, setState] = useState(props.username);
return <div>{ state }</div>;
};
This child will re-render when props change, as local state updates with the new props.当道具更改时,这个孩子将重新渲染,因为本地 state 使用新道具更新。
const Child = (props) => {
const [state, setState] = useState(props.username);
useEffect(() => {
// changing props changes the component's state, causing a re-render
setState(props.username);
}, [props]);
return <div>{ state }</div>;
};
As seen in the examples above, the programmer is the one in control of whether React triggers a re-render of a child.从上面的例子中可以看出,程序员是控制 React 是否触发子元素的重新渲染的人。
How about lists?
清单呢? What happens when an entry in the middle of a list of child components is removed?
当子组件列表中间的条目被删除时会发生什么? A change to a list like that would obviously generate very different VDOM nodes, yet the state of the components still persists.
对这样的列表进行更改显然会生成非常不同的 VDOM 节点,但组件的 state 仍然存在。
When a list of children is involved (eg. when .map
is used) React will require the key parameter, so that React will be aware what was add/removed/changed between parent component re-renders.当涉及到子列表时(例如,当使用
.map
时)React 将需要key参数,以便 React 知道在父组件重新渲染之间添加/删除/更改了什么。 React requires that the same key be used for the same components to prevent unnecessary re-renders (don't use Math.random()
as your key). React 要求对相同的组件使用相同的密钥,以防止不必要的重新渲染(不要使用
Math.random()
作为密钥)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.