何时使用 React setState 回调

When to use React setState callback

提问人:Sahil Jain 提问时间:2/4/2017 最后编辑:Shubham KhatriSahil Jain 更新时间:8/29/2023 访问量:498428

问:

当 react 组件状态发生变化时,将调用 render 方法。因此,对于任何状态更改,都可以在 render 方法正文中执行操作。那么 setState 回调是否有特定的用例?

ReactJS 回调 setstate

评论

4赞 Davin Tryon 2/4/2017
目前尚不清楚你在问什么。你能包含一些代码吗?
4赞 Jayce444 2/4/2017
setState 回调适用于在状态 DEFINITELYbeen 更改后要执行的任何操作。由于 setState 是异步的,如果你想调用一个 fx 并确保加载了新状态,那么这就是回调的目的
4赞 M3RS 4/3/2018
setState 回调的用例非常清楚。当您希望函数在 SPECIFIC 状态更新后运行时,可以使用它。如果你把这个函数放进去,它就会在每次更新任何状态时运行,这可能不是你想要的。这也将使您的代码的可读性和逻辑性降低。render()
0赞 Jonathan Orrego 11/15/2022
一种用途是当您使用状态来存储来自服务器的结果时...你现在想要它,而不是在渲染之后,因为状态是自定义的,比如......myVar、setMyVar

答:

326赞 Shubham Khatri 2/4/2017 #1

是的,因为在某种程度上起作用。这意味着调用变量后不会立即更改。因此,如果您想在状态变量上设置状态后立即执行操作,然后返回结果,则回调将很有用setStateasynchronoussetStatethis.state

请看下面的例子

....
changeTitle: function changeTitle (event) {
  this.setState({ title: event.target.value });
  this.validateTitle();
},
validateTitle: function validateTitle () {
  if (this.state.title.length === 0) {
    this.setState({ titleError: "Title can't be blank" });
  }
},
....

上述代码可能无法按预期工作,因为在对变量执行验证之前,变量可能尚未发生突变。现在您可能想知道我们可以在函数本身中执行验证,但是如果我们可以在 changeTitle 函数本身中处理这个问题,那将是更好、更干净的方式,因为这将使您的代码更有条理和易于理解titlerender()

在这种情况下,回调很有用

....
changeTitle: function changeTitle (event) {
  this.setState({ title: event.target.value }, function() {
    this.validateTitle();
  });

},
validateTitle: function validateTitle () {
  if (this.state.title.length === 0) {
    this.setState({ titleError: "Title can't be blank" });
  }
},
....

另一个例子是,当你想要时,当状态改变时,你会采取行动。您将希望在回调中执行此操作,而不是每次发生重新渲染时都会调用它,因此许多此类场景都可能需要回调。dispatchrender()

另一种情况是 API 调用

当您需要根据特定状态更改进行 API 调用时,可能会出现这种情况,如果您在 render 方法中执行此操作,它将在每次渲染更改时调用,或者因为某些 Prop 传递给更改了。onStateChild Component

在本例中,您需要使用 a 将更新的状态值传递给 API 调用setState callback

....
changeTitle: function (event) {
  this.setState({ title: event.target.value }, () => this.APICallFunction());
},
APICallFunction: function () {
  // Call API with the updated value
}
....

评论

