javascript - 与 setState 回调相比,使用 componentDidUpdate 有什么优势?
问题描述
为什么在 React 组件中使用componentDidUpdate
更推荐的setState
回调函数(可选的第二个参数)(如果需要同步 setState 行为)?
由于setState
是异步的,我正在考虑使用setState
回调函数(第二个参数)来确保代码在状态更新后执行,类似于then()
Promise。setState
特别是如果我需要在后续调用之间重新渲染。
然而,官方的 React Docs 说“setState() 的第二个参数是一个可选的回调函数,一旦 setState 完成并重新渲染组件就会执行。一般我们建议使用 componentDidUpdate() 来代替这种逻辑。” 这就是他们在那里所说的一切,所以看起来有点模糊。我想知道是否有更具体的原因建议不要使用它?如果可以的话,我会问 React 的人自己。
如果我希望按顺序执行多个 setState 调用,则 setState 回调在代码组织方面似乎比 componentDidUpdate 更好 - 回调代码是在 setState 调用中定义的。如果我使用 componentDidUpdate 我必须检查相关的状态变量是否发生了变化,并在那里定义后续代码,这不太容易跟踪。此外,在包含 setState 调用的函数中定义的变量将超出范围,除非我也将它们放入状态。
以下示例可能会显示何时使用 componentDidUpdate 可能会很棘手:
private functionInComponent = () => {
let someVariableBeforeSetStateCall;
... // operations done on someVariableBeforeSetStateCall, etc.
this.setState(
{ firstVariable: firstValue, }, //firstVariable may or may not have been changed
() => {
let secondVariable = this.props.functionFromParentComponent();
secondVariable += someVariableBeforeSetStateCall;
this.setState({ secondVariable: secondValue });
}
);
}
对比
public componentDidUpdate(prevProps. prevState) {
if (prevState.firstVariableWasSet !== this.state.firstVariableWasSet) {
let secondVariable = this.props.functionFromParentComponent();
secondVariable += this.state.someVariableBeforeSetStateCall;
this.setState({
secondVariable: secondValue,
firstVariableWasSet: false,
});
}
}
private functionInComponent = () => {
let someVariableBeforeSetStateCall = this.state.someVariableBeforeSetStateCall;
... // operations done on someVariableBeforeSetStateCall, etc.
this.setState({
firstVariable: firstValue,
someVariableBeforeSetStateCall: someVariableBeforeSetStateCall,
firstVariableWasSet: true });
//firstVariable may or may not have been changed via input,
//now someVariableBeforeSetStateCall may or may not get updated at the same time
//as firstVariableWasSet or firstVariable due to async nature of setState
}
另外,除了通常推荐使用 componentDidUpdate 之外,在什么情况下 setState 回调更适合使用?
解决方案
为什么比回调函数
componentDidUpdate
更推荐使用?setState
1. 一致的逻辑
当使用回调参数时setState()
,你可能setState()
在不同的地方有两个单独的调用,它们都更新相同的状态,你必须记住在两个地方使用相同的回调。
一个常见的例子是每当状态改变时调用第三方服务:
private method1(value) {
this.setState({ value }, () => {
SomeAPI.gotNewValue(this.state.value);
});
}
private method2(newval) {
this.setState({ value }); // forgot callback?
}
这可能是一个逻辑错误,因为您可能想在值更改时调用该服务。
这就是为什么componentDidUpdate()
推荐:
public componentDidUpdate(prevProps, prevState) {
if (this.state.value !== prevState.value) {
SomeAPI.gotNewValue(this.state.value);
}
}
private method1(value) {
this.setState({ value });
}
private method2(newval) {
this.setState({ value });
}
这样,保证在状态更新时调用服务。
此外,可以从外部代码(例如 Redux)更新状态,您将没有机会为这些外部更新添加回调。
2.批量更新
setState()
组件重新渲染后执行的回调参数。setState()
但是,由于批处理,不能保证多次调用会导致多次渲染。
考虑这个组件:
class Foo extends React.Component {
constructor(props) {
super(props);
this.state = { value: 0 };
}
componentDidUpdate(prevProps, prevState) {
console.log('componentDidUpdate: ' + this.state.value);
}
onClick = () => {
this.setState(
{ value: 7 },
() => console.log('onClick: ' + this.state.value));
this.setState(
{ value: 42 },
() => console.log('onClick: ' + this.state.value));
}
render() {
return <button onClick={this.onClick}>{this.state.value}</button>;
}
}
我们setState()
在处理程序中有两个调用onClick()
,每个调用都只是将新的状态值打印到控制台。
您可能希望onClick()
打印该值7
,然后42
. 但实际上,它打印42
了两次!这是因为这两个setState()
调用是批处理在一起的,并且只会导致一次渲染发生。
此外,我们还有一个componentDidUpdate()
也打印新值的。因为我们只发生了一次渲染,所以它只执行一次,并打印出 value 42
。
如果您希望与批量更新保持一致,通常使用componentDidMount()
.
2.1。什么时候发生批处理?
没关系。
批处理是一种优化,因此您不应该依赖批处理发生或不发生。React 的未来版本可能会在不同的场景中执行或多或少的批处理。
但是,如果您必须知道,在当前版本的 React (16.8.x) 中,批处理发生在异步用户事件处理程序(例如onclick
)中,如果 React 可以完全控制执行,有时还发生在生命周期方法中。所有其他上下文从不使用批处理。
有关更多信息,请参阅此答案:https ://stackoverflow.com/a/48610973/640397
3. 什么时候使用setState
回调比较好?
当外部代码需要等待状态更新时,您应该使用setState
回调而不是componentDidUpdate
,并将其包装在 Promise 中。
例如,假设我们有一个Child
如下所示的组件:
interface IProps {
onClick: () => Promise<void>;
}
class Child extends React.Component<IProps> {
private async click() {
await this.props.onClick();
console.log('Parent notified of click');
}
render() {
return <button onClick={this.click}>click me</button>;
}
}
我们有一个Parent
组件,它必须在点击孩子时更新一些状态:
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = { clicked: false };
}
private setClicked = (): Promise<void> => {
return new Promise((resolve) => this.setState({ clicked: true }, resolve));
}
render() {
return <Child onClick={this.setClicked} />;
}
}
在setClicked
中,我们必须创建一个Promise
返回给子级的方法,唯一的方法是向 . 传递一个回调setState
。
在 中创建它是不可能Promise
的componentDidUpdate
,但即使是这样,由于批处理,它也无法正常工作。
杂项。
由于
setState
是异步的,我正在考虑使用setState
回调函数(第二个参数)来确保代码在状态更新后执行,类似于.then()
Promise。
for 的回调与Promise 的工作setState()
方式不同,因此最好将您的知识分开。
setState
特别是如果我需要在后续调用之间重新渲染。
为什么你需要在调用之间重新渲染一个组件setState()
?
我能想象的唯一原因是父组件是否依赖于子 DOM 元素的某些信息,例如它的宽度或高度,并且父组件根据这些值在子组件上设置一些道具。
在您的示例中,您调用this.props.functionFromParentComponent()
,它返回一个值,然后您可以使用该值来计算某些状态。
首先,应该避免派生状态,因为记忆是一个更好的选择。但即便如此,为什么不让父级直接将值作为道具传递下去呢?然后,您至少可以计算 中的状态值getDerivedStateFromProps()
。
//firstVariable may or may not have been changed,
//now someVariableBeforeSetStateCall may or may not get updated at the same time
//as firstVariableWasSet or firstVariable due to async nature of setState
这些评论对我来说没有多大意义。的异步性质setState()
并不意味着状态没有得到正确更新。代码应该按预期工作。
推荐阅读
- angular - 分页在 Angular 材料中无法正常工作
- .net - Aspose.Pdf.TextStamp 与之前添加的文本重叠。如何防止这种情况发生?
- cross-compiling - How to cross-compile u-boot on cygwin?
- c# - Fill-up C# struct from C
- javascript - 无限 asyncIterator 未按预期工作
- r - R中的累积和向量
- python - 合并具有任意键的复杂字典列表
- terraform - Terraform 0.11 - 在资源之间共享复杂的属性值
- python - 创建嵌套列表的简单代码的意外问题
- java - hystrix 流的请求级别监控