如何在react-hooks上使用`setState`回调

IT技术 reactjs react-hooks use-state
2021-04-16 23:42:34

React hooks 引入useState用于设置组件状态。但是如何使用钩子来替换回调,如下面的代码:

setState(
  { name: "Michael" },
  () => console.log(this.state)
);

我想在状态更新后做一些事情。

我知道我可以useEffect用来做额外的事情,但我必须检查状态以前的值,这需要一些代码。我正在寻找一个可以与useState钩子一起使用的简单解决方案

6个回答

您需要使用useEffect钩子来实现这一点。

const [counter, setCounter] = useState(0);

const doSomething = () => {
  setCounter(123);
}

useEffect(() => {
   console.log('Do something after counter has changed', counter);
}, [counter]);
useEffect不能在所有场景中使用。有时状态会从多个位置更新,您希望仅使用来自这些位置之一的调用。如何轻松区分?在这种情况下回调是完美的。黑客将使用另一个丑陋的useState方法,以便可以检测到特定的变化。非常难看...
2021-06-05 23:42:34
那么如果我想在不同的 setState 调用中调用不同的回调,这会改变相同的状态值呢?您的答案是错误的,不应将其标记为正确以免混淆新手。事实是 setState 在从没有明确解决方法的类中迁移钩子时回调其最困难的问题之一。有时你真的会得到一些依赖于值的效果,有时它需要一些hacky方法,比如在Refs中保存某种标志。
2021-06-06 23:42:34
这将console.log在第一次渲染以及任何时间counter更改时触发如果你只想在状态更新后做一些事情,而不是在初始渲染时做一些事情,因为设置了初始值怎么办?我想您可以检查该值useEffect并决定您是否想做某事。这会被视为最佳实践吗?
2021-06-09 23:42:34
@KenIngram 我刚刚尝试过并得到了这个非常具体的错误: Warning: State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().
2021-06-09 23:42:34
为了避免useEffect在初始渲染时运行,您可以创建一个自定义useEffecthook,它不会在初始渲染时运行。要创建这样的钩子,你可以查看这个问题:stackoverflow.com/questions/53253940/...
2021-06-20 23:42:34

如果你想更新以前的状态,那么你可以在钩子中这样做:

const [count, setCount] = useState(0);


setCount(previousCount => previousCount + 1);
我想setCounter你的意思setCount
2021-06-07 23:42:34
@tonitone120 在 setCount 中有一个箭头函数。
2021-06-07 23:42:34
@BimalGrg 这真的有效吗?我无法复制这一点。它无法编译,出现此错误:expected as assignment or function call and instead saw an expression
2021-06-10 23:42:34
@tonitone120 如果您想在状态更新后立即执行代码,那么您必须使用 useEffect 钩子。在 useEffect 中,您可以检查状态是否更新并相应地执行任何操作。
2021-06-10 23:42:34
@BimalGrg 问题是要求能够状态更新立即执行代码这段代码真的有助于完成这项任务吗?
2021-06-11 23:42:34

用 模仿setState回调useEffect只触发状态更新(不是初始状态):

const [state, setState] = useState({ name: "Michael" })
const isFirstRender = useRef(true)
useEffect(() => {
  if (isFirstRender.current) {
    isFirstRender.current = false // toggle flag after first render/mounting
    return;
  }
  console.log(state) // do something after state has updated
}, [state])

定制挂钩 useEffectUpdate

function useEffectUpdate(callback) {
  const isFirstRender = useRef(true);
  useEffect(() => {
    if (isFirstRender.current) {
      isFirstRender.current = false; // toggle flag after first render/mounting
      return;
    }
    callback(); // performing action after state has updated
  }, [callback]);
}

// client usage, given some state dep
const cb = useCallback(() => { console.log(state) }, [state]); // memoize callback
useEffectUpdate(cb);
替代方案:useState 可以实现setState在类中一样接收回调
2021-06-07 23:42:34

我认为,使用useEffect不是一种直观的方式。

我为此创建了一个包装器。在这个自定义钩子中,您可以将回调传输到setState参数而不是useState参数。

我刚刚创建了 Typescript 版本。因此,如果您需要在 Javascript 中使用它,只需从代码中删除一些类型符号。

用法

const [state, setState] = useStateCallback(1);
setState(2, (n) => {
  console.log(n) // 2
});

宣言

import { SetStateAction, useCallback, useEffect, useRef, useState } from 'react';

type Callback<T> = (value?: T) => void;
type DispatchWithCallback<T> = (value: T, callback?: Callback<T>) => void;

function useStateCallback<T>(initialState: T | (() => T)): [T, DispatchWithCallback<SetStateAction<T>>] {
  const [state, _setState] = useState(initialState);

  const callbackRef = useRef<Callback<T>>();
  const isFirstCallbackCall = useRef<boolean>(true);

  const setState = useCallback((setStateAction: SetStateAction<T>, callback?: Callback<T>): void => {
    callbackRef.current = callback;
    _setState(setStateAction);
  }, []);

  useEffect(() => {
    if (isFirstCallbackCall.current) {
      isFirstCallbackCall.current = false;
      return;
    }
    callbackRef.current?.(state);
  }, [state]);

  return [state, setState];
}

export default useStateCallback;

退税

如果传递的箭头函数引用了一个变量外部函数,那么它将捕获当前值而不是状态更新后的值。在上面的用法示例中,console.log(state)将打印 1 而不是 2。

谢谢!很酷的方法。创建了一个示例代码和框
2021-05-21 23:42:34
@Ankan-Zerob 我认为,在使用部分,state在调用回调时指的是前一个。不是吗?
2021-05-29 23:42:34
@ValYouW 感谢您创建示例。唯一的问题是,如果传递的箭头函数引用变量外函数,那么它将捕获当前值而不是状态更新后的值。
2021-05-31 23:42:34
是的,这就是功能组件的吸引人的部分......
2021-06-13 23:42:34
这里如何获取之前的状态值?
2021-06-19 23:42:34

我遇到了同样的问题,在我的设置中使用 useEffect 没有解决问题(我正在从多个子组件的数组中更新父级的状态,我需要知道哪个组件更新了数据)。

在 promise 中包装 setState 允许在完成后触发任意操作:

import React, {useState} from 'react'

function App() {
  const [count, setCount] = useState(0)

  function handleClick(){
    Promise.resolve()
      .then(() => { setCount(count => count+1)})
      .then(() => console.log(count))
  }


  return (
    <button onClick= {handleClick}> Increase counter </button>
  )
}

export default App;

以下问题让我找到了正确的方向: React 使用钩子时是否有批处理状态更新功能?

setCount是异步的,对吗?如果是这样,就会出现竞争条件,并且console.log可能会打印一个旧值。
2021-06-11 23:42:34