it-source

React useState with functional update 폼이 필요한 이유는 무엇입니까?

criticalcode 2023. 2. 25. 21:21
반응형

React useState with functional update 폼이 필요한 이유는 무엇입니까?

기능 업데이트에 대한 React Hook 문서를 읽고 있는데 다음 인용문을 참조하십시오.

업데이트된 값이 이전 값을 기반으로 하므로 "+" 및 "-" 버튼은 기능 양식을 사용합니다.

그러나 기능 갱신이 어떤 목적으로 필요한지, 새로운 상태의 컴퓨팅에서 오래된 상태를 직접 사용하는 것과 어떤 차이가 있는지 알 수 없습니다.

React useState Hook의 업데이트 기능에 기능 업데이트 양식이 필요한 이유는 무엇입니까? 차이를 명확하게 알 수 있는 예(직접 업데이트를 사용하면 버그가 발생합니다)는 무엇입니까?

예를 들어 이 예를 문서에서 변경한 경우

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
    </>
  );
}

count★★★★

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(count + 1)}>+</button>
      <button onClick={() => setCount(count - 1)}>-</button>
    </>
  );
}

동작의 차이를 알 수 없고 카운트가 갱신되지 않는(또는 최신이 아닌) 경우를 상상할 수 없습니다. 카운트가 바뀔 이 생기기 때문입니다.onClick의 「」를 합니다.count.

반응하다가 있을 수 .count 코드 샘플의 .예를 들어, 다음 2개의 코드샘플의 결과를 비교합니다.

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => {
        setCount(prevCount => prevCount + 1); 
        setCount(prevCount => prevCount + 1)}
      }>+</button>
    </>
  );
}

그리고.

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => {
        setCount(count + 1); 
        setCount(count + 1)}
      }>+</button>
    </>
  );
}

나는 최근에 우연히 이것에 대한 필요성을 느꼈다.예를 들어, 어레이를 어느 정도의 요소로 채우고, 그 어레이에 추가할 수 있는 컴포넌트가 있다고 합시다(예를 들어, 사용자가 화면을 아래로 스크롤 할 때 피드 10개 항목을 한 번에 로드하고 있었습니다).코드는 이렇게 생겼습니다.

