使用 componentDidUpdate 相对于 setState 回调有什么优势?

What is the advantage of using componentDidUpdate over the setState callback?

为什么在 React 组件中使用 componentDidUpdate 而不是 setState 回调函数(可选的第二个参数)(如果需要同步 setState 行为)?

由于setState是异步的,我在考虑使用setState回调函数(第二个参数)来确保代码在状态更新后执行,类似于then()为了承诺。特别是如果我需要在后续 setState 调用之间重新渲染。

但是,官方 React 文档说 "The second parameter to setState() is an optional callback function that will be executed once setState is completed and the component is re-rendered. Generally we recommend using componentDidUpdate() for such logic instead." 这就是他们在那里所说的全部内容,所以看起来有点含糊。我想知道是否有更具体的原因建议不要使用它?如果可以的话,我会问问 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回调在什么情况下更合适?

Why is using componentDidUpdate more recommended over the setState callback function?

1。逻辑一致

当使用 setState() 的回调参数时,您可能在不同的地方对 setState() 进行了两次单独的调用,它们都更新了相同的状态,您必须记住使用相同的两地回调。

一个常见的例子是每当状态发生变化时调用 third-party 服务:

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()的回调参数在组件re-rendered之后执行。但是,由于批处理,多次调用 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>;
  }
}

我们在 onClick() 处理程序中有两个 setState() 调用,每个调用只是将新的状态值打印到控制台。

您可能希望 onClick() 打印值 7,然后是 42。但实际上,它打印了 42 两次!这是因为两个 setState() 调用是一起批处理的,只会导致一个渲染发生。

此外,我们还有一个 componentDidUpdate(),它也打印新值。由于我们只发生一次渲染,它只执行一次,并打印值 42.

如果您希望与批量更新保持一致,通常使用 componentDidMount().

会容易得多

2.1。什么时候进行批处理?

没关系。

批处理是一种优化,因此您永远不应依赖批处理的发生或不发生。 React 的未来版本可能会在不同的场景中执行或多或少的批处理。

但是,如果您必须知道,在当前版本的 React (16.8.x) 中,批处理发生在异步用户事件处理程序中(例如 onclick)并且 有时 生命周期方法,如果 React 可以完全控制执行。所有其他上下文从不使用批处理。

查看此答案了解更多信息:

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 组件,它必须在 child 被点击时更新一些状态:

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 到 return 到 child,唯一的方法是将回调传递给 setState.

无法在 componentDidUpdate 中创建此 Promise,但即使创建,由于批处理也无法正常工作。

其他

Since setState is asynchronous, I was thinking about using the setState callback function (2nd argument) to ensure that code is executed after state has been updated, similar to .then() for promises.

setState() 的回调完全 与 promises 的工作方式不同,因此最好将您的知识分开。

Especially if I need a re-render in between subsequent setState calls.

为什么您需要 re-render 在 setState() 调用之间的组件?

我能想到的唯一原因是 parent 组件是否依赖于 child 的 DOM 元素的某些信息,例如它的宽度或高度,以及 parent 根据这些值在 child 上设置一些道具。

在您的示例中,您调用 this.props.functionFromParentComponent(),它 return 是一个值,然后您使用它来计算某些状态。

首先,应该避免派生状态,因为记忆是一个更好的选择。但即便如此,为什么不让 parent 直接将值作为 prop 向下传递呢?然后你至少可以计算 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() 的异步性质并不意味着状态没有得到正确更新。该代码应按预期工作。