[javascript] useState 설정 메소드가 즉시 변경 사항을 반영하지 않음

나는 후크를 배우려고 노력하고 있으며 그 useState방법으로 인해 혼란스러워했다. 배열의 형태로 초기 값을 상태에 할당하고 있습니다. 세트 방법에는 useState심지어 나를 위해 작동하지 않습니다 spread(...)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);

setMovies(result)뿐만 아니라 setMovies(...result)작동하지 않습니다. 여기에 도움이 필요합니다.

결과 변수가 영화 배열로 푸시 될 것으로 기대합니다.



답변

많은 확장함으로써 생성 클래스 구성 요소들에 setState를 추천 React.Component또는 React.PureComponent의해 제공 업데이터를 이용한 상태 업데이트 useState후크 비동기이며, 즉시 반영되지

또한 여기서 주요 이슈는 비동기 특성뿐만 아니라 현재 클로저를 기반으로 함수가 상태 값을 사용한다는 사실과 상태 업데이트는 기존 클로저가 영향을받지 않고 새로운 클로저가 생성되는 다음 다시 렌더링에 반영된다는 것입니다 . 현재 상태에서 후크 내 값은 기존 클로저에 의해 얻어지며 다시 렌더링이 발생하면 클로저는 함수가 다시 생성되는지 여부에 따라 업데이트됩니다.

setTimeout함수 를 추가하더라도 다시 렌더링이 발생한 시간이 지나면 시간 초과가 실행되지만 setTimout은 업데이트 된 값이 아니라 이전 클로저의 값을 계속 사용합니다.

setMovies(result);
console.log(movies) // movies here will not be updated

상태 업데이트에 대한 조치를 수행하려면 useState에 componentDidUpdate의해 리턴 된 setter에 콜백 패턴이 없으므로 클래스 컴포넌트에서 사용 하는 것과 유사하게 useEffect 후크를 사용해야합니다.

useEffect(() => {
    // action on update of movies
}, [movies]);

상태를 업데이트하는 구문과 관련 하여 상태 setMovies(result)의 이전 movies값을 비동기 요청에서 사용 가능한 값으로 대체합니다.

그러나 응답을 이전의 기존 값과 병합하려면 상태 업데이트의 콜백 구문과 스프레드 구문의 올바른 사용을 사용해야합니다.

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


답변

받는 사람 Addional 자세한 내용은 이전의 대답 :

React setState 비동기식 (클래스와 후크 모두)이며 관찰 된 동작을 설명하기 위해 그 사실을 사용하고 싶은 유혹이 있지만 그것이 일어나는 이유 는 아닙니다 .

TLDR : 이유는 불변 값 주위의 클로저 범위 const입니다.


솔루션 :

  • 렌더링 함수 (내포 된 함수가 아닌)의 값을 읽습니다.

    useEffect(() => { setMovies(result) }, [])
    console.log(movies)
  • 변수를 종속 항목에 추가하고 react-hooks / exhaustive-deps eslint 규칙을 사용하십시오.

    useEffect(() => { setMovies(result) }, [])
    useEffect(() => { console.log(movies) }, [movies])
  • 변경 가능한 참조를 사용하십시오 (위에서 불가능한 경우).

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

왜 발생하는지 설명하십시오.

비동기가 유일한 이유 였다면 가능할 것 await setState()입니다.

Howerver, 모두 props하고 state있습니다 1 렌더링 동안 변하지 않는 것으로 가정 .

this.state불변 인 것처럼 취급하십시오 .

후크를 사용 하면 키워드 와 함께 상수 값 을 사용하여이 가정이 향상됩니다 const.

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

값은 두 렌더간에 다를 수 있지만 렌더 자체와 클로저 내에서 일정하게 유지됩니다 (렌더가 완료된 후에도 더 오래 지속되는 함수 (예 : 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>


답변

@kentcdobs 기사 (아래 참조)에 따라 useReducer로 다시 작성을 마쳤습니다.이 클로저 문제로 인해 1 비트가 아닌 확실한 결과를 얻었습니다.

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

이제 모든 페이지의 모든 곳에서 모든 것이 매끄럽게 유지됩니다.

좋은!

고마워 켄트!


답변

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

지금 당신은 당신의 코드가 실제로 것을 볼 수 않는 일을. 작동하지 않는 것은 console.log(movies)입니다. 이전 상태를movies 가리 키기 때문 입니다. 의 바로 바깥쪽으로 이동 하면 업데이트 된 영화 개체가 표시됩니다.console.log(movies)useEffect


답변