useState set 方法不会立即反映更改

The useState set method is not reflecting a change immediately

提问人:Pranjal 提问时间:1/7/2019 最后编辑:Peter MortensenPranjal 更新时间:10/25/2023 访问量:774661

问:

我正在尝试学习钩子,但这种方法让我感到困惑。我正在以数组的形式为状态分配初始值。中的 set 方法对我不起作用,无论有没有传播语法。useStateuseState

我在另一台 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)

我希望变量被推入数组中。resultmovies

javascript reactjs 反应钩子

评论

0赞 Domenico Ruggiano 4/3/2021
你是否能够看到将变化移出钩子?console.log("movies =", movies);useEffect

答:

908赞 Shubham Khatri 1/7/2019 #1

就像 extension or 创建的类组件中的 .setState() 一样,使用 hook 提供的更新程序的状态更新也是异步的,不会立即反映出来。React.ComponentReact.PureComponentuseState

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

即使您添加了 the 函数,尽管超时将在重新渲染发生一段时间后运行,但仍将使用其先前闭包中的值,而不是更新后的闭包。setTimeoutsetTimeout

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

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

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

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

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

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

评论

88赞 da45 4/20/2019
嗨,在表单提交处理程序中调用useState怎么样?我正在验证一个复杂的表单,我在submitHandler useState钩子中调用,不幸的是,更改不是立即的!
5赞 RA. 8/20/2019
useEffect不过,这可能不是最好的解决方案,因为它不支持异步调用。因此,如果我们想对状态更改进行一些异步验证,我们无法控制它。movies
7赞 Aprillion 11/21/2019
请注意,虽然建议非常好,但对原因的解释可以改进 - 与是否异步的事实无关,不像是同步的,即使提供了同步功能,闭包也会保持不变 - 请参阅我答案中的示例the updater provided by useState hookthis.statethis.setStateconst moviesuseState
5赞 Mihir 4/1/2020
setMovies(prevMovies => ([...prevMovies, ...result]));为我工作
21赞 HMR 5/12/2020
它记录错误的结果是因为您记录的是一个过时的闭包,而不是因为 setter 是异步的。如果异步是问题所在,那么您可以在超时后进行记录,但您可以设置一个小时的超时时间,但仍然记录错误的结果,因为异步不是导致问题的原因。
528赞 Aprillion 11/15/2019 #2

上一个答案的其他详细信息:

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

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


解决 方案:

  • 读取 render 函数中的值(不在嵌套函数中):

      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)
      }, [])
    
  • 使用可变引用(如果我们不需要状态并且只想记住值 - 更新 ref 不会触发重新渲染):

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

解释为什么会这样:

如果异步是唯一的原因,则可以 .await setState()

但是,假定两者在 1 次渲染期间保持不变propsstate

将其视为不可变的。this.state

使用钩子时,通过将常量值与关键字一起使用来增强此假设:const

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

该值在 2 个渲染之间可能不同,但在渲染本身和任何闭包中保持常量(即使在渲染完成后仍存在更长时间的函数,例如,事件处理程序,在任何 Promise 或 setTimeout 中)。useEffect

考虑以下虚假但同步的类似 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()
<div id="root"></div>

评论

0赞 Aprillion 6/16/2020
@AlJoslin乍一看,这似乎是一个单独的问题,即使它可能是由关闭范围引起的。如果您有具体问题,请创建一个带有代码示例和所有...
2赞 Al Joslin 6/17/2020
实际上,我刚刚完成了使用 useReducer 的重写,遵循@kentcdobs文章(下面的参考),这确实给了我一个坚实的结果,没有受到这些闭合问题的影响。(参考:kentcdodds.com/blog/how-to-use-react-context-effectively)
0赞 ACV 10/22/2020
由于某种原因,解决方案 2 不起作用。.我收到回调,但值仍然为空。 这什么也没打印.useEffect(() => { console.log(movies) }, [movies])
1赞 Aprillion 10/23/2020
@ACV解决方案 2 适用于原始问题。如果您需要解决不同的问题,YMMW,但我仍然 100% 确定引用的代码是否按文档记录工作,并且问题出在其他地方。
0赞 Matt Frei 3/15/2022
所有这些解决方案都需要使用 useEffect。我的问题是我的“电影”等价物是我从上下文提供程序获得的对象,可以由许多其他组件更改。我不想在每次更改效果时都运行效果,因为我的效果不是 setMovies - 这是一个不同的函数,我只需要在对电影进行特定更改时调用 - 由于过时的上下文,我在需要时看不到更改。
-3赞 Nikita Malyschkin 5/18/2020 #3
// replace
return <p>hello</p>;
// with
return <p>{JSON.stringify(movies)}</p>;

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

