useState设置方法不能立即反映更改

Pra*_*jal 46 javascript reactjs react-hooks

我正在尝试学习钩子,而useState方法使我感到困惑。我正在将初始值分配给数组形式的状态。即使使用spread(...)或,useState中的set方法对我也不起作用without spread operator。我在另一台PC上制作了一个API,我正在调用它并提取要设置为状态的数据。

这是我的代码:

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

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);
        setMovies(result);
        console.log(movies);
      } catch (e) {
        console.error(e);
      }
    })();
  }, []);

  return <p>hello</p>;
};

const rootElement = document.getElementById("root");
ReactDOM.render(<StateSelector />, rootElement);
Run Code Online (Sandbox Code Playgroud)

setMovies(result)以及setMovies(...result)不工作。可以在这里使用一些帮助。提前致谢。

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

Apr*_*ion 332

上一个答案的其他详细信息:

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

TLDR:原因是围绕不可变值的闭包范围const


解决方案:


解释为什么会发生:

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

但是,propsstate假定在 1 render 期间保持不变

将其this.state视为不可变的。

使用钩子,通过使用带有关键字的常量值可以增强这种假设const

const [state, setState] = useState('initial')
Run Code Online (Sandbox Code Playgroud)

两次渲染之间的值可能不同,但在渲染本身和任何闭包(即使在渲染完成后存活时间更长的函数,例如useEffect,任何 Promise 或 setTimeout 内的事件处理程序)内仍保持不变。

考虑以下虚假但同步的类似 React 的实现:

  useEffect(() => { setMovies(result) }, [])
  console.log(movies)
Run Code Online (Sandbox Code Playgroud)
  useEffect(() => { setMovies(result) }, [])
  useEffect(() => { console.log(movies) }, [movies])
Run Code Online (Sandbox Code Playgroud)

  • 很好的答案。在我看来,如果你投入足够的时间来阅读和吸收它,这个答案将让你在未来避免最心碎的事情。 (33认同)
  • 实际上,我刚刚完成了 useReducer 的重写,遵循 @kentcdobs 文章(参考下面),这确实给了我一个可靠的结果,没有受到这些闭包问题的影响。(参考:https://kentcdodds.com/blog/how-to-use-react-context-efficiently) (2认同)

Shu*_*tri 78

类似于通过扩展React.Component或创建的Class组件中的setState,React.PureComponent使用useState钩子提供的更新程序的状态更新也是异步的,不会立即反映和更新,但会触发重新渲染

setMovies(result);
console.log(movies) // movies here will not be updated
Run Code Online (Sandbox Code Playgroud)

如果要对状态更新执行操作,则需要使用useEffect钩子,就像componentDidUpdate在类组件中使用一样,因为useState返回的setter没有回调模式

useEffect(() => {
    // action on update of movies
}, [movies]);
Run Code Online (Sandbox Code Playgroud)

就更新状态的语法而言,setMovies(result)将状态中的先前movies值替换为异步请求中可用的值

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

setMovies(prevMovies => ([...prevMovies, ...result]));
Run Code Online (Sandbox Code Playgroud)

  • 它记录了错误的结果,因为您记录的是[过时的闭包](https://dmitripavlutin.com/react-hooks-stale-closures/),而不是因为设置器是异步的。如果异步是问题所在,那么您可以在超时后进行日志记录,但您可以设置一个小时的超时,但仍然会记录错误的结果,因为异步并不是导致问题的原因。 (8认同)
  • 请注意,虽然建议非常好,但对原因的解释还可以改进 - 与“useState hook 提供的更新程序”是否异步无关,这与可能已变异的“this.state”不同如果 `this.setState` 是同步的,即使 `useState` 提供了同步函数,围绕 `const movie` 的闭包也将保持不变 - 请参阅我的答案中的示例 (6认同)
  • 嗨,如何在表单提交处理程序中调用useState?我正在验证一个复杂的表单,并且在内部的SubmitHandler useState挂钩中调用,不幸的是更改不是立即进行的! (4认同)
  • 不过,“useEffect”可能不是最好的解决方案,因为它不支持异步调用。因此,如果我们想对“电影”状态更改进行一些异步验证,我们无法控制它。 (3认同)
  • `setMovies(prevMovies =&gt; ([...prevMovies, ...result]));` 对我有用 (3认同)
  • 我是唯一一个对这些试图通过添加如此多复杂、非直观的解决方法来“简化”生活的框架感到沮丧的人吗?我学了很多 JavaScript,现在如果我使用 useState 创建一个 Set,我需要以某种方式解构该 Set,找到要删除的项目,并使用该数组返回一个新 Set。那么 Set 的意义何在呢?现在使用 useState 更新值不是同步的。我大部分时间都花在为这些“简单”框架编写解决方法上。 (2认同)

Muh*_*que 13

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]);
Run Code Online (Sandbox Code Playgroud)

或者,您可以尝试 React.useRef() 在 React hook 中进行即时更改。

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


小智 12

我也遇到了同样的问题。正如上面的其他答案已经澄清了这里的错误,这是useState异步的,您正在尝试使用setState. console.log()由于 的异步性质,它不会在部件上更新setState,它允许您执行进一步的代码,而值更新发生在后台。这样你就得到了之前的值。当setState在后台完成时,它将更新该值,您将可以在下一次渲染时访问该值。

如果有人有兴趣详细了解这一点。这是关于该主题的非常好的会议演讲。

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


Ahm*_*bai 12

这里的大多数答案都是关于如何根据其先前的值更新状态,但我不明白这与问题有何关系

