useState 设置方法不会立即反映更改

Posted

技术标签:

【中文标题】useState 设置方法不会立即反映更改【英文标题】:useState set method not reflecting change immediately 【发布时间】:2021-08-26 04:36:48 【问题描述】:

我正在尝试学习钩子,useState 方法让我感到困惑。我正在以数组的形式为状态分配初始值。 useState 中的 set 方法对我不起作用,无论有没有扩展运算符。

我在另一台 PC 上创建了一个 API,我正在调用它并获取我想要设置为状态的数据。

这是我的代码:

<div id="root"></div>

<script type="text/babel" defer>
// import React,  useState, useEffect  from "react";
// import ReactDOM from "react-dom";
const  useState, useEffect  = React; // web-browser variant

const StateSelector = () => 
  const initialValue = [
    
      category: "",
      photo: "",
      description: "",
      id: 0,
      name: "",
      rating: 0
    
  ];

  const [movies, setMovies] = useState(initialValue);

  useEffect(() => 
    (async function() 
      try 
        // const response = await fetch("http://192.168.1.164:5000/movies/display");
        // const json = await response.json();
        // const result = json.data.result;
        const result = [
          
            category: "cat1",
            description: "desc1",
            id: "1546514491119",
            name: "randomname2",
            photo: null,
            rating: "3"
          ,
          
            category: "cat2",
            description: "desc1",
            id: "1546837819818",
            name: "randomname1",
            rating: "5"
          
        ];
        console.log("result =", result);
        setMovies(result);
        console.log("movies =", movies);
       catch (e) 
        console.error(e);
      
    )();
  , []);

  return <p>hello</p>;
;

const rootElement = document.getElementById("root");
ReactDOM.render(<StateSelector />, rootElement);
</script>

<script src="https://unpkg.com/@babel/standalone@7/babel.min.js"></script>
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>

setMovies(result)setMovies(...result) 都不起作用。

我希望将结果变量推入电影数组。

【问题讨论】:

您能否看到将console.log("movies =", movies); 移动到useEffect 挂钩之外的更改? 这在a meta question中用作示例 【参考方案1】:

类似于扩展React.ComponentReact.PureComponent创建的Class组件中的setState,使用useState钩子提供的updater更新状态也是异步的,不会立即反映。

此外,这里的主要问题不仅仅是异步性质,而是函数基于当前闭包使用状态值这一事实,并且状态更新将反映在现有闭包的下一次重新渲染中不受影响,但会创建新的。现在在当前状态下,钩子中的值是通过现有的闭包获得的,当重新渲染发生时,闭包会根据函数是否再次重新创建而更新。

即使你添加了 setTimeout 函数,虽然超时会在重新渲染发生的一段时间后运行,但 setTimeout 仍将使用之前关闭的值,而不是更新后的值.

setMovies(result);
console.log(movies) // movies here will not be updated

如果要对状态更新执行操作,则需要使用 useEffect 挂钩,就像在类组件中使用 componentDidUpdate 一样,因为 useState 返回的 setter 没有回调模式

useEffect(() => 
    // action on update of movies
, [movies]);

就更新状态的语法而言,setMovies(result) 将用异步请求中可用的值替换状态中之前的 movies 值。

但是,如果要将响应与先前存在的值合并,则必须使用状态更新的回调语法以及正确使用传播语法,例如

setMovies(prevMovies => ([...prevMovies, ...result]));

【讨论】:

嗨,在表单提交处理程序中调用 useState 怎么样?我正在验证一个复杂的表单,我在 submitHandler useState 钩子中调用,不幸的是,更改不是立即的! useEffect 可能不是最好的解决方案,因为它不支持异步调用。因此,如果我们想对 movies 状态更改进行一些异步验证,我们无法控制它。 请注意,虽然建议非常好,但对原因的解释可以改进 - 与 the updater provided by useState hook 是否异步这一事实无关,不像 this.state如果this.setState 是同步的,则发生突变,即使useState 提供了同步函数,const movies 周围的闭包也将保持不变-请参阅我的答案中的示例 setMovies(prevMovies =&gt; ([...prevMovies, ...result])); 为我工作 它记录了错误的结果,因为您正在记录 stale closure 而不是因为 setter 是异步的。如果异步是问题所在,那么您可以在超时后记录,但您可以设置一个小时的超时并仍然记录错误的结果,因为异步不是导致问题的原因。【参考方案2】:

previous answer 的其他详细信息:

虽然 React 的 setState 是异步的(类和钩子),并且很想用这个事实来解释观察到的行为,但这不是 原因它发生了。

TLDR:原因是 closure 范围围绕不可变的 const 值。


解决方案:

读取渲染函数中的值(不在嵌套函数中):

  useEffect(() =>  setMovies(result) , [])
  console.log(movies)

将变量添加到依赖项中(并使用react-hooks/exhaustive-deps eslint 规则):

  useEffect(() =>  setMovies(result) , [])
  useEffect(() =>  console.log(movies) , [movies])

使用临时变量:

  useEffect(() => 
    const newMovies = result
    console.log(newMovies)
    setMovies(newMovies)
  , [])

使用可变引用(如果我们不需要状态并且只想记住值 - 更新引用不会触发重新渲染):

  const moviesRef = useRef(initialValue)
  useEffect(() => 
    moviesRef.current = result
    console.log(moviesRef.current)
  , [])

解释为什么会发生:

如果异步是唯一的原因,那么await setState() 是可能的。

但是,propsstate 都是 assumed to be unchanging during 1 render。

this.state 视为不可变的。

有了钩子,这个假设通过使用带有const关键字的常量值得到增强:

const [state, setState] = useState('initial')

2 次渲染之间的值可能不同,但在渲染本身和任何 closures 内保持不变(即使在渲染完成后寿命更长的函数,例如 useEffect,事件处理程序,在任何 Promise 或 setTimeout 内)。

考虑仿造,但同步,类似 React 的实现:

// sync implementation:

let internalState
let renderAgain

const setState = (updateFn) => 
  internalState = updateFn(internalState)
  renderAgain()


const useState = (defaultState) => 
  if (!internalState) 
    internalState = defaultState
  
  return [internalState, setState]


const render = (component, node) => 
  const html, handleClick = component()
  node.innerHTML = html
  renderAgain = () => render(component, node)
  return handleClick


// test:

const MyComponent = () => 
  const [x, setX] = useState(1)
  console.log('in render:', x) // ✅
  
  const handleClick = () => 
    setX(current => current + 1)
    console.log('in handler/effect/Promise/setTimeout:', x) // ❌ NOT updated
  
  
  return 
    html: `<button>$x</button>`,
    handleClick
  


const triggerClick = render(MyComponent, document.getElementById('root'))
triggerClick()
triggerClick()
triggerClick()
&lt;div id="root"&gt;&lt;/div&gt;

【讨论】:

优秀的答案。国际海事组织,如果您花足够的时间阅读和吸收它,这个答案将为您在未来节省最多的心碎。 @AlJoslin 乍一看,这似乎是一个单独的问题,即使它可能是由闭包范围引起的。如果您有具体问题,请使用代码示例和所有内容创建一个新的 *** 问题... 实际上,我刚刚完成了使用 useReducer 的重写,遵循@kentcdobs 文章(参考下文),这确实给了我一个可靠的结果,没有受到这些关闭问题的影响。 (参考:kentcdodds.com/blog/how-to-use-react-context-effectively) @ACV 解决方案 2 适用于原始问题。如果您需要解决不同的问题,YMMW,但我仍然 100% 确定引用的代码按文档说明工作,并且问题出在其他地方。 这应该是“接受”答案@Pranjal【参考方案3】:
// replace
return <p>hello</p>;
// with
return <p>JSON.stringify(movies)</p>;