评论

8赞 vikramvi 3/25/2021
不知道为什么这个答案被大量否决,它告诉如何通过将“预期”控制台 .log 值放在 useState 函数之外来获取它。简单而甜蜜,如果有人想知道为什么会这样发生,可以参考上面的详细说明
0赞 ADITYA AHLAWAT 9/19/2022
不过很好尝试
13赞 Al Joslin 6/17/2020 #4

我刚刚用 useReducer 完成了重写,遵循 Kent C. Dodds 的文章(参考下面的文章),这确实给了我一个坚实的结果,没有受到这些闭合问题的影响。

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

我把他的可读样板压缩到我喜欢的枯燥程度——阅读他的沙盒实现将向你展示它实际上是如何工作的。

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 = {};

现在,在我所有页面上,所有内容都顺利地保留了下来

评论

13赞 Martin Carel 11/15/2020
我不认为这个答案在OP的背景下特别有用。这个答案甚至没有使用OP调查的核心。useState()
2赞 oligofren 9/27/2021
顺利的解决方案,但不是对正在发生的事情的答案
13赞 Muhammad Imran Siddique 8/20/2020 #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() 在 React 钩子中即时更改。

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

评论

0赞 oligofren 9/27/2021
最后一个代码示例在使用 Promise API 时不需要 async 和 await。这仅在第一个中需要
29赞 Aminadav Glickshtein 12/24/2020 #6

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

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

因此,该模块允许您同时使用 state's 和 ref's。它向后兼容 ,因此您只需替换语句即可react-useStateRefReact.useStateimport

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);
      }
    })();
  }, []);

更多信息:

4赞 Abhishek Sharma 3/25/2021 #7

我发现这很好。例如,不要将状态(方法 1)定义为,

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

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

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

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

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

评论

2赞 Martin 5/20/2022
这个答案是没有用的。对于重新渲染和捕获的闭包值,这种方法没有丝毫区别。当状态值被故意设置为 时,它唯一的区别是,在这种情况下,您将再次获得。这只是一种令人困惑的方法,因为您可以将其设置为初始值,而无需额外的步骤。undefinedinitialValue
0赞 ggorlen 6/1/2022
方法 2 污染了全球空间。如前所述,方法 1 充其量是一种反模式。
0赞 Dmitriy Mozgovoy 5/23/2021 #8

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

  1. useAsyncWatcher(...values):watcherFn(peekPrevValue: boolean)=>Promise- 是 useEffect 的 promise 包装器,可以等待更新并返回一个新值,如果可选参数设置为 true,则可能返回上一个值。peekPrevValue

(现场演示)

    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;
  1. useAsyncDeepState是一个深层状态实现(类似于 this.setState (patchObject)),其 setter 可以返回与内部效果同步的 promise。如果调用 setter 时不带参数,则它不会更改状态值,而只是订阅状态更新。在这种情况下,您可以从组件内部的任何位置获取状态值,因为函数闭包不再是障碍。

(现场演示)

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>
  );
}
0赞 rjhcnf 7/2/2021 #9
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.
})

评论

1赞 Emile Bergeron 3/15/2022
别这样。的 setter 回调应该是纯的。此外,在这里,将永远是.setStateupdatedStateundefined
0赞 Ricola 6/8/2022
@EmileBergeron您是否有指向文档的链接,说明回调应该没有副作用?
0赞 Emile Bergeron 6/9/2022
我手头没有链接,但它与严格模式一起记录在案,这有助于识别不需要的副作用。
-4赞 user16396840 7/7/2021 #10

使用后台计时器库。它解决了我的问题。

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

评论

2赞 Prabhu Thomas 2/23/2022
添加延迟不是实际的解决方案。这只是一种解决方法。即便如此,当你可以使用一个简单的 setTimeout 时,你也不需要一个库
11赞 windmaomao 7/9/2021 #11

关闭并不是唯一的原因。

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

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

这意味着即使我们没有闭包问题,react 版本也不会立即为您提供新值。新值甚至在下一次渲染之前都不存在。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

2赞 amritanshu singh 8/28/2021 #12

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

这是我的代码。我想将 Firebase 中的网址存储在状态下。

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 作为数组置于状态。这可能也适合您。

评论

