【问题标题】:useState set method not reflecting change immediatelyuseState 设置方法不会立即反映更改
【发布时间】:2021-12-12 20:46:48
【问题描述】:

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

我希望将结果变量推入电影数组。

【问题讨论】:

  • 您能否看到将console.log("movies =", movies); 移动到useEffect 挂钩之外的更改?
  • 这在a meta question中用作示例

标签: javascript reactjs react-hooks


【解决方案1】:

在setter函数范围内实现状态设置回调(不带useEffect)并不复杂。

使用 promise 解决 setter 函数体中的新状态:

const getState = <T>(
  setState: React.Dispatch<React.SetStateAction<T>>
): Promise<T> => {
  return new Promise((resolve) => {
    setState((currentState: T) => {
      resolve(currentState);
      return currentState;
    });
  });
};

这个例子展示了countoutOfSyncCount/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);
    setOutOfSyncCount(count);
    const newCount = await getState(setCount);
    setSyncCount(newCount);
    // Or then'able
    // getState(setCount).then(setSyncCount);
  };

  return (
    <>
      <h2>Count = {count}</h2>
      <h2>Synced count = {syncCount}</h2>
      <h2>Out of sync count = {outOfSyncCount}</h2>
      <button onClick={handleOnClick}>Increment</button>
    </>
  );
};

我认为这种方法的一个吸引人的用例是使用与 Redux thunk 非常相似的上下文 api 和异步和/或副作用操作。这也将比Kent's method 涉及更少的复杂性。 (我没有在大型示例或生产中使用过这个,所以我不知道,也许你会进入一些奇怪的状态)。

例子:

// Sample "thunk" with async and side effects
const handleSubmit = async (
  setState: React.Dispatch<SetStateAction<FormState>>
): Promise<void> => {
  const state = await getState(setState);
​
  if (!state.validated) {
    return;
  }
​
  // Submit endpoint
  await fetch('submitFormUrl', { method: 'post', body: state });
  tackFormSuccess(state);
  setState({ ...state, submitted: true });
};
​
// Component consuming the thunk
const SubmitComponent: React.FC = () => {
  const dispatch = useFormDispatch();
  return <button onclick={() => handleSubmit(dispatch)}>Submit</button>
}

