通过setState回调使用componentDidUpdate有什么好处?

时间:2019-06-07 21:30:22

标签: javascript reactjs setstate

为什么建议在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回调更适合使用?

1 个答案:

答案 0 :(得分:3)

  

为什么推荐使用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(),它也可以打印新值。由于我们只有一个渲染发生,因此它只执行一次,并打印值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()的承诺

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()的异步性质并不表示有关状态无法正确更新的任何信息。该代码应按预期工作。