2赞 Emile Bergeron 3/15/2022
.push改变当前状态,这在 React 中是一种不好的做法。这是更新状态数组的正确方法。
0赞 Emile Bergeron 3/15/2022
循环调用是另一种不好的做法,每次调用它时都会触发一个新的渲染,因为它在异步调用时(在 React 的生命周期之外)不是批处理的。正确的方法是构建新数组,然后只调用一次。setImageUrlsetImageUrl
0赞 Emile Bergeron 3/15/2022
此外,在这样的循环中使用 await 效率低下。像 Promise.all 这样的东西会改善这一点。
-2赞 Tunn 12/19/2021 #13

不是说要这样做,但是在没有useEffect的情况下做OP要求的事情并不难。

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

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

这就是你使用它的方式(示例显示了UI渲染中和/之间的比较):countoutOfSyncCountsyncCount

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

    // Doesn't work
    setOutOfSyncCount(count);

    // Works
    const newCount = await getState(setCount);
    setSyncCount(newCount);
  };

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

评论

3赞 Emile Bergeron 3/15/2022
setStatesetter 回调应该是纯的
2赞 Tunn 3/15/2022
我赞成强调纯函数,但该链接并不特定于回调(有些用例不使用纯函数作为回调)。此外,虽然这不是“纯粹的”,但它实际上也不会改变任何状态。我并不是说这是最好的方法(我不使用它),只是它为 OP 问题提供了另一种解决方案useState
2赞 Jamie Nicholl-Shelley 3/22/2022
使用计数和/或回调恐怕会阻碍 usestate 本身背后的设计方法。
1赞 Tunn 3/25/2022
同样,不是说这是最好的方法或使用它,只是说它有效。用一个可重复的例子来说明使用它的问题会更有用,而不是从设计理论层面忽略它
1赞 ggorlen 6/1/2022
“设计理论水平”似乎是批评某事的完全正当的理由。仅仅因为某些东西有效或可能,并不意味着它值得放在那里。尽管有警告,人们实际上可能会使用这种模式。有更好的方法来处理“问题”(这并不完全是一个问题,只是对于不习惯异步代码的人来说)。
0赞 GMKHussain 1/25/2022 #14

无需任何额外的 NPM 包

