为什么建议在React组件中使用componentDidUpdate
而不是setState
回调函数(可选的第二个参数)(如果需要同步setState行为)?
由于setState
是异步的,因此我在考虑使用setState
回调函数(第二个参数)来确保状态更新后执行代码,类似于then()
的承诺。尤其是如果我需要在随后的setState
次调用之间重新渲染。
但是,官方的React Docs说:“ setState()的第二个参数是一个可选的回调函数,将在setState完成并重新渲染组件后执行。通常,对于这种逻辑,我们建议使用componentDidUpdate()代替”。 那就是他们在那儿所说的一切,所以似乎有点含糊。我想知道是否还有更具体的原因建议不要使用它?如果可以的话,我会问反应人员自己。
如果我希望顺序执行多个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 });
}
);
}
vs
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回调更适合使用?
答案 0 :(得分:3)
为什么推荐使用
componentDidUpdate
而不是setState
回调函数?
在使用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)进行更新,因此您将没有机会向这些外部更新添加回调。
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()
,它也可以打印新值。由于我们只有一个渲染发生,因此它只执行一次,并打印值42
。
如果您希望与批量更新保持一致,通常更容易使用componentDidMount()
。
没关系。
批处理是一种优化,因此,您绝不应该依赖于已发生或未发生的批处理。未来版本的React可能会在不同情况下执行或多或少的批处理。
但是,如果必须知道的话,在当前版本的React(16.8.x)中,批处理会在异步用户事件处理程序(例如onclick
)和有时生命周期方法中进行(如果有React)完全控制执行。所有其他上下文从不使用批处理。
有关更多信息,请参见此答案:https://stackoverflow.com/a/48610973/640397
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()
的承诺
setState()
的回调不能像promises那样完全工作,因此最好分离出您的知识。
特别是如果我需要在随后的
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()
的异步性质并不表示有关状态无法正确更新的任何信息。该代码应按预期工作。