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


当前回答

之前答案的附加细节:

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

TLDR:原因是围绕不可变const值的闭包作用域。


解决方案:

read the value in render function (not inside nested functions): useEffect(() => { setMovies(result) }, []) console.log(movies) add the variable into dependencies (and use the react-hooks/exhaustive-deps eslint rule): useEffect(() => { setMovies(result) }, []) useEffect(() => { console.log(movies) }, [movies]) use a temporary variable: useEffect(() => { const newMovies = result console.log(newMovies) setMovies(newMovies) }, []) use a mutable reference (if we don't need a state and only want to remember the value - updating a ref doesn't trigger re-render): const moviesRef = useRef(initialValue) useEffect(() => { moviesRef.current = result console.log(moviesRef.current) }, [])


解释为什么会这样:

如果async是唯一的原因,那么可以等待setState()。

然而,道具和状态都假定在1渲染期间不变。

对待这个问题。状态,仿佛它是不可变的。

对于钩子,通过使用常量值和const关键字来加强这个假设:

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

该值在2个渲染之间可能不同,但在渲染本身和任何闭包(即使在渲染完成后仍然存在更长的函数,例如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() <div id="root"></div>

其他回答

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

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

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

之前答案的附加细节:

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

TLDR:原因是围绕不可变const值的闭包作用域。


解决方案:

read the value in render function (not inside nested functions): useEffect(() => { setMovies(result) }, []) console.log(movies) add the variable into dependencies (and use the react-hooks/exhaustive-deps eslint rule): useEffect(() => { setMovies(result) }, []) useEffect(() => { console.log(movies) }, [movies]) use a temporary variable: useEffect(() => { const newMovies = result console.log(newMovies) setMovies(newMovies) }, []) use a mutable reference (if we don't need a state and only want to remember the value - updating a ref doesn't trigger re-render): const moviesRef = useRef(initialValue) useEffect(() => { moviesRef.current = result console.log(moviesRef.current) }, [])


解释为什么会这样:

如果async是唯一的原因,那么可以等待setState()。

然而,道具和状态都假定在1渲染期间不变。

对待这个问题。状态,仿佛它是不可变的。

对于钩子,通过使用常量值和const关键字来加强这个假设:

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

该值在2个渲染之间可能不同,但在渲染本身和任何闭包(即使在渲染完成后仍然存在更长的函数,例如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() <div id="root"></div>

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

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