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


当前回答

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.
})

其他回答

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

之前答案的附加细节:

虽然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>

不需要任何额外的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>
        </>
    )

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

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

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