与 setState 回调相比,使用 componentDidUpdate 有什么优势? [英] What is the advantage of using componentDidUpdate over the setState callback?

查看:50
本文介绍了与 setState 回调相比,使用 componentDidUpdate 有什么优势?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

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.

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

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.

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

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.

以下示例可能会显示何时使用 componentDidUpdate 可能会很棘手:

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

对比

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 回调更适合使用?

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

推荐答案

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

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

1.逻辑一致

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

1. Consistent logic

When using the callback argument to setState(), you might have two separate calls to setState() in different places which both update the same state, and you'd have to remember to use the same callback in both places.

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

A common example is making a call to a third-party service whenever a piece of state changes:

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

private method2(newval) {
    this.setState({ value }); // forgot callback?
}

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

This is probably a logic error, since presumably you'd want to call the service any time the value changes.

这就是推荐 componentDidUpdate() 的原因:

This is why componentDidUpdate() is recommended:

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

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

This way, the service is guaranteed to be called whenever the state updates.

此外,状态可以从外部代码(例如 Redux)更新,您将没有机会为这些外部更新添加回调.

Additionally, state could be updated from external code (e.g. Redux), and you won't have a chance to add a callback to those external updates.

setState() 的回调参数在组件重新渲染后执行.但是,由于批处理,多次调用 setState() 并不能保证导致多次渲染.

The callback argument of setState() executes after the component is re-rendered. However, multiple calls to setState() are not guaranteed to cause multiple renders, due to batching.

考虑这个组件:

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() 调用,每个调用都只是将新的状态值打印到控制台.

We have two setState() calls in the onClick() handler, each simply prints the new state value to the console.

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

You might expect onClick() to print the value 7 and then 42. But actually, it prints 42 twice! This is because the two setState() calls are batched together, and only cause one render to occur.

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

Also, we have a componentDidUpdate() which also prints the new value. Since we only have one render occurring, it is only executed once, and prints the value 42.

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

If you want consistency with batched updates, it's usually far easier to use componentDidMount().

没关系.

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

Batching is an optimization, and therefore you should never rely either on batching occurring or it not occurring. Future versions of React may perform more or less batching in different scenarios.

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

But, if you must know, in the current version of React (16.8.x), batching occurs in asynchronous user event handlers (e.g. onclick) and sometimes lifecycle methods if React has full control over the execution. All other contexts never use batching.

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

See this answer for more info: https://stackoverflow.com/a/48610973/640397

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

When external code needs to wait for the state to be updated, you should use the setState callback instead of componentDidUpdate, and wrap it in a promise.

例如,假设我们有一个 Child 组件,它看起来像这样:

For example, suppose we have a Child component which looks like this:

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

And we have a Parent component which must update some state when the child is clicked:

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.

In setClicked, we must create a Promise to return to the child, and the only way to do that is by passing a callback to setState.

不可能在 componentDidUpdate 中创建这个 Promise,但即使创建了,它也不会因为批处理而正常工作.

It's not possible to create this Promise in componentDidUpdate, but even if it were, it wouldn't work properly due to batching.

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

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() 的回调与 Promise 的工作方式完全不同,因此最好将您的知识分开.

The callback for setState() doesn't quite work the same way as promises do, so it might be best to separate your knowledge.

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

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

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

Why would you ever need to re-render a component in between setState() calls?

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

The only reason I can imagine is if the parent component depends on some info from the child's DOM element, such as its width or height, and the parent sets some props on the child based on those values.

在你的例子中,你调用 this.props.functionFromParentComponent(),它返回一个值,然后你用它来计算一些状态.

In your example, you call this.props.functionFromParentComponent(), which returns a value, which you then use to compute some state.

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

Firstly, derived state should be avoided, since memoization is a much better option. But even so, why not just have the parent pass the value directly down as a prop? Then you can at least compute the state value in 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() 的异步特性并不意味着状态没有得到正确更新.代码应该按预期工作.

These comments don't make much sense to me. The asynchronous nature of setState() doesn't imply anything about state not getting properly updated. The code should work as intended.

这篇关于与 setState 回调相比,使用 componentDidUpdate 有什么优势?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