//...
const BackendPageListing = () => {
    
    const [ myData, setMyData] = useState( {
        id: 1,
        content: "abc"
    })

    const myFunction = ( x ) => {
        
        setPagenateInfo({
        ...myData,
        content: x
        })

        console.log(myData) // not reflecting change immediately

        let myDataNew = {...myData, content: x };
        
        console.log(myDataNew) // Reflecting change immediately

    }

    return (
        <>
            <button onClick={()=>{ myFunction("New Content")} }>Update MyData</button>
        </>
    )
11赞 Nirjal Mahat 6/21/2022 #15

我也遇到了同样的问题。正如上面的其他答案已经澄清了这里的错误,即异步,您正在尝试使用紧跟在 之后的值。由于 的异步性质,它不会在部分更新,它允许您执行进一步的代码,而值更新发生在后台。因此,您正在获得以前的值。在后台完成 后,它将更新该值,您将可以在下一次渲染时访问该值。useStatesetStateconsole.log()setStatesetState

如果有人有兴趣详细了解这一点。这是一个关于这个主题的非常好的会议演讲。

https://www.youtube.com/watch?v=8aGhZQkoFbQ

9赞 Ahmed Sbai 3/26/2023 #16

这里的大多数答案都是关于如何根据以前的值更新状态,但我不明白这与问题有什么关系

useState set 方法不会立即反映更改


反应 18

useState 是异步的:

当触发某个代码的事件发生时,代码开始运行,当它结束时,react 将检查是否有状态更新,如果是这种情况,则只有这样才会更新钩子的值,这会导致新的渲染,其中新值可用。useState

const [example,setExemple] = useState("")
//...
<button
  onClick={() => {
    const newValue = "new";
    setExample(newValue);
    console.log(example); // output "" and this is normal, because the component didn't rerenderd yet so the new value is not availabe yet
  }}
>
  Update state
</button>

假设我们有一个场景,我们有一个依赖于另一个状态的状态,例如,我们想根据每次更新时的新值进行 API 调用,然后将来自响应的数据存储在另一个状态中。
为了实现这一点,我们有两种方法:
exampleanotherExample

1. 使用 newValue 的值:

<button
  onClick={async () => {
    const newValue = "new";
    const response = await axios.get(`http://127.0.0.1:5000/${newValue}`);
    setExample(newValue);
    setAnotherExample(response.data);
  }}
>
  test
</button>

由于您知道它将接收此值,因此您可以直接基于它创建逻辑。example

2. 通过在其依赖项数组中包含 example,触发 useEffect 在每次更新示例时运行:

<button
  onClick={() => {
    const newValue = "new";
    setExample(newValue);
  }}
>
  test
</button>
useEffect(() => {
 async function test(){
  const response = await axios.get(`http://127.0.0.1:5000/${example}`);
  setAnotherExample(response.data);
 } 
 test();
}, [example])

因此,当使用组件重新渲染的事件函数更新时,我们现在处于一个新的不同渲染中,一旦完成,就会运行,因为 example 的值与上次渲染期间的值不同,并且由于它是一个新的不同渲染,useState 钩子的新值在这里可用。exampleuseEffectexample

注意:在第一次挂载期间,钩子无论如何都会运行。useEffect

哪种方法更好?

  • 虽然第一种方法将在一次渲染🙂中完成所有工作(一种更好的方法)“React 将多个状态更新分组到单个重新渲染中以获得更好的性能”,但第二种方法将在两次渲染中完成,第一个在更新时完成,第二个在内部😕更新时完成exampleanotherExampleuseEffect

  • 由于组件仅在钩子的新值与旧值不同时重新渲染,因此 when 等于组件不会重新渲染,因此不会运行并且不会更新(更好的方法),但是在第一种方法中,无论如何都会调用 API,如果没有必要,我们不想这样做,如果发生这种情况也会更新🙂(将收到相同的它已经包含的数据,因为它是相同的 REQUEST,因为它等于 ),但是如果对象或数组中的响应,则方法(钩子利用的)无法检测新值是否等于前一个值,因此,组件将重新呈现 😕useStatenewValueexampleuseEffectanotherExampleanotherExampleanotherExamplenewValueexampleObject.isuseState

结论:

如上所述,每个都有其优点,因此取决于用例。

第二种方法更推荐,但在某些情况下,第一种方法的性能可能更高,例如,当您确定代码仅在使用 时运行,或者当您想要使用其他一些局部变量时,您将无法再从内部访问useEffectnewValueonChange

评论

1赞 AmerllicA 8/30/2023
从第一天开始就一直是异步的,它与 ReactJs 18 版本无关。useState
0赞 Ahmed Sbai 8/30/2023
我没有说这是 React 18 中的新功能。
0赞 AmerllicA 8/30/2023
那么你为什么提到Reactjs版本呢?这让我感到困惑
2赞 Ahmed Sbai 8/31/2023
因为这个答案是在 React 18 是最后一个发布版本时发布的,也许以后就无效了,谁知道呢
8赞 Suresh B 5/23/2023 #17

React 中 useState 钩子返回的 setState 函数不会立即更新状态。相反,它会计划在下一个渲染周期中处理状态更新。这是因为 React 出于性能原因对状态进行批处理更新。

如果尝试在调用 setState 后立即访问更新的状态,则可能不会立即看到更新的值。相反,您可以使用 useEffect 挂钩在状态更新后执行操作。

以下示例演示了如何使用 useEffect 在状态更新后执行操作

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

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

  useEffect(() => {
    // This effect will run after each state update
    console.log('Count has been updated:', count);
  }, [count]);

  const incrementCount = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={incrementCount}>Increment</button>
    </div>
  );
};

在上面的示例中,useEffect 钩子用于在每次状态更新后记录更新的计数值。通过将 [count] 作为依赖项数组传递给 useEffect,效果将仅在计数状态更改时运行。

0赞 AmerllicA 8/30/2023 #18

⚠️ 功能组件

我相信一个超级干净的方法是创建一个自定义钩子,它提供了将回调传递给 setter 函数的能力,那么在状态更新后准确地执行一些操作将是 100% 的保证。

通过查看这篇文章,您可以了解如何制作钩子。使用 定义状态如下所示:useStateCallbackuseStateCallback

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

const handleFooBar = () => {
  setCount(c => c + 1, () => { // The callback function
    // All actions here will be run exactly AFTER the update of the count state
  })
};
0赞 Akaisteph7 10/25/2023 #19

问题

我的问题并不是在调用set方法后立即尝试访问状态。在重新渲染发生后,我试图以完全不同的功能执行此操作,但更新仍然没有反映出来。目标函数是在功能组件中定义的,但它是从类组件调用的。

就我而言,我最终意识到这是由陈旧的关闭引起的问题。这可能是因为类组件不使用功能,因此我的代码中的类组件采用了传递给它的函数并创建了它的副本或其他东西,并且该副本没有使用对我的变量的最新引用。不过,直接传递给类组件的实际变量仍然得到了正确的反映。useState

溶液

用功能组件替换类组件为我解决了这个问题。