现在您应该看到,您的代码确实确实工作。不起作用的是console.log(movies)。这是因为movies 指向旧状态。如果您将console.log(movies) 移到useEffect 之外,在返回的正上方,您将看到更新后的电影对象。

【讨论】:

不确定为什么这个答案被大量否决,它告诉如何通过将它放在 useState 函数之外来获得“预期的”console.log 值。简单而甜蜜,如果有人想知道为什么会这样,可以参考上面的详细说明【参考方案4】:

我刚刚完成了 useReducer 的重写,遵循了@kentcdobs 文章(参考下文),它确实给了我一个可靠的结果,并且没有受到这些关闭问题的影响。

见:https://kentcdodds.com/blog/how-to-use-react-context-effectively

我将他的可读样板压缩到我喜欢的 DRYness 级别——阅读他的沙盒实现将向您展示它的实际工作原理。

import React from 'react'

// ref: https://kentcdodds.com/blog/how-to-use-react-context-effectively

const ApplicationDispatch = React.createContext()
const ApplicationContext = React.createContext()

function stateReducer(state, action) 
  if (state.hasOwnProperty(action.type)) 
    return  ...state, [action.type]: state[action.type] = action.newValue ;
  
  throw new Error(`Unhandled action type: $action.type`);


const initialState = 
  keyCode: '',
  testCode: '',
  testMode: false,
  phoneNumber: '',
  resultCode: null,
  mobileInfo: '',
  configName: '',
  appConfig: ,
;

function DispatchProvider( children ) 
  const [state, dispatch] = React.useReducer(stateReducer, initialState);
  return (
    <ApplicationDispatch.Provider value=dispatch>
      <ApplicationContext.Provider value=state>
        children
      </ApplicationContext.Provider>
    </ApplicationDispatch.Provider>
  )


function useDispatchable(stateName) 
  const context = React.useContext(ApplicationContext);
  const dispatch = React.useContext(ApplicationDispatch);
  return [context[stateName], newValue => dispatch( type: stateName, newValue )];


function useKeyCode()  return useDispatchable('keyCode'); 
function useTestCode()  return useDispatchable('testCode'); 
function useTestMode()  return useDispatchable('testMode'); 
function usePhoneNumber()  return useDispatchable('phoneNumber'); 
function useResultCode()  return useDispatchable('resultCode'); 
function useMobileInfo()  return useDispatchable('mobileInfo'); 
function useConfigName()  return useDispatchable('configName'); 
function useAppConfig()  return useDispatchable('appConfig'); 

export 
  DispatchProvider,
  useKeyCode,
  useTestCode,
  useTestMode,
  usePhoneNumber,
  useResultCode,
  useMobileInfo,
  useConfigName,
  useAppConfig,

类似这样的用法:

import  useHistory  from "react-router-dom";

// https://react-bootstrap.github.io/components/alerts
import  Container, Row  from 'react-bootstrap';

import  useAppConfig, useKeyCode, usePhoneNumber  from '../../ApplicationDispatchProvider';

import  ControlSet  from '../../components/control-set';
import  keypadClass  from '../../utils/style-utils';
import  MaskedEntry  from '../../components/masked-entry';
import  Messaging  from '../../components/messaging';
import  SimpleKeypad, HandleKeyPress, ALT_ID  from '../../components/simple-keypad';

