IT이야기

반응 후크를 사용하여 이벤트 핸들러가 잘못된 상태로 발사되는 이유는?

cyworld 2022. 3. 24. 21:47
반응형

반응 후크를 사용하여 이벤트 핸들러가 잘못된 상태로 발사되는 이유는?

나는 이 회전하는 것의 복사본을 만들려고 한다.div반응 후크 사용 예시.https://codesandbox.io/s/XDjY28XoV

지금까지 내 암호는 이렇다.

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

const App = () => {
  const [box, setBox] = useState(null);

  const [isActive, setIsActive] = useState(false);
  const [angle, setAngle] = useState(0);
  const [startAngle, setStartAngle] = useState(0);
  const [currentAngle, setCurrentAngle] = useState(0);
  const [boxCenterPoint, setBoxCenterPoint] = useState({});

  const setBoxCallback = useCallback(node => {
    if (node !== null) {
      setBox(node)
    }
  }, [])

  // to avoid unwanted behaviour, deselect all text
  const deselectAll = () => {
    if (document.selection) {
      document.selection.empty();
    } else if (window.getSelection) {
      window.getSelection().removeAllRanges();
    }
  }

  // method to get the positionof the pointer event relative to the center of the box
  const getPositionFromCenter = e => {
    const fromBoxCenter = {
      x: e.clientX - boxCenterPoint.x,
      y: -(e.clientY - boxCenterPoint.y)
    };
    return fromBoxCenter;
  }

  const mouseDownHandler = e => {
    e.stopPropagation();
    const fromBoxCenter = getPositionFromCenter(e);
    const newStartAngle =
      90 - Math.atan2(fromBoxCenter.y, fromBoxCenter.x) * (180 / Math.PI);
    setStartAngle(newStartAngle);
    setIsActive(true);
  }

  const mouseUpHandler = e => {
    deselectAll();
    e.stopPropagation();
    if (isActive) {
      const newCurrentAngle = currentAngle + (angle - startAngle);
      setIsActive(false);
      setCurrentAngle(newCurrentAngle);
    }
  }

  const mouseMoveHandler = e => {
    if (isActive) {
      const fromBoxCenter = getPositionFromCenter(e);
      const newAngle =
        90 - Math.atan2(fromBoxCenter.y, fromBoxCenter.x) * (180 / Math.PI);
      box.style.transform =
        "rotate(" +
        (currentAngle + (newAngle - (startAngle ? startAngle : 0))) +
        "deg)";
      setAngle(newAngle)
    }
  }

  useEffect(() => {
    if (box) {
      const boxPosition = box.getBoundingClientRect();
      // get the current center point
      const boxCenterX = boxPosition.left + boxPosition.width / 2;
      const boxCenterY = boxPosition.top + boxPosition.height / 2;

      // update the state
      setBoxCenterPoint({ x: boxCenterX, y: boxCenterY });
    }

    // in case the event ends outside the box
    window.onmouseup = mouseUpHandler;
    window.onmousemove = mouseMoveHandler;
  }, [ box ])

  return (
    <div className="box-container">
      <div
        className="box"
        onMouseDown={mouseDownHandler}
        onMouseUp={mouseUpHandler}
        ref={setBoxCallback}
      >
        Rotate
      </div>
    </div>
  );
}

export default App;

현재 mouseMoveHandler는 다음 상태로 호출됨isActive = false비록 국가가 사실일지라도이 이벤트 핸들러를 올바른 상태로 작동시키려면 어떻게 해야 하는가?

또한 콘솔에 다음과 같은 경고가 기록되고 있다.

React Hook useEffect has missing dependencies: 'mouseMoveHandler' and 'mouseUpHandler'. Either include them or remove the dependency array  react-hooks/exhaustive-deps

useEffect 종속성 어레이에 구성 요소 메서드를 포함해야 하는 이유나는 리액션 훅을 사용하는 다른 간단한 부품에 대해 이렇게 할 필요가 없었다.

감사합니다.

문제

왜 그럴까?isActive거짓인가?

const mouseMoveHandler = e => {
   if(isActive) {
       // ...
   }
};

(편리상 참고사항 나는 단지 말하는 것이다.mouseMoveHandler, 그러나 여기의 모든 것은 에 적용된다.mouseUpHandler또한)

위의 코드가 실행되면 함수 인스턴스가 생성되어isActive기능 폐쇄를 통해 가변적그 변수는 상수니까, 만약isActive함수가 정의되면 항상 거짓이고false해당 함수 인스턴스가 존재하는 한.

useEffect또한 함수를 취하며, 그 함수는 당신의 함수에 대한 지속적인 참조를 가지고 있다.moveMouseHandler함수 인스턴스 - useEffect 콜백(Effect Callback)이 존재하는 한 다음과 같은 복사본을 참조한다.moveMouseHandler어디에isActive거짓이다.

