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

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

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

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


当前回答

我们可以写一个叫做useScheduleNextRenderCallback的钩子,它返回一个“schedule”函数。在我们调用setState之后,我们可以调用“schedule”函数,传递一个我们希望在下次呈现时运行的回调。

import { useCallback, useEffect, useRef } from "react";

type ScheduledCallback = () => void;
export const useScheduleNextRenderCallback = () => {
  const ref = useRef<ScheduledCallback>();

  useEffect(() => {
    if (ref.current !== undefined) {
      ref.current();
      ref.current = undefined;
    }
  });

  const schedule = useCallback((fn: ScheduledCallback) => {
    ref.current = fn;
  }, []);

  return schedule;
};

使用示例:

const App = () => {
  const scheduleNextRenderCallback = useScheduleNextRenderCallback();

  const [state, setState] = useState(0);

  const onClick = useCallback(() => {
    setState(state => state + 1);
    scheduleNextRenderCallback(() => {
      console.log("next render");
    });
  }, []);

  return <button onClick={onClick}>click me to update state</button>;
};

简化的测试用例:https://stackblitz.com/edit/react-ts-rjd9jk

其他回答

你的问题很有道理。让我告诉您,useEffect在默认情况下运行一次,并在每次依赖数组更改之后运行一次。

请看下面的例子:

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

const App = () => {
  const [age, setAge] = useState(0);
  const [ageFlag, setAgeFlag] = useState(false);

  const updateAge = ()=>{
    setAgeFlag(false);
    setAge(age+1);
    setAgeFlag(true);
  };

  useEffect(() => {
    if(!ageFlag){
      console.log('effect called without change - by default');
    }
    else{
      console.log('effect called with change ');
    }
  }, [ageFlag,age]);

  return (
    <form>
      <h2>hooks demo effect.....</h2>
      {age}
      <button onClick={updateAge}>Text</button>
    </form>
  );
}

export default App;

如果你想要setState回调与钩子一起执行,那么使用标志变量并在useEffect中给出If ELSE OR If块,以便当条件满足时,只执行该代码块。无论如何,当依赖项数组改变时,效果会运行,但效果中的IF代码只会在特定的条件下执行。

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

编辑

在这里使用promise似乎仍然推迟了重新渲染后的执行,两次触发setState可能是获得最新状态的最佳解决方案。因为setState将被列出,我们只需要在重新渲染之前获得prevState。

最初的发布

我刚刚弄清楚了我们是否可以在这里使用一个Promise让setState变成可等待的。

这是我的实验结果,感觉比使用回调更好

主要是在useEffect中触发解析函数

function useAsyncState(initialState) {
  const [state, setState] = useState(initialState)
  const resolveCb = useRef()

  const handleSetState = (updatedState) => new Promise((resolve, reject) => {
    // force previous promise resolved
    if (typeof resolveCb.current === 'function') {
      resolveCb.current(updatedState)
    }
    resolveCb.current = resolve
    try {
      setState(updatedState)
    } catch(err) {
      resolveCb.current = undefined
      reject(err)
    }
  })

  useEffect(() => {
    if (typeof resolveCb.current === 'function') {
      resolveCb.current(state)
      resolveCb.current = undefined
    }
  }, [state])

  return [state, handleSetState]
}

组件中使用

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

  const increment = useMemoizedFn(async () => {
    const newCount = await setCount(count + 1)
    console.log(newCount)
  })

  console.log('rerender')

  return (
    <div>
      <h3 onClick={increment}>Hi, {count}</h3>
    </div>
  )
}

我有一个非常具体的用例,我需要在dom中呈现一个类,然后设置另一个类。这就是我的解决方案,我发现它相当优雅。

const [value1, setValue1] = useState({value: 'whatever', onValue: false})


useEffect(() => {
    setValue1(prev => ({
      value: 'whatever',
      onValue: !prev.onValue, 
    }));
}, ['whatever'])

 
useEffect(() => {

// if you want to ensure the render happens before doThing2() then put it in a timeout of 1ms,
  setTimeout(doThing2, 1); 

// or if you are happy to call it immediately after setting value don't include the timeout
 doThing2()


}, [value1.onValue])

这个怎么样:

const [Name, setName] = useState("");
...
onClick={()=>{
setName("Michael")
setName(prevName=>{...}) //prevName is Michael?
}}