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

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

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

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


当前回答

我认为你需要的是useState和useCallback:

useState react doc; useCallback react doc;

示例代码

import React, { useCallback, useState } from 'react';

const Test = () => {
  const [name, setName] = useState("");
  const testCallback = useCallback(() => console.log(name), [name]);

  return (
    <button onClick={() => {
      setName("Michael")
      testCallback();
    }}>Name</button>
  )
};

export default Test;

其他回答

我认为,使用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。

UseEffect是主要的解决方案。但正如Darryl提到的,使用useEffect并将state作为第二个参数传入有一个缺陷,组件将在初始化过程中运行。如果你只想让回调函数使用更新后的状态值运行,你可以设置一个本地常量,并在setState和回调中使用它。

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

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

  // now you can "do something" with updatedNumber and don't have to worry about the async nature of setState!
  console.log(updatedNumber);
}

如果不需要异步更新状态,可以使用ref来保存值,而不是使用useState。

const name = useRef("John");
name.current = "Michael";
console.log(name.current); // will print "Michael" since updating the ref is not async

在我们有内置的setState回调支持之前,我们可以用简单的javascript方式…调用该函数并直接将新变量传递给它。

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

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

  function doSomethingWCounter(newCounter) {
    console.log(newCounter); // 123
  }

我写了自定义挂钩与typescript,如果有人还需要它。

import React, { useEffect, useRef, useState } from "react";

export const useStateWithCallback = <T>(initialState: T): [state: T, setState: (updatedState: React.SetStateAction<T>, callback?: (updatedState: T) => void) => void] => {
    const [state, setState] = useState<T>(initialState);
    const callbackRef = useRef<(updated: T) => void>();

    const handleSetState = (updatedState: React.SetStateAction<T>, callback?: (updatedState: T) => void) => {
        callbackRef.current = callback;
        setState(updatedState);
    };

    useEffect(() => {
        if (typeof callbackRef.current === "function") {
            callbackRef.current(state);
            callbackRef.current = undefined;
        }
    }, [state]);

    return [state, handleSetState];
}