usestate集方法不立即反映更改

bqf10yzr  于 2021-09-23  发布在  Java
关注(0)|答案(13)|浏览(269)

我正在努力学习钩子和钩子 useState 方法让我困惑。我以数组的形式为状态赋值。中的set方法 useState 即使和你在一起也不为我工作 spread(...)without spread operator . 我在另一台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数组中。

js5cn81o

js5cn81o1#

使用后台定时器库解决我的问题https://github.com/ocetnik/react-native-background-timer const timeoutId = BackgroundTimer.setTimeout(() => { // this will be executed once after 1 seconds // even when app is the the background console.log('tac'); }, 1000);

vkc1a9a2

vkc1a9a22#

关闭并不是唯一的原因。
基于源代码的 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

holgip5t

holgip5t3#

我发现了一个骗局 setState 钩子。不能使用旧变量。您必须创建新变量并将其传递给hook。例如:

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 变量此变量的引用与旧状态的引用相同。必须返回新创建的变量。若您传递相同的引用变量,则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>
)
y4ekin9u

y4ekin9u4#

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

现在您应该看到,您的代码确实可以工作。不起作用的是 console.log(movies) . 这是因为 movies 指向旧的状态。如果你移动你的 console.log(movies)useEffect ,右上方的返回,您将看到更新的电影对象。

vpfxa7rd

vpfxa7rd5#

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

# 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")
        }
    })()
}, []);
holgip5t

holgip5t6#

有很多很好的答案可以说明如何修复代码,但是有一个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
})

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

7z5jn7bk

7z5jn7bk7#

使用my library中的自定义挂钩,您可以等待状态值更新: useAsyncWatcher(...values):watcherFn(peekPrevValue: boolean)=>Promise -是一个围绕useeffect的承诺 Package 器,它可以等待更新并返回一个新值,如果可选,还可能返回上一个值 peekPrevValue 参数设置为true。
(现场演示)

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

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 (

useAsyncDeepState demo
state.counter : {state.counter}
state.computedCounter : {state.computedCounter}
<button onClick={() => inc()}>Inc counter

);
}

mcvgt66p

mcvgt66p8#

var[state,setstate]=usestate(默认值)
useffect(()=>{var updatedstate setstate(currentstate=>{//不要通过获取更新状态updatestate=currentstate return currentstate}来更改状态)警报(updatestate)//当前状态。}

kq0g1dla

kq0g1dla9#

类似于通过扩展 React.ComponentReact.PureComponent ,使用提供的更新程序进行状态更新 useState 钩子也是异步的,不会立即反映出来。
此外,这里的主要问题不仅仅是异步性质,而是函数根据其当前闭包使用状态值,并且状态更新将反映在下一次重新呈现中,其中现有闭包不受影响,而是创建新闭包。现在,在当前状态下,钩子中的值由现有闭包获得,当重新呈现时,闭包将根据是否重新创建函数进行更新。
即使你添加了一个 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]));
sbtkgmzw

sbtkgmzw10#

对上一答案的补充细节:
当React setState 是异步的(类和钩子都是异步的),很容易用这个事实来解释观察到的行为,但这并不是它发生的原因。
tldr:原因是围绕一个不可变的 const 价值

解决方案:

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

useEffect(() => { setMovies(result) }, [])
  console.log(movies)

将变量添加到依赖项中(并使用react hooks/deps eslint规则):

useEffect(() => { setMovies(result) }, [])
  useEffect(() => { console.log(movies) }, [movies])

使用可变引用(当无法执行上述操作时):

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

解释发生的原因:

如果async是唯一的原因,那么 await setState() .
然而,两者 propsstate 假定在1次渲染期间保持不变。
对待 this.state 好像它是不变的。
对于钩子,通过将常量值与 const 关键词:

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

该值在两个渲染之间可能不同,但在渲染本身和任何闭包内保持不变(即使在渲染完成后仍然有效的函数,例如。 useEffect ,事件处理程序,在任何承诺或设置超时内)。
考虑仿冒,但同步,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>
rqqzpn5f

rqqzpn5f11#

您可以使用 useRef 钩子,但当它更新时,它不会重新渲染。我已经创建了一个名为usestateref的钩子,它可以从两个世界中为您带来好处。这就像更新组件时重新渲染的状态,就像“ref”始终具有最新值。
请参见此示例:

var [state,setState,ref]=useStateRef(0)

它的工作原理一模一样 useState 但除此之外,它还提供了当前状态 ref.current 了解更多信息:
https://www.npmjs.com/package/react-usestateref

xdnvmnnf

xdnvmnnf12#

我刚刚用usereducer完成了一个重写,在@kentcdobs文章(参考下面)之后,这篇文章给了我一个可靠的结果,它不会因为这些闭包问题而遭受任何损失。
见:https://kentcdodds.com/blog/how-to-use-react-context-effectively
我将他的可读性样板压缩到我喜欢的干燥程度——阅读他的沙盒实现将向您展示它实际上是如何工作的。
享受吧,我知道我是!!

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 = {};

现在,在我的所有页面上,一切都很顺利
美好的
谢谢你,肯特!

zour9fqk

zour9fqk13#

我发现这很好,而不是将状态(方法1)定义为,
const initialValue = 1; const [state,setState] = useState(initialValue) 尝试这种方法(方法2), const [state = initialValue,setState] = useState() 这在不使用useeffect的情况下解决了重新渲染问题,因为我们不关心此案例的内部关闭方法。
p、 如果您关心在任何用例中使用旧状态,那么需要使用usestate with useeffect,因为它需要具有该状态,所以在这种情况下应使用方法1。

相关问题