export const AltIdPage = () => 
  const history = useHistory();
  const [keyCode, setKeyCode] = useKeyCode();
  const [phoneNumber, setPhoneNumber] = usePhoneNumber();
  const [appConfig, setAppConfig] = useAppConfig();

  const keyPressed = btn => 
    const maxLen = appConfig.phoneNumberEntry.entryLen;
    const newValue = HandleKeyPress(btn, phoneNumber).slice(0, maxLen);
    setPhoneNumber(newValue);
  

  const doSubmit = () => 
    history.push('s');
  

  const disableBtns = phoneNumber.length < appConfig.phoneNumberEntry.entryLen;

  return (
    <Container fluid className="text-center">
      <Row>
        <Messaging ... msgColors: appConfig.pageColors, msgLines: appConfig.entryMsgs.altIdMsgs  />
      </Row>
      <Row>
        <MaskedEntry ... ...appConfig.phoneNumberEntry, entryColors: appConfig.pageColors, entryLine: phoneNumber  />
      </Row>
      <Row>
        <SimpleKeypad ... keyboardName: ALT_ID, themeName: appConfig.keyTheme, keyPressed, styleClass: keypadClass  />
      </Row>
      <Row>
        <ControlSet ... btnColors: appConfig.buttonColors, disabled: disableBtns, btns: [ text: 'Submit', click: doSubmit ]  />
      </Row>
    </Container>
  );
;

AltIdPage.propTypes = ;

现在我的所有页面上的所有内容都可以顺利进行

【讨论】:

我不认为这个答案在 OP 的上下文中特别有用。这个答案甚至没有使用useState(),这是 OP 调查的核心。 顺利的解决方案,但不能回答正在发生的事情【参考方案5】:

React useEffect 有自己的状态/生命周期,它与状态的突变有关,在效果销毁之前不会更新状态。

只需在参数状态中传递单个参数或将其保留为黑色数组,它将完美地工作。

React.useEffect(() => 
    console.log("effect");
    (async () => 
        try 
            let result = await fetch("/query/countries");
            const res = await result.json();
            let result1 = await fetch("/query/projects");
            const res1 = await result1.json();
            let result11 = await fetch("/query/regions");
            const res11 = await result11.json();
            setData(
                countries: res,
                projects: res1,
                regions: res11
            );
         catch 
    )(data)
, [setData])
# or use this
useEffect(() => 
    (async () => 
        try 
            await Promise.all([
                fetch("/query/countries").then((response) => response.json()),
                fetch("/query/projects").then((response) => response.json()),
                fetch("/query/regions").then((response) => response.json())
            ]).then(([country, project, region]) => 
                // console.log(country, project, region);
                setData(
                    countries: country,
                    projects: project,
                    regions: region
                );
            )
         catch 
            console.log("data fetch error")
        
    )()
, [setData]);

或者,您可以尝试 React.useRef() 以立即更改反应钩子。

const movies = React.useRef(null);
useEffect(() => 
movies.current='values';
console.log(movies.current)
, [])

【讨论】:

最后一个代码示例既不需要 async 也不需要 await,因为您使用 Promise API。这只需要在第一个 伙计,你刚刚在午夜救了我!非常感谢@muhammad【参考方案6】:

我知道已经有很好的答案了。但我想给出另一个想法,如何解决同样的问题,并使用我的模块 react-useStateRef 访问最新的“电影”状态。

正如您所理解的,通过使用 React 状态,您可以在每次状态更改时呈现页面。但是通过使用 React ref,你总能得到最新的值。

所以react-useStateRef 模块让你可以一起使用 state 和 ref。它向后兼容React.useState,因此您只需替换import 语句

const  useEffect  = React
import  useState  from 'react-usestateref'

  const [movies, setMovies] = useState(initialValue);

  useEffect(() => 
    (async function() 
      try 

        const result = [
          
            id: "1546514491119",
          ,
        ];
        console.log("result =", result);
        setMovies(result);
        console.log("movies =", movies.current); // will give you the latest results
       catch (e) 
        console.error(e);
      
    )();
  , []);

更多信息:

react-usestsateref

【讨论】:

【参考方案7】:

有很多很好的答案可以说明如何修复您的代码,但是有一个 NPM 包可以让您通过更改 import 来修复它。它叫做react-useStateRef。

在你的情况下:

