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

Posted

技术标签:

【中文标题】与 setState 回调相比,使用 componentDidUpdate 有啥优势?【英文标题】:What is the advantage of using componentDidUpdate over the setState callback?与 setState 回调相比,使用 componentDidUpdate 有什么优势? 【发布时间】:2019-10-23 09:06:34 【问题描述】:

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

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

然而,官方 React Docs 说“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 回调更适合使用?

【问题讨论】:

组件可能会更新而不改变状态;你也有道具和背景。而setState 回调是一次性使用的回调。它会在应用此特定状态更改时通知您。 @Thomas 是的,如果我只想要一次性使用回调怎么办?我使用回调的主要原因是模拟同步代码,类似于 .then() componentDidUpdate 每次需要重新渲染时都会调用,包括 props 更改 然后使用 setState 和更新函数而不是回调。 this.setState((state, props) => return counter: state.counter + props.step; );。并且多个 setState 调用总是按顺序执行/应用,您不需要在回调中嵌套 setState 调用 @Thomas 在您引用的文档中也有一个回调。他没有谈论更新程序功能 【参考方案1】:

为什么比起setState回调函数更推荐使用componentDidUpdate

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>;
  

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

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

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

如果您希望与批量更新保持一致,通常使用componentDidMount() 会容易得多。

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

没关系。

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

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

有关更多信息,请参阅此答案:https://***.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 传递一个回调。

不可能在componentDidUpdate 中创建这个Promise,但即使这样,由于批处理,它也无法正常工作。

杂项

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

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

特别是如果我需要在随后的 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

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

【讨论】:

以上是关于与 setState 回调相比,使用 componentDidUpdate 有啥优势?的主要内容,如果未能解决你的问题,请参考以下文章

react的this.setState中的坑

使用 setState() 时出现 setState() 回调参数返回了一个 Future

在 React JS 的 this.setState 的回调中使用 this.setState?

如何使用 setState() 的回调? [复制]

在功能组件中使用回调对 setState 进行反应

无法在 socket.io 回调 React.js 中调用 setState