![](/img/trans.png)
[英]React redux - parent state changing, but child components not re-rendering
[英]How does React re-use child components / keep the state of child components when re-rendering the parent component?
在 React 中,每次渲染/重新渲染组件时,它都会使用createElement
重新生成它的所有子节点/组件。 React 如何知道何时在重新渲染之间保留组件 state?
例如,考虑以下代码:
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'));
您可以在此处使用 Preact REPL 尝试此代码。
请注意,当按下按钮并更新 clicks 值时, Timer
组件的 state 保持不变,不会被替换。 React 如何知道重用组件实例?
虽然一开始这似乎是一个简单的问题,但当您考虑更改传递给子组件的道具或子组件列表等事情时,它会变得更加复杂。 React 如何处理更改子组件的 props? 即使子组件的道具已更改,子组件的 state 是否仍然存在? (在 Vue 中,组件的 state 在其 props 更改时确实存在)列表怎么样? 当子组件列表中间的条目被删除时会发生什么? 对这样的列表进行更改显然会生成非常不同的 VDOM 节点,但组件的 state 仍然存在。
createElement
vs render
vs 挂载当渲染一个 React 组件(例如您的Button
)时,会使用createElement
创建许多子组件。 createElement(Timer, props, children)
不会创建Timer
组件的实例,甚至不会渲染它,它只会创建一个“React 元素”,它表示应该渲染组件的事实。
当您的Button
被渲染时, react 会将结果与之前的结果进行协调,以决定需要对每个子元素执行什么操作:
Button
时,所有的孩子都将是新的(因为没有以前的结果可以匹配)。如果 React 比较它们并且它们具有相同的类型,则一个元素“匹配”另一个元素。
React 比较子元素的默认方式是简单地同时遍历两个子元素列表,将第一个元素相互比较,然后再比较第二个,等等。
如果孩子有key
s,则将新列表中的每个孩子与旧列表中具有相同密钥的孩子进行比较。
有关更详细的说明,请参阅React Reconciliation Docs 。
你的Button
总是只返回一个元素:一个button
。 因此,当您的Button
重新渲染时,该button
匹配,并且其 DOM 元素被重新使用,然后比较该button
的子项。
第一个孩子总是一个Timer
,所以类型匹配并且组件实例被重用。 Timer
props 没有改变,所以 React 可能会重新渲染它(在具有相同状态的实例上调用render
),或者它可能不会重新渲染它,从而保持树的那部分保持不变。 这两种情况都会在你的情况下产生相同的结果——因为你在render
中没有副作用——并且 React 故意将何时重新渲染的决定作为实现细节。
第二个子元素始终是字符串"Clicks: "
,因此 react 也只保留了该 DOM 元素。
如果this.state.click
自上次渲染后发生了变化,那么第三个子节点将是一个不同的字符串,可能从"0"
变为"1"
,因此文本节点将在 DOM 中被替换。
如果Button
的render
要返回不同类型的根元素,如下所示:
render() {
return createElement(this.state.clicks % 2 ? 'button' : 'a', { onClick: () => this.click() },
createElement(Timer, null),
'Clicks: ',
this.state.clicks
);
}
然后在第一步中,将a
与button
进行比较,因为它们是不同的类型,旧元素及其所有子元素将从 DOM 中删除、卸载和销毁。 然后新元素将在没有先前渲染结果的情况下创建,因此将使用新的 state 创建一个新的Timer
实例,并且计时器将返回 0。
Timer 匹配? |
上一棵树 | 新树 |
---|---|---|
不匹配 | <div><Timer /></div> |
<span><Timer /></span> |
匹配 | <div>a <Timer /> a</div> |
<div>b <Timer /> b</div> |
不匹配 | <div><Timer /></div> |
<div>first <Timer /></div> |
匹配 | <div>{false}<Timer /></div> |
<div>first <Timer /></div> |
匹配 | <div><Timer key="t" /></div> |
<div>first <Timer key="t" /></div> |
从未使用过 Vue,但这是我的看法。
即使子组件的道具已更改,子组件的 state 是否仍然存在? (在 Vue 中,组件的 state 在其 props 更改时确实存在)
这取决于您如何处理孩子身上的道具。
每次您更改(变异)道具时,这个孩子都会重新渲染。
const Child = (props) => {
return <div>{ props.username }</div>;
};
由于返回值取决于本地 state,而不是道具,因此当道具更改时,此子项不会重新渲染。
const Child = (props) => {
const [state, setState] = useState(props.username);
return <div>{ state }</div>;
};
当道具更改时,这个孩子将重新渲染,因为本地 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>;
};
从上面的例子中可以看出,程序员是控制 React 是否触发子元素的重新渲染的人。
清单呢? 当子组件列表中间的条目被删除时会发生什么? 对这样的列表进行更改显然会生成非常不同的 VDOM 节点,但组件的 state 仍然存在。
当涉及到子列表时(例如,当使用.map
时)React 将需要key参数,以便 React 知道在父组件重新渲染之间添加/删除/更改了什么。 React 要求对相同的组件使用相同的密钥,以防止不必要的重新渲染(不要使用Math.random()
作为密钥)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.