我正在尝试学习钩子和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数组。


当前回答

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

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

其他回答

// replace
return <p>hello</p>;
// with
return <p>{JSON.stringify(movies)}</p>;

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

很像扩展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]));

如果我们必须只更新状态,那么更好的方法是使用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置于数组状态。这可能对你也有用。

并不是说要这样做,但是没有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应使用。