React 中的 useState() 在 set 后变为 undefined

9

我遇到了一个问题,不明白为什么钩子返回undefined:

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


function App(){

    const [globalVariable, setGlobalVariable] = useState();

  useEffect(()=>{

    const test = 5
    console.log(test) // return 5
    setGlobalVariable(test)
    console.log(globalVariable) // return undefined
    
    
    },[]);
  
  

  return (
    <div>
    </div>
  );
}

export default App;

我应该如何设置全局变量(globalVariable)的新值?


4
这个回答是否解决了你的问题?useState的set方法不立即反映更改 - Yoshi
setState 是一个异步方法。 - Kakiz
8个回答

10

正如其他成员所提到的,setState运行在基于队列的异步系统中。这意味着如果你想进行状态更新,该更新将被堆积在队列中。然而,它不会返回Promise,因此你不能使用.thenwait。由于这个原因,我们需要注意在React中操作状态可能出现的问题。

比如说,我们有:

// PROBLEM # 1
export function Button() {
  const [count, setCount] = useState(0);

  function increment() {
    setCount(count + 1);
    console.log(count); // returns 0
  }

  return (
    <button onClick={increment}>Increment<\button>
  )
}

这基本上是你所遇到的相同问题。由于setCount是异步的,console.log会在更新之前返回值(这里为0)。在你的情况下,它将返回undefined,因为你没有在useState钩子中传递任何初始状态。你所做的另一件不同的事情是从useEffect钩子内部触发状态变化,但这并不影响手头的问题。
解决方法是,一个好的实践是将新状态存储在另一个变量中,如下所示:
// SOLUTION TO PROBLEM # 1
export function Button() {
  const [count, setCount] = useState(0);

  function increment() {
    const newCountValue = count + 1;

    setCount(newCountValue);
    console.log(newCountValue); // returns 1
  }

  return (
    <button onClick={increment}>Increment<\button>
  )
}

这基本上回答了你的问题。

但是,在其他情况下,状态更新可能不像人们期望的那样工作。了解这些场景以避免可能出现的错误非常重要。例如,假设我们连续多次更新给定状态,如下所示:

// PROBLEM # 2
export function Button() {
  const [count, setCount] = useState(0);

  function increment() {
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
    console.log(count); // returns 0, but the final state will be 1 (not 3)
  }

  return (
    <button onClick={increment}>Increment<\button>
  )
}

在这里,console.log仍然会因为问题#1描述的原因返回0。但是,如果您在应用程序中某个地方使用了count状态,您将看到在onClick事件之后的最终值为1而不是3。

以下是您可以修复此问题的方法:

// SOLUTION TO PROBLEM # 2
export function Button() {
  const [count, setCount] = useState(0);

  function increment() {
    setCount((oldState) => oldState + 1);
    setCount((oldState) => oldState + 1);
    setCount((oldState) => oldState + 1);
    console.log(count); // returns 0, but the final state will be 3
  }

  return (
    <button onClick={increment}>Increment<\button>
  )
}

通过向setState传递一个函数,新状态是使用队列中的先前值而不是初始值计算出来的。

最后举个例子,假设我们在尝试改变状态之后立即调用一个函数,但这个函数依赖于状态值。由于操作是异步的,该函数将使用“旧”的状态值,就像问题#1中一样:

// PROBLEM # 3
export function Button() {
  const [count, setCount] = useState(0);

  onCountChange() {
    console.log(count); // returns 0;
  }

  function increment() {
    setCount(count + 1);
    onCountChange();
  }

  return (
    <button onClick={increment}>Increment<\button>
  )
}