function Stream() {
  const [feedItems, setFeedItems] = useState([]);
  const { fetching, error, data, run } = useQuery(SOME_QUERY, vars);

  useEffect(() => {
    if (data) {
      setFeedItems([...feedItems, ...data.items]);
    }
  }, [data]);     // <---- this breaks the rules of hooks, missing feedItems

...
<button onClick={()=>run()}>get more</button>
...

setFeedItems를 호출하기 때문에 feedItems를 useEffect 후크의 종속성 목록에 추가할 수는 없습니다.

복구 기능 업데이트:

useEffect(() => {
    if (data) {
      setFeedItems(prevItems => [...prevItems, ...data.items]);
    }
  }, [data]);     //  <--- all good now

저는 이와 같은 질문에 대답했습니다만, 이것이 정식 질문이었기 때문에 종료되었습니다.그것은 제가 몰랐던 것입니다.그 대답을 보고, 저는 그것이 어떤 가치를 더하고 있다고 생각하기 때문에, 여기에 제 답변을 다시 게재하기로 결정했습니다.

업데이트가 해당 주에서 발견된 이전 값에 따라 달라지는 경우 기능 양식을 사용해야 합니다.이 경우 기능 양식을 사용하지 않으면 코드가 깨질 수 있습니다.

왜 깨지고 언제 깨지고

반응 기능 성분은 폐쇄일 뿐이며, 폐쇄에 있는 상태 값이 오래되었을 수 있습니다. 즉, 폐쇄 내부의 값이 해당 성분에 대해 반응 상태에 있는 값과 일치하지 않으며, 다음과 같은 경우에 발생할 수 있습니다.

1 비동기 조작(이 예에서는 slow add를 클릭한 후 add 버튼을 여러 번 클릭하면 나중에 slow add 버튼을 클릭했을 때 상태가 closure 내부로 리셋된 것을 알 수 있습니다.)

const App = () => {
  const [counter, setCounter] = useState(0);

  return (
    <>
      <p>counter {counter} </p>
      <button
        onClick={() => {
          setCounter(counter + 1);
        }}
      >
        immediately add
      </button>
      <button
        onClick={() => {
          setTimeout(() => setCounter(counter + 1), 1000);
        }}
      >
        Add
      </button>
    </>
  );
};

2- 동일한 폐쇄에서 업데이트 기능을 여러 번 호출하는 경우

const App = () => {
  const [counter, setCounter] = useState(0);

  return (
    <>
      <p>counter {counter} </p>
      <button
        onClick={() => {
          setCounter(counter + 1);
          setCounter(counter + 1);
        }}
      >
        Add twice
      </button>
   
    </>
  );
}

State update is asynchronous in React라는 답변은 오해의 소지가 있습니다.아래의 코멘트도 있습니다.내가 이것을 더 파고들기 전까지는 내 생각도 틀렸다.맞아요, 이건 거의 필요 없어요.

기능 상태 업데이트의 핵심 개념은 새로운 상태에 의존하는 상태가 오래될 수 있다는 것입니다.어떻게 주(州)가 오래됐지?이것에 관한 몇개의 속설을 불식시켜 봅시다.

  • 속설: 이벤트 처리 중에 상태가 변경될 수 있습니다.
    • 팩트: ECMAScript 이벤트루프는 한 번에 1개만 실행합니다.핸들러를 실행하고 있는 경우는, 핸들러와 함께 실행되고 있는 것은 없습니다.
  • 속설: 2회 빠르게 클릭하면(또는 다른 사용자 액션이 빠르게 실행되면) 양쪽 핸들러 콜에서 상태 업데이트가 일괄 처리될 수 있습니다.
    • 팩트: React는 여러 사용자가 시작한 이벤트에 걸쳐 업데이트를 일괄 처리하지 않을 것을 보증합니다.이는 이전 버전보다 더 많은 일괄 처리를 수행하는 React 18에서도 마찬가지입니다.이벤트 핸들러 사이에 렌더가 있는 것을 신뢰할 수 있습니다.

리액트 작업 그룹:

주의: 일반적으로 안전한 경우에만 배치 업데이트에 대응하십시오.예를 들어 React를 사용하면 클릭이나 키 누르기와 같은사용자 시작 이벤트에 대해 다음 이벤트 전에 DOM이 완전히 업데이트됩니다.이것에 의해, 예를 들면, 송신시에 디세블로 하는 폼을 2회 송신할 수 없게 됩니다.

그래서 언제 오래된 상태가 되나요?

생각할 수 있는 주요 케이스는 다음과 같습니다.

동일한 핸들러에 여러 상태 업데이트

이것은, 같은 핸들러로 같은 상태를 여러 번 설정해, 이전의 상태에 의존하는 경우입니다.지적하신 바와 같이, 이 사건은 상당히 조작된 것으로 보입니다.왜냐하면 이는 명백히 잘못된 것으로 보이기 때문입니다.

  <button
    onClick={() => {
      setCount(count + 1);
      setCount(count + 1);
    }}
  >+</button>

보다 그럴듯한 경우는 각각이 같은 상태에서 업데이트를 실행하고 이전 상태에 따라 달라지는 여러 함수를 호출하는 것입니다.그래도 이상해요. 모든 계산을 한 다음 주를 한 번 설정하는 게 더 말이 되겠네요.

핸들러의 비동기 상태 업데이트

예를 들어 다음과 같습니다.

  <button
    onClick={() => {
      doSomeApiCall().then(() => setCount(count + 1));
    }}
  >+</button>

이것은 그렇게 명백하게 틀린 것은 아니다.통화 중 상태를 변경할 수 있습니다.doSomeApiCall언제 해결되는지 알아야 합니다. 경우 상태 업데이트는 정말로 비동기적이지만 React가 아니라 이렇게 했습니다.

기능 폼에 의해 다음과 같은 문제가 해결됩니다.

  <button
    onClick={() => {
      doSomeApiCall().then(() => setCount((currCount) => currCount + 1));
    }}
  >+</button>

useEffect 상태 업데이트 중

G Gallegos의 답변에 따르면useEffect일반적으로, 그리고 Letvar의 답변은 이 점을 지적했다.useEffect와 함께requestAnimationFrame의 이전 상태를 기반으로 상태를 갱신하는 경우useEffect그 상태를 의존관계 배열(또는 의존관계 배열을 사용하지 않음)에 넣는 것은 무한 루프의 레시피입니다.대신 기능 양식을 사용하십시오.

요약

사용자가 트리거하는 이벤트 핸들러 2. 상태별로 1회, 3. 동기식으로 하면 이전 상태에 기반한 상태 업데이트에 기능 폼이 필요하지 않습니다.이러한 조건 중 하나를 위반하면 기능 업데이트가 필요합니다.

기능 업데이트를 항상 사용하려는 사용자도 있을 수 있으므로 이러한 상황에 대해 걱정할 필요가 없습니다.다른 사람들은 명확성을 위해 더 짧은 형식을 선호할 수 있습니다. 이는 많은 핸들러에게 해당됩니다.이 시점에서는 개인적인 취향/코드 스타일입니다.

이력 메모

클래스 컴포넌트만 상태를 가질 때 후크 전에 리액트를 배웠습니다.클래스 컴포넌트에서는 "같은 핸들러 내의 여러 상태 갱신"이 그다지 잘못된 것처럼 보이지 않습니다.

  <button
    onClick={() => {
      this.setState({ count: this.state.count + 1 });
      this.setState({ count: this.state.count + 1 });
    }}
  >+</button>

state는 함수 파라미터가 아닌 인스턴스 변수이므로 다음과 같은 경우를 제외하고 이 값은 정상으로 표시됩니다.setState는, 같은 핸들러에 있는 콜을 배치 합니다.

실제로 React <= 17에서는 다음과 같이 올바르게 작동합니다.

  setTimeout(() => {
    this.setState({ count: this.state.count + 1 });
    this.setState({ count: this.state.count + 1 });
  }, 1000);

이벤트 핸들러가 아니기 때문에 리액트 리렌더(Respect rerender)는 각각setState불러.

리액트 18은 이 케이스 및 유사한 케이스에 대한 배치 처리를 도입합니다.이는 유용한 성능 향상입니다.위의 동작에 의존하는 클래스 컴포넌트를 망가뜨린다는 단점이 있습니다.

레퍼런스

를 사용하는 또 다른 예setState-requestAnimationFrame리액트 훅 포함.상세한 것에 대하여는, https://css-tricks.com/using-requestanimationframe-with-react-hooks/ 를 참조해 주세요.

「」의 입니다.requestAnimationFrame 되어 부정확한 결과를 count를할 때,setCount(count+delta)★★★★★★★★★★★★★★★★★★★★★★★」setCount(prevCount => prevCount + delta)올바른 값을 산출합니다.

언급URL : https://stackoverflow.com/questions/57828368/why-react-usestate-with-functional-update-form-is-needed

반응형