useState 후크 세터가 상태를 잘못 겹쳐 씁니다.


31

문제가 있습니다 : 버튼 클릭으로 2 개의 함수를 호출하려고합니다. 두 함수 모두 상태를 업데이트합니다 (useState 후크를 사용하고 있습니다). 첫 번째 함수는 value1을 'new 1'로 올바르게 업데이트하지만 1s (setTimeout) 후에 두 번째 함수가 실행되고 값 2가 'new 2'로 변경되지만 BUT! value1을 다시 '1'로 설정했습니다. 왜 이런 일이 발생합니까? 미리 감사드립니다!

import React, { useState } from "react";

const Test = () => {
  const [state, setState] = useState({
    value1: "1",
    value2: "2"
  });

  const changeValue1 = () => {
    setState({ ...state, value1: "new 1" });
  };
  const changeValue2 = () => {
    setState({ ...state, value2: "new 2" });
  };

  return (
    <>
      <button
        onClick={() => {
          changeValue1();
          setTimeout(changeValue2, 1000);
        }}
      >
        CHANGE BOTH
      </button>
      <h1>{state.value1}</h1>
      <h1>{state.value2}</h1>
    </>
  );
};

export default Test;

시작할 때 상태를 기록 할 수 changeValue2있습니까?
DanStarns 2009 년

1
객체를 두 개의 개별 호출로 분할 useState하거나 대신 사용하는 것이 좋습니다 useReducer.
Jared Smith

네, 두 번째입니다. 그냥 useState () 두 호출을 사용
Esben SKOV 페데르센

const [state, ...]그런 다음 setter에서이를 참조합니다. 항상 동일한 상태를 사용합니다.
Johannes Kuhn

최선의 조치 : useState"가변"마다 하나씩 2 개의 개별 호출을 사용하십시오 .
Dima Tisnek

답변:


30

폐쇄 지옥에 오신 것을 환영합니다 . 때마다 때문에이 문제가 발생 setState라고하며, state새로운 메모리 참조를 가져옵니다 만, 기능 changeValue1changeValue2때문에 폐쇄는 이전 초기 유지 state참조.

용액 보장 setState발을 changeValue1하고 changeValue2최신의 상태가 사용하는 것이다 얻는다 콜백 (파라미터로서 이전 상태를 갖는)

import React, { useState } from "react";

const Test = () => {
  const [state, setState] = useState({
    value1: "1",
    value2: "2"
  });

  const changeValue1 = () => {
    setState((prevState) => ({ ...prevState, value1: "new 1" }));
  };
  const changeValue2 = () => {
    setState((prevState) => ({ ...prevState, value2: "new 2" }));
  };

  // ...
};

이 폐쇄 문제에 대한 자세한 내용은 여기여기를 참조하십시오 .


useState 후크가있는 콜백은 문서화되지 않은 기능인 것 같습니다. 계속 작동합니까?
HMR

@HMR 예, 작동하며 다른 페이지에 문서화되어 있습니다. "기능 업데이트"섹션을보십시오. reactjs.org/docs/hooks-reference.html ( "이전 상태를 사용하여 새 상태를 계산하면 setState에 함수를 전달할 수 있습니다")
Alberto Trindade Tavares

1
@ AlbertoTrindadeTavares 네, 문서를보고 있었지만 아무것도 찾을 수 없었습니다. 답변 주셔서 감사합니다!
Bartek

1
첫 번째 솔루션은 "쉬운 솔루션"이 아니라 올바른 방법입니다. 두 번째는 구성 요소가 싱글 톤으로 디자인 된 경우에만 작동하며 상태가 매번 새로운 객체가되기 때문에 확실하지 않습니다.
Scimonster

1
@AlbertoTrindadeTavares 감사합니다! 좋은 것
José Salgado

19

당신의 기능은 다음과 같아야합니다 :

const changeValue1 = () => {
    setState((prevState) => ({ ...prevState, value1: "new 1" }));
};
const changeValue2 = () => {
    setState((prevState) => ({ ...prevState, value2: "new 2" }));
};

따라서 조치가 실행될 때 이전 상태를 사용하여 현재 상태의 기존 특성이 누락되지 않았는지 확인하십시오. 또한 클로저를 관리하지 않아도됩니다.


6

changeValue2호출, 상태의 회전이 inital 상태로 있도록 초기 상태는 유지되고 다음 value2속성이 기록됩니다.

changeValue2후 다음 에 호출되면 state가 유지 {value1: "1", value2: "new 2"}되므로 value1속성을 덮어 씁니다.

setState매개 변수에 화살표 기능이 필요합니다 .

const Test = () => {
  const [state, setState] = React.useState({
    value1: "1",
    value2: "2"
  });

  const changeValue1 = () => {
    setState(prev => ({ ...prev, value1: "new 1" }));
  };
  const changeValue2 = () => {
    setState(prev => ({ ...prev, value2: "new 2" }));
  };

  return (
    <React.Fragment>
      <button
        onClick={() => {
          changeValue1();
          setTimeout(changeValue2, 1000);
        }}
      >
        CHANGE BOTH
      </button>
      <h1>{state.value1}</h1>
      <h1>{state.value2}</h1>
    </React.Fragment>
  );
};

ReactDOM.render(<Test />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>


3

무슨 일이 일어나고 있는지 changeValue1와 둘 다 생성 된 렌더changeValue2 에서 상태 볼 수 있으므로 구성 요소를 처음 렌더링 할 때이 두 함수는 다음을 참조하십시오.

state= {
  value1: "1",
  value2: "2"
}

버튼을 클릭 changeValue1하면가 먼저 호출되고 상태가 {value1: "new1", value2: "2"}예상대로 변경 됩니다.

이제 1 초 후에이 changeValue2함수가 호출되지만이 함수는 여전히 초기 상태 ( {value1; "1", value2: "2"})를 확인하므로이 함수는 다음과 같이 상태를 업데이트합니다.

setState({ ...state, value2: "new 2" });

당신은보고 : 결국 {value1; "1", value2: "new2"}.

출처

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.