useState set 方法没有立即反映更改


反应18

useState 是异步的:

当触发某个代码的事件发生时,代码开始运行,当它完成时,react将检查是否有状态更新,如果是,则只有useState更新钩子的值,这会导致一个新的渲染,其中新值可用。

const [example,setExemple] = useState("")
//...
<button
  onClick={() => {
    const newValue = "new";
    setExample(newValue);
    console.log(example); // output "" and this is normal, because the component didn't rerenderd yet so the new value is not availabe yet
  }}
>
  Update state
</button>
Run Code Online (Sandbox Code Playgroud)

假设我们有一个场景,一个状态依赖于另一个状态,例如我们希望根据example每次更新时的新值进行 API 调用,然后将响应中的数据存储在另一个状态中anotherExample
为了实现这样我们有两种方法:

1. 使用 的值newValue

<button
  onClick={async () => {
    const newValue = "new";
    const response = await axios.get(`http://127.0.0.1:5000/${newValue}`);
    setExample(newValue);
    setAnotherExample(response.data);
  }}
>
  test
</button>
Run Code Online (Sandbox Code Playgroud)

因为您知道example将收到该值,所以您可以直接基于它创建逻辑。

2.每次更新时触发useEffect运行,方法是将其包含在其依赖项数组中:exampleexample

<button
  onClick={() => {
    const newValue = "new";
    setExample(newValue);
  }}
>
  test
</button>
Run Code Online (Sandbox Code Playgroud)
useEffect(() => {
 async function test(){
  const response = await axios.get(`http://127.0.0.1:5000/${example}`);
  setAnotherExample(response.data);
 } 
 test();
}, [example])
Run Code Online (Sandbox Code Playgroud)

因此,当example使用事件函数更新组件重新渲染时,我们现在处于一个新的不同渲染中,一旦完成,useEffect就会运行,因为 的值example与上次渲染期间的值不同,并且因为它是一个新的不同渲染, useState 挂钩的新值example可在此处获得。

注意:useEffect在第一次安装期间,钩子无论如何都会运行。

哪种方法更好?

  • 第一种方法将在一次渲染中完成所有工作 (更好的方法) “React 将多个状态更新分组到单个重新渲染中以获得更好的性能”,第二种方法将在两次渲染中完成,第一次example更新时和第二次anotherExample从内部更新useEffect

  • 由于组件仅在useState钩子的新值与旧值不同时才重新渲染,因此当newValue等于时example组件不会重新渲染,因此useEffect不会运行且anotherExample不会更新 (更好的方法),但是在第一种方法中无论如何,API 都会被调用,如果不需要,我们不想这样做,如果发生这种情况,anotherExample也会更新(anotherExample将收到它已经包含的相同数据,因为它是相同的请求,因为newValue等于example),但如果响应在对象或数组中,Object.is方法(useState钩子使用的)无法检测新值是否等于前一个值,因此,组件将重新渲染

结论:

正如上面提到的,每种都有其优点,所以这取决于用例。

更推荐使用第二种方法,但是在某些情况下,第一种方法的性能更高,例如,当您确定代码仅在newValue使用 获取新值时才运行onChange,或者当您想使用您将要使用的其他一些局部变量时不再能够从 useEffect 内部访问

  • 因为这个答案是在React 18是最后一个发布版本时发布的,也许以后它就不再有效了,谁知道呢 (3认同)
  • `useState` 从第一天起就一直是异步的,它与 ReactJs 18 版本无关。 (2认同)

win*_*mao 11

关闭并不是唯一的原因。

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

发生的情况是,当您调用 时,更新操作会排队setValue。在计划开始后,只有当您到达下一个渲染时,这些更新操作才会应用于该状态。

这意味着即使我们没有关闭问题,react 版本也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();
}

Run Code Online (Sandbox Code Playgroud)

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


Ami*_*ein 10

我知道已经有很好的答案了。但我想给出另一个想法如何解决相同的问题,并使用我的模块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);
      }
    })();
  }, []);
Run Code Online (Sandbox Code Playgroud)

更多信息:


Al *_*lin 8

我刚刚完成了使用 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,
}
Run Code Online (Sandbox Code Playgroud)

用法与此类似:

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 = {};
Run Code Online (Sandbox Code Playgroud)

现在一切都在我所有的页面上顺利地持续存在

好的!

谢谢肯特!

  • 我不认为这个答案在OP的背景下特别有帮助。这个答案甚至没有使用“useState()”,这是OP查询的核心。 (5认同)
  • 顺利的解决方案,但不是对正在发生的事情的答案 (2认同)

Sur*_*h B 8

React 中useState hook返回的setState函数不会立即更新状态。相反,它会安排在下一个渲染周期中处理状态更新。这是因为 React 出于性能原因批量状态更新。

如果您在调用setState后尝试立即访问更新后的状态,您可能不会立即看到更新后的值。相反,您可以使用useEffect挂钩在状态更新后执行操作。

这是一个示例,演示如何使用 useEffect 在状态更新后执行操作

 import React, { useState, useEffect } from 'react';

const MyComponent = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    // This effect will run after each state update
    console.log('Count has been updated:', count);
  }, [count]);

  const incrementCount = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={incrementCount}>Increment</button>
    </div>
  );
};
Run Code Online (Sandbox Code Playgroud)

在上面的示例中,useEffect挂钩用于记录每次状态更新后更新的计数值。通过将[count]作为依赖数组传递给useEffect,效果将仅在count状态更改时运行。