【讨论】:

    【解决方案2】:

    previous answer 的其他详细信息:

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

    TLDR:原因是 closure 范围围绕不可变的 const 值。


    解决方案:

    • 读取渲染函数中的值(不在嵌套函数中):

        useEffect(() => { setMovies(result) }, [])
        console.log(movies)
      
    • 将变量添加到依赖项中(并使用react-hooks/exhaustive-deps eslint 规则):

        useEffect(() => { setMovies(result) }, [])
        useEffect(() => { console.log(movies) }, [movies])
      
    • 使用临时变量:

        useEffect(() => {
          const newMovies = result
          console.log(newMovies)
          setMovies(newMovies)
        }, [])
      
    • 使用可变引用(如果我们不需要状态并且只想记住值 - 更新引用不会触发重新渲染):

        const moviesRef = useRef(initialValue)
        useEffect(() => {
          moviesRef.current = result
          console.log(moviesRef.current)
        }, [])
      

    解释为什么会发生:

    如果异步是唯一的原因,那么await setState() 是可能的。

    但是,propsstate 都是 assumed to be unchanging during 1 render

    this.state 视为不可变的。

    有了钩子,这个假设通过使用带有const关键字的常量值得到增强:

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

    2 次渲染之间的值可能不同,但在渲染本身和任何 closures 内保持不变(即使在渲染完成后寿命更长的函数,例如 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()
    &lt;div id="root"&gt;&lt;/div&gt;

    【讨论】:

    • 优秀的答案。国际海事组织,如果您花足够的时间阅读和吸收它,这个答案将为您在未来节省最多的心碎。
    • @AlJoslin 乍一看,这似乎是一个单独的问题,即使它可能是由闭包范围引起的。如果您有具体问题,请使用代码示例和所有内容创建一个新的 stackoverflow 问题...
    • 实际上,我刚刚完成了使用 useReducer 的重写,遵循@kentcdobs 文章(参考下文),这确实给了我一个可靠的结果,没有受到这些关闭问题的影响。 (参考:kentcdodds.com/blog/how-to-use-react-context-effectively
    • @ACV 解决方案 2 适用于原始问题。如果您需要解决不同的问题,YMMW,但我仍然 100% 确定引用的代码按文档说明工作,并且问题出在其他地方。
    • 这应该是“接受”答案@Pranjal
    【解决方案3】:

    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() 以立即更改反应钩子。

    const movies = React.useRef(null);
    useEffect(() => {
    movies.current='values';
    console.log(movies.current)
    }, [])
    
    

    【讨论】:

    • 最后一个代码示例既不需要 async 也不需要 await,因为您使用 Promise API。这只需要在第一个
    • 伙计,你刚刚在午夜救了我!非常感谢@muhammad
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      使用Background Timer 库。它解决了我的问题。

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

      【讨论】:

        【解决方案6】:

        有一种特殊的语法可以以更舒适的方式处理 Promise,称为“async/await”。它非常容易理解和使用。

        setState 的这种情况可以有多种解决方案。我发现的最简单舒适的解决方案之一如下:

        在函数式组件中,将 useEffect() 设为 async/await。即,在上面的例子中,useEffect() 已经作为一个异步函数。现在做 setMovies(results) as await 像:

        await setMovies(results);
        

        这肯定会解决立即更改的问题。关键字 await 让 JavaScript 等待,直到该承诺完成并返回其结果。

        您也不必像问题中所写的那样设置初始值。

        你只能声明可变电影,如

        const [movies, setMovies] = useState([]);
        

        有关详细信息,请参阅 Async/await

        【讨论】:

        • setMovies 不返回承诺,因此 async/await 不会真正解决任何问题。它可能看起来有效,但实际上是一个简单的竞争条件。
        【解决方案7】:

        我觉得这很好。而不是将状态(方法 1)定义为示例,

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

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

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

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

        P.S.:如果您担心在任何用例中使用旧状态,则需要使用带有 useEffect 的 useState,因为它需要具有该状态,因此在这种情况下应使用方法 1。

        【讨论】:

          【解决方案8】:

          有很多很好的答案可以说明如何修复您的代码,但是有一个 NPM 包可以让您通过更改 import 来修复它。它叫做react-useStateRef

          在你的情况下:

          import useState from 'react-usestateref'
          const [movies, setMovies,moviesRef] = useState(initialValue);
          ....
          useEffect(() => {
             setMovies(...)
             console.log(moviesRef.current) // It will have the last value
          })
          

          如您所见,使用此库可以让您访问最新状态。

          【讨论】:

          • 它不起作用我安装并导入了 useStateRef。但这个问题没有改变。 const [pageCountOwnAlbum, setpageCountOwnAlbum, refOwnAlbum] = useStateRef(1) // 分页自己的相册数据获取 console.log("Value---", refOwnAlbum.current);
          • 给予和以前一样
          • 我有点困惑。你已经用相同的包推荐回答了这个问题already,而这次没有透露你的隶属关系!?
          【解决方案9】:

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

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

          所以react-useStateRef 模块让你可以一起使用 state 和 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);
                }
              })();
            }, []);
          

          更多信息:

          【讨论】:

            【解决方案10】:

            我刚刚完成了使用 useReducer 的重写,遵循 @kentcdobs 文章(参考下文),它确实给了我一个可靠的结果,没有受到这些关闭问题的影响。

            见:https://kentcdodds.com/blog/how-to-use-react-context-effectively

            我将他的可读样板压缩到我喜欢的 DRYness 级别——阅读他的沙盒实现将向您展示它是如何工作的。

            import React from 'react'
            
            // ref: https://kentcdodds.com/blog/how-to-use-react-context-effectively
            
            const ApplicationDispatch = React.createContext()
            const ApplicationContext = React.createContext()
            
            function stateReducer(state, action) {
              if (state.hasOwnProperty(action.type)) {
                return { ...state, [action.type]: state[action.type] = action.newValue };
              }
              throw new Error(`Unhandled action type: ${action.type}`);
            }
            
            const initialState = {
              keyCode: '',
              testCode: '',
              testMode: false,
              phoneNumber: '',
              resultCode: null,
              mobileInfo: '',
              configName: '',
              appConfig: {},
            };
            
            function DispatchProvider({ children }) {
              const [state, dispatch] = React.useReducer(stateReducer, initialState);
              return (
                <ApplicationDispatch.Provider value={dispatch}>
                  <ApplicationContext.Provider value={state}>
                    {children}
                  </ApplicationContext.Provider>
                </ApplicationDispatch.Provider>
              )
            }
            
            function useDispatchable(stateName) {
              const context = React.useContext(ApplicationContext);
              const dispatch = React.useContext(ApplicationDispatch);
              return [context[stateName], newValue => dispatch({ type: stateName, newValue })];
            }
            
            function useKeyCode() { return useDispatchable('keyCode'); }
            function useTestCode() { return useDispatchable('testCode'); }
            function useTestMode() { return useDispatchable('testMode'); }
            function usePhoneNumber() { return useDispatchable('phoneNumber'); }
            function useResultCode() { return useDispatchable('resultCode'); }
            function useMobileInfo() { return useDispatchable('mobileInfo'); }
            function useConfigName() { return useDispatchable('configName'); }
            function useAppConfig() { return useDispatchable('appConfig'); }
            
            export {
              DispatchProvider,
              useKeyCode,
              useTestCode,
              useTestMode,
              usePhoneNumber,
              useResultCode,
              useMobileInfo,
              useConfigName,
              useAppConfig,
            }
            

            类似这样的用法:

            import { useHistory } from "react-router-dom";
            
            // https://react-bootstrap.github.io/components/alerts
            import { Container, Row } from 'react-bootstrap';
            
            import { useAppConfig, useKeyCode, usePhoneNumber } from '../../ApplicationDispatchProvider';
            
            import { ControlSet } from '../../components/control-set';
            import { keypadClass } from '../../utils/style-utils';
            import { MaskedEntry } from '../../components/masked-entry';
            import { Messaging } from '../../components/messaging';
            import { SimpleKeypad, HandleKeyPress, ALT_ID } from '../../components/simple-keypad';
            
            export const AltIdPage = () => {
              const history = useHistory();
              const [keyCode, setKeyCode] = useKeyCode();
              const [phoneNumber, setPhoneNumber] = usePhoneNumber();
              const [appConfig, setAppConfig] = useAppConfig();
            
              const keyPressed = btn => {
                const maxLen = appConfig.phoneNumberEntry.entryLen;
                const newValue = HandleKeyPress(btn, phoneNumber).slice(0, maxLen);
                setPhoneNumber(newValue);
              }
            
              const doSubmit = () => {
                history.push('s');
              }
            
              const disableBtns = phoneNumber.length < appConfig.phoneNumberEntry.entryLen;
            
              return (
                <Container fluid className="text-center">
                  <Row>
                    <Messaging {...{ msgColors: appConfig.pageColors, msgLines: appConfig.entryMsgs.altIdMsgs }} />
                  </Row>
                  <Row>
                    <MaskedEntry {...{ ...appConfig.phoneNumberEntry, entryColors: appConfig.pageColors, entryLine: phoneNumber }} />
                  </Row>
                  <Row>
                    <SimpleKeypad {...{ keyboardName: ALT_ID, themeName: appConfig.keyTheme, keyPressed, styleClass: keypadClass }} />
                  </Row>
                  <Row>
                    <ControlSet {...{ btnColors: appConfig.buttonColors, disabled: disableBtns, btns: [{ text: 'Submit', click: doSubmit }] }} />
                  </Row>
                </Container>
              );
            };
            
            AltIdPage.propTypes = {};
            

            现在我的所有页面上的所有内容都可以顺利进行

            【讨论】:

            • 我不认为这个答案在 OP 的上下文中特别有用。这个答案甚至没有使用useState(),这是 OP 调查的核心。
            • 顺利的解决方案,但不能回答正在发生的事情
            【解决方案11】:
            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.
            })
            

            【讨论】:

              【解决方案12】:

              关闭并不是唯一的原因。

              基于useState的源代码(简化如下)。在我看来,这个值永远不会被立即分配。

              当您调用 setValue 时,会发生更新操作排队。在计划启动后,只有当您进入下一个渲染时,这些更新操作才会应用于该状态。

              这意味着即使我们没有关闭问题,useState 的反应版本也不会立即为您提供新值。在下一次渲染之前,新值甚至都不存在。

                function useState(initialState) {
                  let hook;
                  ...
              
                  let baseState = hook.memoizedState;
                  if (hook.queue.pending) {
                    let firstUpdate = hook.queue.pending.next;
              
                    do {
                      const action = firstUpdate.action;
                      baseState = action(baseState);            // setValue HERE
                      firstUpdate = firstUpdate.next;
                    } while (firstUpdate !== hook.queue.pending);
              
                    hook.queue.pending = null;
                  }
                  hook.memoizedState = baseState;
              
                  return [baseState, dispatchAction.bind(null, hook.queue)];
                }
              
              function dispatchAction(queue, action) {
                const update = {
                  action,
                  next: null
                };
                if (queue.pending === null) {
                  update.next = update;
                } else {
                  update.next = queue.pending.next;
                  queue.pending.next = update;
                }
                queue.pending = update;
              
                isMount = false;
                workInProgressHook = fiber.memoizedState;
                schedule();
              }
              
              

              还有一篇文章以类似的方式解释上述内容,https://dev.to/adamklein/we-don-t-know-how-react-state-hook-works-1lp8

              【讨论】:

              • 很好的分析。 Waaay 很少有人阅读源代码
              【解决方案13】:

              我在setState 钩子中发现了一个技巧。您不得使用旧变量。您必须创建新变量并将其传递给钩子。例如:

              const [users, setUsers] = useState(['Ayşe', 'Fatma'])
              
              useEffect(() => {
                  setUsers((oldUsers) => {
                      oldUsers.push(<div>Emir</div>)
                      oldUsers.push(<div>Buğra</div>)
                      oldUsers.push(<div>Emre</div>)
                      return oldUsers
                  })
              }, [])
              
              return (
                  <Fragment>
                      {users}
                  </Fragment>
              )
              

              您只会看到AyşeFatma 用户。因为您正在返回(或传递)oldUsers 变量。此变量与旧状态的引用具有相同的引用。您必须返回 NEWLY CREATED 变量。 如果你传递相同的引用变量,那么 Reactjs 不会更新状态。

              const [users, setUsers] = useState(['Ayşe', 'Fatma'])
              
              useEffect(() => {
                  setUsers((oldUsers) => {
                      const newUsers = [] // Create new array. This is so important.
                      
                      // you must push every old item to our newly created array
                      oldUsers.map((user, index) => {
                          newUsers.push(user)
                      })
                      // NOTE: map() function is synchronous
                      
                      newUsers.push(<div>Emir</div>)
                      newUsers.push(<div>Buğra</div>)
                      newUsers.push(<div>Emre</div>)
                      return newUsers
                  })
              }, [])
              
              return (
                  <Fragment>
                      {users}
                  </Fragment>
              )
              

              【讨论】:

              【解决方案14】:

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

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

              (Live Demo)

                  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;
              
              1. useAsyncDeepState 是一个深度状态实现(类似于 this.setState (patchObject)),它的 setter 可以返回一个与内部效果同步的 Promise。如果不带参数调用 setter,它不会更改状态值,而只是订阅状态更新。在这种情况下,您可以从组件内部的任何位置获取状态值,因为函数闭包不再是障碍。

              (Live Demo)

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

              【讨论】:

                【解决方案15】:

                类似于扩展React.ComponentReact.PureComponent创建的Class组件中的setState,使用useState钩子提供的updater更新状态也是异步的,不会立即反映。

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

                即使你添加了 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 值。

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

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

                【讨论】:

                • 嗨,在表单提交处理程序中调用 useState 怎么样?我正在验证一个复杂的表单,我在 submitHandler useState 钩子中调用,不幸的是,更改不是立即的!
                • useEffect 可能不是最好的解决方案,因为它不支持异步调用。因此,如果我们想对 movies 状态更改进行一些异步验证,我们无法控制它。
                • 请注意,虽然建议非常好,但对原因的解释可以改进 - 与 the updater provided by useState hook 是否异步这一事实无关,不像 this.state如果this.setState 是同步的,则发生突变,即使useState 提供了同步函数,const movies 周围的闭包也将保持不变-请参阅我的答案中的示例
                • setMovies(prevMovies =&gt; ([...prevMovies, ...result])); 为我工作
                • 它记录了错误的结果,因为您正在记录 stale closure 而不是因为 setter 是异步的。如果异步是问题所在,那么您可以在超时后记录,但您可以设置一个小时的超时并仍然记录错误的结果,因为异步不是导致问题的原因。
                【解决方案16】:
                // replace
                return <p>hello</p>;
                // with
                return <p>{JSON.stringify(movies)}</p>;
                

                现在您应该看到,您的代码确实确实 有效。不起作用的是console.log(movies)。这是因为movies 指向旧状态。如果您将console.log(movies) 移到useEffect 之外,就在返回的正上方,您将看到更新后的电影对象。

                【讨论】:

                • 不确定为什么这个答案被大量否决,它告诉如何通过将它放在 useState 函数之外来获得“预期的”console.log 值。简单而甜蜜,如果有人想知道为什么会这样,可以参考上面的详细说明
                猜你喜欢
                相关资源
                最近更新 更多