与 setState 回调相比,使用 componentDidUpdate 有什么优势?

IT技术 javascript reactjs setstate
2021-04-30 15:53:00

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

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

然而,React 官方文档说“setState() 的第二个参数是一个可选的回调函数,一旦 setState 完成并重新渲染组件就会执行。通常我们建议使用 componentDidUpdate() 代替这种逻辑。” 这就是他们在那里所说的一切,所以看起来有点含糊。我想知道是否有更具体的原因建议不要使用它?如果可以的话,我会问 React 的人自己。

如果我希望按顺序执行多个 setState 调用,就代码组织而言,setState 回调似乎是比 componentDidUpdate 更好的选择 - 回调代码就在 setState 调用中定义。如果我使用 componentDidUpdate 我必须检查相关状态变量是否改变,并在那里定义后续代码,这不太容易跟踪。此外,在包含 setState 调用的函数中定义的变量将超出范围,除非我也将它们置于 state 中。

以下示例可能会显示何时使用 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 回调更适合使用?

1个回答

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

1. 一致的逻辑

当使用回调参数 to 时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 的未来版本可能会在不同的场景中执行或多或少的批处理。

但是,如果你一定要知道,在当前版本的阵营(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

这是不可能创建此PromisecomponentDidUpdate,但即使是这样,它不会正确由于批处理工作。

杂项

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

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

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

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

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

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

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