언제isActive변경사항, 구성요소 렌더링 및 새로운 인스턴스moveMouseHandler어떤 것이 만들어질 것인가.isActive이다true그러나useEffect종속성이 변경된 경우에만 해당 기능을 다시 실행 - 이 경우 종속성(이 경우)[box])은 변하지 않았으므로,useEffect실행되지 않고 의 버전moveMouseHandler어디에isActivefalse는 현재 상태와 관계없이 여전히 창문에 부착되어 있다.

이 때문에 '고갈'이 경고하는 것이다.useEffect- 후크가 해당 종속성을 재실행하고 업데이트하지 않고 일부 종속성이 변경될 수 있음.


고치고 있다

훅이 간접적으로 의존하고 있기 때문에isActive, 당신은 이것을 추가함으로써 고칠 수 있다.isActive에게deps을 위해 배열하다.useEffect:

// Works, but not the best solution
useEffect(() => {
    //...
}, [box, isActive])

하지만, 이것은 그다지 깨끗하지 않다: 만약 당신이 변화한다면.mouseMoveHandler더 많은 상태에 따라 달라지기 위해서, 당신은 같은 버그를 갖게 될 것이다, 만약 당신이 와서 그것을 더하는 것을 기억하지 않는다면.deps배열도. (또한 린터는 이것을 좋아하지 않을 것이다)

useEffect간접적으로 작용하다isActive에 직접 달려있기 때문에mouseMoveHandler; 대신 종속성에 추가할 수 있도록:

useEffect(() => {
    //...
}, [box, mouseMoveHandler])

는 useEffect의 인 useEffect를 mouseMoveHandler그 말은 존중한다는 뜻이지isActive하지만 너무 자주 실행될 것이다. 매번 실행될 것이다.mouseMoveHandler새로운 함수 인스턴스가 되다...새로운 기능이 모든 렌더에 생성되기 때문에 모든 렌더에 해당된다.

렌더링할 때마다 새로운 기능을 만들 필요는 없지만,isActive변경됨:리액션은useCallback그 사용 사례에 찬성하다.사용자 정의 가능mouseMoveHandler로서

const mouseMoveHandler = useCallback(e => {
   if(isActive) {
       // ...
   }
}, [isActive])

그리고 이제 새로운 함수 인스턴스는 단지isActive그런 다음 트리거되는 변경 사항useEffect적절한 순간에 실행할 수 있으며, 당신은 의 정의를 변경할 수 있다.mouseMoveHandler(예: 상태 추가)를 중단하지 않고useEffect낚싯바늘을 달다


이것은 여전히 당신의 문제를 야기시킬 수 있다.useEffect 매번 재방송할 거야 거야.isActive변경 사항, 즉 매번 박스 중심점을 설정한다는 의미isActive아마도 원치 않는 변화일 것이다.이 문제를 피하려면 효과를 두 개의 개별 효과로 나누십시오.

useEffect(() => {
    // update box center
}, [box])

useEffect(() => {
   // expose window methods
}, [mouseMoveHandler, mouseUpHandler]);

종료 결과

궁극적으로 코드는 다음과 같아야 한다.

const mouseMoveHandler = useCallback(e => {
    /* ... */
}, [isActive]);

const mouseUpHandler = useCallback(e => {
    /* ... */
}, [isActive]);

useEffect(() => {
   /* update box center */
}, [box]);

useEffect(() => {
   /* expose callback methods */
}, [mouseUpHandler, mouseMoveHandler])

추가 정보:

반응 작가 중 한 명인 Dan Abramov는 효과 블로그 포스트를 사용하기 위해 Complete Guide에 더 자세히 설명되어 있다.

반응 후크 useState+useEffect+이벤트는 오래된 상태를 제공한다.유사한 문제를 겪고 있는 것 같다. 기본적인 문제는 "그것이 정의된 폐쇄로부터 가치를 얻는"이다.

솔루션 2 "기준 사용"을 시도해 보십시오.당신의 시나리오로

아래에 useRefuseEffect 추가

let refIsActive = useRef(isActive);
useEffect(() => {
    refIsActive.current = isActive;
});

그런 다음 마우스 안쪽에MoveHandler, 해당 참조를 사용하십시오.

 const mouseMoveHandler = (e) => {    
  console.log('isActive',refIsActive.current);
    if (refIsActive.current) {

나는 그것을 해결하기 위해 NPM 모듈을 만들었다.https://www.npmjs.com/package/react-usestateref그리고 그것은 현재의 상태를 어떻게 발사할 것인가에 대한 당신의 질문에 대한 대답과 도움이 될 것 같다.

useState와 값을 수 있도록 .ref

사용 예:

    const [isActive, setIsActive,isActiveRef] = useStateRef(false);
    console.log(isActiveRef.current)

추가 정보:

참조URL: https://stackoverflow.com/questions/55874789/using-react-hooks-why-are-my-event-handlers-firing-with-the-incorrect-state

반응형