import useState from 'react-usestateref'
const [movies, setMovies,moviesRef] = useState(initialValue);
....
useEffect(() => 
   setMovies(...)
   console.log(moviesRef.current) // It will have the last value
)

如您所见,使用此库可以让您访问最新状态。

【讨论】:

它不起作用我安装并导入了 useStateRef。但这个问题没有改变。 const [pageCountOwnAlbum, setpageCountOwnAlbum, refOwnAlbum] = useStateRef(1) // 分页自己的相册数据获取 console.log("Value---", refOwnAlbum.current); 给予和以前一样 我有点困惑。你已经用相同的包推荐回答了这个问题already,而这次没有透露你的隶属关系!?【参考方案8】:

我觉得这很好。而不是将状态(方法 1)定义为示例,

const initialValue = 1;
const [state,setState] = useState(initialValue)

试试这个方法(方法二),

const [state = initialValue,setState] = useState()

这在不使用 useEffect 的情况下解决了重新渲染问题,因为我们不关心这种情况下的内部关闭方法。

P.S.:如果您担心在任何用例中使用旧状态,则需要使用带有 useEffect 的 useState,因为它需要具有该状态,因此在这种情况下应使用方法 1。

【讨论】:

【参考方案9】:

有一种特殊的语法可以以更舒适的方式处理 Promise,称为“async/await”。它非常容易理解和使用。

setState 的这种情况可以有多种解决方案。我发现的最简单舒适的解决方案之一如下:

在函数式组件中,将 useEffect() 设为 async/await。即,在上面的例子中,useEffect() 已经作为一个异步函数。现在做 setMovies(results) as await 像:

await setMovies(results);

这肯定会解决立即更改的问题。关键字 awaitjavascript 等待,直到该承诺完成并返回其结果。

您也不必像问题中所写的那样设置初始值。

你只能声明可变电影,如

const [movies, setMovies] = useState([]);

有关详细信息,请参阅 Async/await

【讨论】:

setMovies 不返回承诺,因此 async/await 不会真正解决任何问题。它可能看起来有效,但实际上是一个简单的竞争条件。【参考方案10】:

使用我的库中的自定义挂钩,您可以等待状态值更新:

    useAsyncWatcher(...values):watcherFn(peekPrevValue: boolean)=&gt;Promise - 是一个围绕 useEffect 的承诺包装器,它可以等待更新并返回一个新值,如果可选的 peekPrevValue 参数设置为 true,则可能返回前一个值。

(Live Demo)

    import React,  useState, useEffect, useCallback  from "react";
    import  useAsyncWatcher  from "use-async-effect2";
    
    function TestComponent(props) 
      const [counter, setCounter] = useState(0);
      const [text, setText] = useState("");
    
      const textWatcher = useAsyncWatcher(text);
    
      useEffect(() => 
        setText(`Counter: $counter`);
      , [counter]);
    
      const inc = useCallback(() => 
        (async () => 
          await new Promise((resolve) => setTimeout(resolve, 1000));
          setCounter((counter) => counter + 1);
          const updatedText = await textWatcher();
          console.log(updatedText);
        )();
      , []);
    
      return (
        <div className="component">
          <div className="caption">useAsyncEffect demo</div>
          <div>counter</div>
          <button onClick=inc>Inc counter</button>
        </div>
      );
    
    
    export default TestComponent;
    useAsyncDeepState 是一个深度状态实现(类似于 this.setState (patchObject)),它的 setter 可以返回一个与内部效果同步的 Promise。如果不带参数调用 setter,它不会更改状态值,而只是订阅状态更新。在这种情况下,您可以从组件内部的任何位置获取状态值,因为函数闭包不再是障碍。

(Live Demo)

import React,  useCallback, useEffect  from "react";
import  useAsyncDeepState  from "use-async-effect2";

