我正在尝试学习钩子和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)都不起作用。

我希望结果变量被推入movies数组。


当前回答

I too was stuck with the same problem. As other answers above have clarified the error here, which is that useState is asynchronous and you are trying to use the value just after setState. It is not updating on the console.log() part because of the asynchronous nature of setState, it lets your further code to execute, while the value updating happens on the background. Thus you are getting the previous value. When the setState is completed on the background it will update the value and you will have access to that value on the next render.

如果有人有兴趣了解这个细节。这是一个关于这个话题的非常好的会议发言。

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

其他回答

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

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

(现场演示)

    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是一个深度状态实现(类似于此。setState (patchObject)),它的setter可以返回一个与内部效果同步的承诺。如果调用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>
  );
}

并不是说要这样做,但是没有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;
    });
  });
};

这就是你如何使用它(示例显示了count和outOfSyncCount/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);

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

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

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

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

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

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

附注:如果你关心在任何用例中使用旧状态,那么useState和useEffect需要使用,因为它需要那种状态,所以在这种情况下接近1应使用。

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

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

模块react-useStateRef让你同时使用状态和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

很像扩展React创建的类组件中的. setstate()。组件或React。使用useState钩子提供的更新器的PureComponent状态更新也是异步的,并且不会立即反映出来。

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

即使您添加了一个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值替换状态中先前的movies值。

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

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