5赞 Sahil Jain 2/4/2017
我知道它本质上是异步的。我的问题是,是否有一些特定的东西只能用于 setState 回调,也许渲染方法主体可能不支持(除了让我们说更好的代码可读性之外。
0赞 Shubham Khatri 2/4/2017
@SahilJain Validation 是正确的示例,您不会希望在 render() 函数中处理它,因为这样每次您在 render() 中进行任何更改时都会调用它,您只想在只有输入更改时调用它,因此在函数本身中
0赞 webdeb 2/4/2017
React 禁止在渲染过程中更改状态。因此,将验证放入回调中是正确的。
0赞 Dmitry Minkovsky 10/11/2017
if (this.title.length === 0) {应该是,对吧?this.state.title.length
7赞 R Esmond 12/16/2018
第一个用例可能不是一个好主意。setState 回调在重新渲染后触发,因此您无缘无故地导致双重渲染。这正是函数参数(updater)的目的。你只需运行,更改就会叠加。无需双重渲染。setState(state => state.title.length ? { titleError: "Title can't be blank" } : null)
48赞 webdeb 2/4/2017 #2

这 1.我想到的用例是一个调用,它不应该进入渲染,因为它会运行状态更改。API 调用应该只在特殊状态更改时执行,而不是在每次渲染时执行。apieach

changeSearchParams = (params) => {
  this.setState({ params }, this.performSearch)
} 

performSearch = () => {
  API.search(this.state.params, (result) => {
    this.setState({ result })
  });
}

因此,对于任何状态更改,都可以在 render 方法正文中执行操作。

非常糟糕的做法,因为 -method 应该是纯的,这意味着不应该执行任何操作、状态更改、api 调用,只需合成您的视图并返回它。应仅对某些事件执行操作。渲染不是事件,而是例如。rendercomponentDidMount

39赞 Aniket Jha 3/10/2018 #3

考虑 setState 调用

this.setState({ counter: this.state.counter + 1 })

想法

setState 可以在异步函数中调用

所以你不能依赖.如果上述调用是在异步函数内部进行的,则该函数将引用该时间点组件的状态,但我们希望这指的是 setState 调用或异步任务开始时状态内部的属性。由于任务是异步调用,因此该属性可能在一段时间内发生了变化。因此,使用关键字来引用状态的某些属性是不可靠的,因此我们使用参数为 previousState 和 props 的回调函数,这意味着当异步任务完成并且是时候使用 setState 调用更新状态时,prevState 将在 setState 尚未启动时立即引用状态。确保 nextState 不会损坏的可靠性。thisthisthis

错误代码:会导致数据损坏

this.setState(
   {counter:this.state.counter+1}
 );

具有回调功能的 setState 的正确代码:

 this.setState(
       (prevState,props)=>{
           return {counter:prevState.counter+1};
        }
    );

因此,每当我们需要根据刚才属性所拥有的值将当前状态更新为下一个状态并且所有这些都以异步方式发生时,最好使用 setState 作为回调函数。

我试图在这里用 codepen CODE PEN 解释它

76赞 Araz Babayev 5/30/2018 #4
this.setState({
    name:'value' 
},() => {
    console.log(this.state.name);
});

评论

28赞 Machavity 5/30/2018
感谢您提供此代码片段,它可能会提供一些有限的即时帮助。一个适当的解释将大大提高它的长期价值,说明为什么这是解决问题的一个很好的解决方案,并使它对未来有其他类似问题的读者更有用。请编辑您的答案以添加一些解释,包括您所做的假设。
1赞 Araz Babayev 5/30/2018
如果要在状态更改后调用函数,可以使用该方法。
0赞 Sumanth Varada 8/13/2019
如果您想设置多个状态,如名称、名字等,该怎么办?
4赞 abitcode 3/26/2022
不适用于 react hook useState
4赞 Abdul Azeem 12/11/2020 #5

有时我们需要一个代码块,我们需要在 setState 之后立即执行一些操作,以确保状态正在更新。这就是 setState 回调发挥作用的地方

例如,有一个场景,我需要为 20 个客户中的 2 个客户启用模式,对于我们启用它的客户,有一组时间进行 API 调用,所以它看起来像这样

async componentDidMount() {
  const appConfig = getCustomerConfig();
  this.setState({enableModal: appConfig?.enableFeatures?.paymentModal }, async 
   ()=>{
     if(this.state.enableModal){
       //make some API call for data needed in poput
     }
  });
}

render 函数的 UI 块中也需要 enableModal 布尔值,这就是我在这里执行 setState 的原因,否则,可能只检查一次条件,要么调用 API 集,要么不调用 API 集。

0赞 puchu 5/2/2023 #6

回调有一个罕见但重要的案例。setState

您希望在更新属性后重新计算状态,并且您的重新计算可能会再次更新。你不能只使用 depending on ,因为 React 会将其视为“检测到循环依赖”。你可以通过使用回调来规避 React。但请确保您没有创建无限循环。xxuseEffectxsetState

我已经将这个解决方案应用于 2 个巨大的 React 项目。

2赞 AmerllicA 8/29/2023 #7

功能组件的 setState 回调

所有答案都是关于如何将函数传递给类组件的 setState 函数。但是对于功能组件,我强烈建议使用自定义钩子函数:callback

import { useRef, useCallback, useEffect, useState } from 'react';
import type { Dispatch, SetStateAction } from 'react';
import isFunction from 'lodash.isfunction';

type StateFunctionType<S> = Dispatch<SetStateAction<S>>;
export type SetStateCallbackGeneric<S> = (
  x: S | StateFunctionType<S>,
  cb?: (newState: S) => void
) => void;

const useStateCallback = <T>(
  initialState: T
): [T, SetStateCallbackGeneric<T>] => {
  const [state, setState] = useState<T>(initialState);
  const cbRef = useRef<any>(null);

  const setStateCallback: SetStateCallbackGeneric<T> = useCallback(
    (newState, cb) => {
      cbRef.current = cb;
      setState(newState as any);
    },
    []
  );

  useEffect(() => {
    if (isFunction(cbRef?.current)) {
      // @ts-ignore
      cbRef?.current?.(state);
      cbRef.current = null;
    }
  }, [state]);

  return [state, setStateCallback];
};

export default useStateCallback;

通过使用这个钩子函数,你可以很容易地将一个函数传递给 setter 函数,请看一个例子:callback

const [text, setText] = useStateCallback<string>('')

const handleFoo = (txt: string) => {
  setText(txt, () => {
    // Do What you want exactly AFTER text gets updated
  })
};