function TestComponent(props) 
  const [state, setState] = useAsyncDeepState(
    counter: 0,
    computedCounter: 0
  );

  useEffect(() => 
    setState(( counter ) => (
      computedCounter: counter * 2
    ));
  , [state.counter]);

  const inc = useCallback(() => 
    (async () => 
      await new Promise((resolve) => setTimeout(resolve, 1000));
      await setState(( counter ) => ( counter: counter + 1 ));
      console.log("computedCounter=", state.computedCounter);
    )();
  );

  return (
    <div className="component">
      <div className="caption">useAsyncDeepState demo</div>
      <div>state.counter : state.counter</div>
      <div>state.computedCounter : state.computedCounter</div>
      <button onClick=() => inc()>Inc counter</button>
    </div>
  );

【讨论】:

【参考方案11】:

我在setState 钩子中发现了一个技巧。您不得使用旧变量。您必须创建新变量并将其传递给钩子。例如:

const [users, setUsers] = useState(['Ayşe', 'Fatma'])

useEffect(() => 
    setUsers((oldUsers) => 
        oldUsers.push(<div>Emir</div>)
        oldUsers.push(<div>Buğra</div>)
        oldUsers.push(<div>Emre</div>)
        return oldUsers
    )
, [])

return (
    <Fragment>
        users
    </Fragment>
)

您只会看到AyşeFatma 用户。因为您正在返回(或传递)oldUsers 变量。此变量与旧状态的引用具有相同的引用。您必须返回 NEWLY CREATED 变量。 如果你传递相同的引用变量,那么 Reactjs 不会更新状态。

const [users, setUsers] = useState(['Ayşe', 'Fatma'])

useEffect(() => 
    setUsers((oldUsers) => 
        const newUsers = [] // Create new array. This is so important.
        
        // you must push every old item to our newly created array
        oldUsers.map((user, index) => 
            newUsers.push(user)
        )
        // NOTE: map() function is synchronous
        
        newUsers.push(<div>Emir</div>)
        newUsers.push(<div>Buğra</div>)
        newUsers.push(<div>Emre</div>)
        return newUsers
    )
, [])

return (
    <Fragment>
        users
    </Fragment>
)

【讨论】:

这是一个不相关的问题,而在 OP 的问题中,它最终会更新状态,只是在以后的渲染周期中。 Updating a state array comes with its own issues, which are already addressed on Stack Overflow.【参考方案12】:
var [state,setState]=useState(defaultValue)

useEffect(()=>
   var updatedState
   setState(currentState=>    // Do not change the state by get the updated state
      updateState=currentState
      return currentState
   )
   alert(updateState) // the current state.
)

【讨论】:

【参考方案13】:

使用Background Timer 库。它解决了我的问题。

const timeoutId = BackgroundTimer.setTimeout(() => 
    // This will be executed once after 1 seconds
    // even when the application is the background
    console.log('tac');
, 1000);

【讨论】:

【参考方案14】:

关闭并不是唯一的原因。

基于useState的源代码(简化如下)。在我看来,这个值永远不会被立即分配。

当您调用 setValue 时,会发生更新操作排队。在计划启动后,只有当您进入下一个渲染时,这些更新操作才会应用于该状态。

这意味着即使我们没有关闭问题,useState 的反应版本也不会立即为您提供新值。在下一次渲染之前,新值甚至都不存在。

  function useState(initialState) 
    let hook;
    ...

    let baseState = hook.memoizedState;
    if (hook.queue.pending) 
      let firstUpdate = hook.queue.pending.next;

      do 
        const action = firstUpdate.action;
        baseState = action(baseState);            // setValue HERE
        firstUpdate = firstUpdate.next;
       while (firstUpdate !== hook.queue.pending);

      hook.queue.pending = null;
    
    hook.memoizedState = baseState;

    return [baseState, dispatchAction.bind(null, hook.queue)];
  

function dispatchAction(queue, action) 
  const update = 
    action,
    next: null
  ;
  if (queue.pending === null) 
    update.next = update;
   else 
    update.next = queue.pending.next;
    queue.pending.next = update;
  
  queue.pending = update;

  isMount = false;
  workInProgressHook = fiber.memoizedState;
  schedule();