注意在这个问题中,我们有一个依赖于外部变量(count)的函数onCountChange。所谓外部变量是指该变量没有在onCountChange作用域内声明。它们实际上是兄弟节点(它们都在Button作用域内)。现在,如果您熟悉“纯函数”、“幂等”和“副作用”概念,您会意识到我们面临的是一个副作用问题——我们希望在状态改变后发生某些事情。
粗略地说,每当您需要处理React中的一些副作用时,可以通过useEffect钩子来完成。因此,要解决我们的第三个问题,我们可以简单地执行如下操作:
// SOLUTION TO PROBLEM # 3
export function Button() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log(count); // returns 0;
  }, [count]);

  function increment() {
    setCount(count + 1);
  }

  return (
    <button onClick={increment}>Increment<\button>
  )
}

在上述解决方案中,我们将 onCountChange 的内容移至 useEffect 中,并将 count 添加为依赖项,因此每当它发生更改时,都会触发该 effect。

3

两件事情:

  • useState有一个初始值,默认为undefined。
const [globalVariable, setGlobalVariable] = useState(123); // initial value set to 123
  • setGlobalVariable不是同步操作。更改它不会立即改变globalVariable的值。作用域中的值将保持不变,直到下一个渲染阶段。

2

因为状态只有在组件重新渲染时才会有新值。所以只需将 console.log(globalVariable) 放在 useEffect 外面,像这样:

useEffect(() => {
  const test = 5;
  console.log(test); // return 5
  setGlobalVariable(test);
}, []);
console.log(globalVariable);

1

这是因为组件没有重新渲染。您需要在渲染之间保留该值,为此需要使用useState将变量更改为状态。请查看以下代码以获得更好的理解:

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

function Test() {
  const [globalVariable, setGlobalVariable] = useState();
  const [test, setTest] = useState(5);

  useEffect(() => {
    // const test = 5;
    // console.log(test); // return 5
    setGlobalVariable(test);
    console.log(globalVariable); // return undefined
    console.log(test); // return 5
  }, []);

  return <div></div>;
}

export default Test;

1

0
这是因为React状态更新是基于队列的系统。它不会立即更新。但是如果您渲染全局变量值,则会得到所需的变量值。
如果在基于类的组件中有相同的要求,则需要将回调函数传递给setState。它将在状态更新后立即执行。 this.setState(stateupdate,callback)
一旦状态更新,回调函数将使用最新状态执行。
const StackOverflow = () => {
const [globalVariable, setGlobalVariable] = useState();

useEffect(()=>{
    const test = 5
    console.log(test) // return 5
    setGlobalVariable(test)
    console.log(globalVariable)
},[]);

return (
    <Container maxWidth="lg" style={{paddingTop:"5%"}}>
        <div>
           <h1>StackOverflow - {globalVariable}</h1>
        </div>
    </Container>
)

}


0
错误的原因是 userData 状态变量最初是未定义的。您正在尝试在 JSX 代码中访问 userData 的 name 属性,但它尚未定义。因此,在呈现 JSX 代码之前,您需要添加一个条件来检查 userData 是否存在。您可以使用 && 运算符来实现这一目的。
您可以在 React 中的 JSX 中使用 {userData && <h3>{userData.name}</h3>} 而不是直接使用 <h3>{userData.name}</h3> 或者,如果数据类型不是对象,则可以将 useState 变量简单地定义为空对象,如 useState({}) 或其他空值。

{userData &&<h3>{userData.name}</h3> } - sammingasainathrao
目前你所写的答案不是很清晰。请编辑并添加更多细节,以便其他人能够理解如何回答该问题。您可以在帮助中心找到有关编写好答案的更多信息。 - Community

-2
那是因为 setGlobalVariable(test) 需要一些时间,而你正试图在 useEffect 中执行它,useEffect 只会运行一次,你应该使用 setTimeOut 函数来延迟一段时间以显示状态。尝试这样做,然后告诉我是否有效。
  const [globalVariable, setGlobalVariable] = useState();

  useEffect(() => {
    const test = 5;
    // console.log(test); // return 5
    setGlobalVariable(test);
    setTimeout(() => {
      console.log(globalVariable);
    }, 2000);
  }, []);

使用setTimeout在这些情况下是个坏主意,因为不同的浏览器/设备处理的时间不同。最好学会理解为什么它还没有更新。 - joshuadelange

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接