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

bit*_*gmi 5 javascript setstate reactjs

Why is using componentDidUpdate more recommended over the setState callback function (optional second argument) in React components (if synchronous setState behavior is desired)?

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. Especially if I need a re-render in between subsequent setState calls.

However, the official React Docs say "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." And that's all they say about it there, so it seems a bit vague. I was wondering if there was a more specific reason it is recommended to not use it? If I could I would ask the React people themselves.

If I want multiple setState calls to be executed sequentially, the setState callback seems like a better choice over componentDidUpdate in terms of code organization - the callback code is defined right there with the setState call. If I use componentDidUpdate I have to check if the relevant state variable changed, and define the subsequent code there, which is less easy to track. Also, variables that were defined in the function containing the setState call would be out of scope unless I put them into state too.

The following example might show when it might be tricky to use 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 });
    }
  );
}
Run Code Online (Sandbox Code Playgroud)

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
}

Run Code Online (Sandbox Code Playgroud)

Also, apart from componentDidUpdate being generally recommended, in what cases would the setState callback be more appropriate to use?

App*_*les 12

为什么componentDidUpdatesetState回调函数更推荐使用?

1. 一致的逻辑

当使用回调参数 to 时setState(),你可能setState()在不同的地方有两个单独的调用,它们都更新相同的状态,你必须记住在两个地方使用相同的回调。

一个常见的例子是在状态发生变化时调用第三方服务:

private method1(value) {
    this.setState({ value }, () => {
        SomeAPI.gotNewValue(this.state.value);
    });
}

private method2(newval) {
    this.setState({ value }); // forgot callback?
}
Run Code Online (Sandbox Code Playgroud)

这可能是一个逻辑错误,因为您想在值发生变化时随时调用该服务。

这就是为什么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 });
}
Run Code Online (Sandbox Code Playgroud)

这样,就可以保证在状态更新时调用服务。

此外,状态可以从外部代码(例如 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>;
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

此外,我们还有一个componentDidUpdate(),它也打印新值。因为我们只发生了一次渲染,所以它只执行一次,并打印值42

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

2.1. 什么时候发生批处理?

没关系。

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

但是,如果你一定要知道,在当前版本的阵营(16.8.x),配料发生在异步用户事件处理程序(如onclick)和有时如果REACT拥有执行完全控制生命周期方法。所有其他上下文从不使用批处理。

有关更多信息,请参阅此答案:https : //stackoverflow.com/a/48610973/640397

3、什么时候用setState回调比较好?

当外部代码需要等待状态更新时,您应该使用setState回调而不是componentDidUpdate,并将其包装在承诺中。

例如,假设我们有一个如下所示的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>;
    }
}
Run Code Online (Sandbox Code Playgroud)

我们有一个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} />;
    }
}
Run Code Online (Sandbox Code Playgroud)

在 中setClicked,我们必须创建一个Promise来返回给孩子,唯一的方法是将回调传递给setState

不可能PromisecomponentDidUpdate.

杂项

由于setState是异步的,我正在考虑使用setState回调函数(第二个参数)来确保在状态更新后执行代码,类似于.then()承诺。

回调setState()相当的工作方式相同的承诺做的,所以它可能是最好分开你的知识。

特别是如果我需要在后续setState调用之间重新渲染。

为什么您需要在两次setState()调用之间重新渲染组件?

我能想象的唯一原因是父组件是否依赖于来自子 DOM 元素的某些信息,例如其宽度或高度,并且父组件基于这些值在子组件上设置了一些道具。

在您的示例中,您调用this.props.functionFromParentComponent(),它返回一个值,然后您可以使用该值来计算某个状态。

首先,应该避免派生状态,因为记忆化是一个更好的选择。但即便如此,为什么不让父级直接将值作为道具传递?然后你至少可以计算 中的状态值getDerivedStateFromProps()

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>;
    }
}
Run Code Online (Sandbox Code Playgroud)

这些评论对我来说没有多大意义。的异步性质setState()并不意味着状态没有得到正确更新。代码应该按预期工作。