还有一篇文章以类似的方式解释上述内容,https://dev.to/adamklein/we-don-t-know-how-react-state-hook-works-1lp8

【讨论】:

很好的分析。 Waaay 很少有人阅读源代码【参考方案15】:

如果我们只需要更新状态,那么更好的方法是使用 push 方法。

这是我的代码。我想在状态中存储来自Firebase 的 URL。

const [imageUrl, setImageUrl] = useState([]);
const [reload, setReload] = useState(0);

useEffect(() => 
    if (reload === 4) 
        downloadUrl1();
    
, [reload]);


const downloadUrl = async () => 
    setImages([]);
    try 
        for (let i = 0; i < images.length; i++) 
            let url = await storage().ref(urls[i].path).getDownloadURL();
            imageUrl.push(url);
            setImageUrl([...imageUrl]);

            console.log(url, 'check', urls.length, 'length', imageUrl.length);
        
    
    catch (e) 
        console.log(e);
    
;

const handleSubmit = async () => 
    setReload(4);
    await downloadUrl();
    console.log(imageUrl);
    console.log('post submitted');
;

此代码用于将 URL 置于数组状态。这也可能对您有用。

【讨论】:

【参考方案16】:

在setter函数范围内实现状态设置回调(不带useEffect)并不复杂。

使用 promise 解决 setter 函数体中的新状态:

const getState = <T>(
  setState: React.Dispatch<React.SetStateAction<T>>
): Promise<T> => 
  return new Promise((resolve) => 
    setState((currentState: T) => 
      resolve(currentState);
      return currentState;
    );
  );
;

这个例子展示了countoutOfSyncCount/syncCount在UI渲染中的对比:

const App: React.FC = () => 
  const [count, setCount] = useState(0);
  const [outOfSyncCount, setOutOfSyncCount] = useState(0);
  const [syncCount, setSyncCount] = useState(0);

  const handleOnClick = async () => 
    setCount(count + 1);
    setOutOfSyncCount(count);
    const newCount = await getState(setCount);
    setSyncCount(newCount);
    // Or then'able
    // getState(setCount).then(setSyncCount);
  ;

  return (
    <>
      <h2>Count = count</h2>
      <h2>Synced count = syncCount</h2>
      <h2>Out of sync count = outOfSyncCount</h2>
      <button onClick=handleOnClick>Increment</button>
    </>
  );
;

我认为这种方法的一个吸引人的用例是使用与 Redux thunk 非常相似的上下文 api 和异步和/或副作用操作。这也将比Kent's method 涉及更少的复杂性。 (我没有在大型示例或生产中使用过这个,所以我不知道,也许你会进入一些奇怪的状态)。

例子:

// Sample "thunk" with async and side effects
const handleSubmit = async (
  setState: React.Dispatch<SetStateAction<FormState>>
): Promise<void> => 
  const state = await getState(setState);
​
  if (!state.validated) 
    return;
  
​
  // Submit endpoint
  await fetch('submitFormUrl',  method: 'post', body: state );
  tackFormSuccess(state);
  setState( ...state, submitted: true );
;
​
// Component consuming the thunk
const SubmitComponent: React.FC = () => 
  const dispatch = useFormDispatch();
  return <button onclick=() => handleSubmit(dispatch)>Submit</button>

【讨论】:

以上是关于useState 设置方法不会立即反映更改的主要内容,如果未能解决你的问题,请参考以下文章

React useState 和 useEffect 混淆

useState 函数没有立即更新[重复]

为啥我的代码更改没有立即反映?

状态更新没有立即反映。如何使用 useEffect 的依赖数组来显示更改?

无法在异步方法中设置 useState 变量并控制台记录它

如何在可观察集合的 UI 中立即反映任何添加、删除、字段更改