programing

useEffect 후크로 이벤트를 등록하는 방법

topblog 2023. 4. 3. 21:09
반응형

useEffect 후크로 이벤트를 등록하는 방법

훅으로 이벤트를 등록하는 방법에 대한 Udemy 코스를 수강하고 있습니다.강사는 아래 코드를 알려 주었습니다.

  const [userText, setUserText] = useState('');

  const handleUserKeyPress = event => {
    const { key, keyCode } = event;

    if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
      setUserText(`${userText}${key}`);
    }
  };

  useEffect(() => {
    window.addEventListener('keydown', handleUserKeyPress);

    return () => {
      window.removeEventListener('keydown', handleUserKeyPress);
    };
  });

  return (
    <div>
      <h1>Feel free to type!</h1>
      <blockquote>{userText}</blockquote>
    </div>
  );

지금은 잘 작동하지만 이것이 올바른 방법인지 확신이 서지 않습니다.그 이유는 제가 제대로 이해한다면 매번 재렌더마다 이벤트가 등록 및 등록 해제되기 때문에 저는 그것이 올바른 방법이라고 생각하지는 않습니다.

나는 ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★useEffect에 걸치다

useEffect(() => {
  window.addEventListener('keydown', handleUserKeyPress);

  return () => {
    window.removeEventListener('keydown', handleUserKeyPress);
  };
}, []);

로 빈 배열을 가 효과를, 이 를 모방합니다.componentDidMount그리고 그 결과를 시험해 보면, 내가 입력하는 모든 키에 덧셈을 하는 것이 아니라 덮어쓰게 되는 것이 이상하다.

setUserText();${userText}${key} 새로운 타입의 키가 현재 상태에 추가되어 새로운 상태로 설정될 것으로 예상했는데, 이전 상태를 잊어버리고 새 상태로 다시 씁니다.

재렌더마다 이벤트를 등록 및 등록 해제하는 것이 정말 올바른 방법이었습니까?

이러한 시나리오를 해결하는 가장 좋은 방법은 이벤트 핸들러에서 무엇을 하고 있는지 확인하는 것입니다.

「」를 설정하는 는,state, 앞의 " " "의 사용state콜백 패턴을 사용하여 이벤트청취자를 초기 마운트에만 등록하는 것이 가장 좋습니다.

콜백 패턴을 사용하지 않으면 이벤트청취자에 의해 리스너 레퍼런스와 그 어휘 스코프가 사용되고 있습니다만, 각 렌더에 대해 갱신된 클로징을 수반하는 새로운 함수가 생성됩니다.따라서 핸들러에서는 갱신된 상태에 액세스할 수 없습니다.

const [userText, setUserText] = useState("");
const handleUserKeyPress = useCallback(event => {
    const { key, keyCode } = event;
    if(keyCode === 32 || (keyCode >= 65 && keyCode <= 90)){
        setUserText(prevUserText => `${prevUserText}${key}`);
    }
}, []);

useEffect(() => {
    window.addEventListener("keydown", handleUserKeyPress);
    return () => {
        window.removeEventListener("keydown", handleUserKeyPress);
    };
}, [handleUserKeyPress]);

  return (
      <div>
          <h1>Feel free to type!</h1>
          <blockquote>{userText}</blockquote>
      </div>
  );

쟁점.

[...] 매번 다시 대여할 때마다 이벤트가 등록 및 등록 해제되고, 저는 이것이 올바른 방법이라고 생각하지 않습니다.

에서 이벤트 하는 것은 가 없습니다.useEffect모든 렌더링에 대해

[...] 두 번째 인수로 배열을 비워두면 구성 요소가 효과를 한 번만 실행하도록 허용됩니다. [...] 입력하는 모든 키에 추가가 아니라 덮어쓰게 됩니다.

이것은 오래된 마감 값에 관한 문제입니다.

이유: 내부 기능 사용useEffect종속성의 일부여야 합니다.의존관계로서 아무것도 설정하지 않았다([]), 그러나 콜은 계속됩니다.handleUserKeyPress에는 「」라고 쓰여져 있습니다userTextdiscloss.discloss.conf.

솔루션

0. useEffectEvent (카메라에)

업데이트: React Developers는 새로운 RFC를 제안했습니다.useEvent후크(이후 이름과 기능이 약간 변경됨)를 사용하여 이러한 유형의 이벤트 관련 문제를 의존관계로 해결합니다.

그때까지 사용 사례에 따라 다음과 같은 대체 방법이 있습니다.

1. 상태 업데이트 프로그램 기능

setUserText(prev => `${prev}${key}`);

✔ 접근법✔최저침습적 접근법
✖ Hooks✖는 않습니다.

const App = () => {
  const [userText, setUserText] = useState("");

  useEffect(() => {
    const handleUserKeyPress = event => {
      const { key, keyCode } = event;

      if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
        setUserText(prev => `${prev}${key}`); // use updater function here
      }
    };

    window.addEventListener("keydown", handleUserKeyPress);
    return () => {
      window.removeEventListener("keydown", handleUserKeyPress);
    };
  }, []); // still no dependencies

  return (
    <div>
      <h1>Feel free to type!</h1>
      <blockquote>{userText}</blockquote>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16.13.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>

2. useRef / / 가변참조

const cbRef = useRef(handleUserKeyPress);
useEffect(() => { cbRef.current = handleUserKeyPress; }); // update each render
useEffect(() => {
    const cb = e => cbRef.current(e); // then use most recent cb value
    window.addEventListener("keydown", cb);
    return () => { window.removeEventListener("keydown", cb) };
}, []);

const App = () => {
  const [userText, setUserText] = useState("");

  const handleUserKeyPress = event => {
    const { key, keyCode } = event;

    if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
      setUserText(`${userText}${key}`);
    }
  };

  const cbRef = useRef(handleUserKeyPress);

  useEffect(() => {
    cbRef.current = handleUserKeyPress;
  });

  useEffect(() => {
    const cb = e => cbRef.current(e);
    window.addEventListener("keydown", cb);

    return () => {
      window.removeEventListener("keydown", cb);
    };
  }, []);

  return (
    <div>
      <h1>Feel free to type!</h1>
      <blockquote>{userText}</blockquote>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16.13.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef, useCallback } = React</script>

✔는 데이터 흐름을 통해 재설정을 트리거해서는 안 되는 콜백/이벤트 핸들러에 사용할 수 있습니다.
✔의존관계 관리
✖좀 더 접근법 ✖ 더 긴요한 접근
docs ✖ React의

자세한 내용은 다음 링크를 참조하십시오: 1 2 3

3. useReducer- "스위치 모드"

바꿀 수 요.useReducer 할 수 .- API는 합니다.- API는 유사합니다.useState.

Variant 2a: 리듀서 기능 내부 로직

const [userText, handleUserKeyPress] = useReducer((state, event) => {
    const { key, keyCode } = event;
    // isUpperCase is always the most recent state (no stale closure value)
    return `${state}${isUpperCase ? key.toUpperCase() : key}`;  
}, "");

const App = () => {
  const [isUpperCase, setUpperCase] = useState(false);
  const [userText, handleUserKeyPress] = useReducer((state, event) => {
    const { key, keyCode } = event;
    if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
      // isUpperCase is always the most recent state (no stale closure)
      return `${state}${isUpperCase ? key.toUpperCase() : key}`;
    }
  }, "");

  useEffect(() => {
    window.addEventListener("keydown", handleUserKeyPress);

    return () => {
      window.removeEventListener("keydown", handleUserKeyPress);
    };
  }, []);

  return (
    <div>
      <h1>Feel free to type!</h1>
      <blockquote>{userText}</blockquote>
      <button style={{ width: "150px" }} onClick={() => setUpperCase(b => !b)}>
        {isUpperCase ? "Disable" : "Enable"} Upper Case
      </button>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16.13.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>

- Variant 2b와 : - 로직useState 기능

const [userText, setUserText] = useReducer((state, action) =>
      typeof action === "function" ? action(state, isUpperCase) : action, "");
// ...
setUserText((prevState, isUpper) => `${prevState}${isUpper ? 
  key.toUpperCase() : key}`);

const App = () => {
  const [isUpperCase, setUpperCase] = useState(false);
  const [userText, setUserText] = useReducer(
    (state, action) =>
      typeof action === "function" ? action(state, isUpperCase) : action,
    ""
  );

  useEffect(() => {
    const handleUserKeyPress = event => {
      const { key, keyCode } = event;
      if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
        setUserText(
          (prevState, isUpper) =>
            `${prevState}${isUpper ? key.toUpperCase() : key}`
        );
      }
    };

    window.addEventListener("keydown", handleUserKeyPress);
    return () => {
      window.removeEventListener("keydown", handleUserKeyPress);
    };
  }, []);

  return (
    <div>
      <h1>Feel free to type!</h1>
      <blockquote>{userText}</blockquote>
      <button style={{ width: "150px" }} onClick={() => setUpperCase(b => !b)}>
        {isUpperCase ? "Disable" : "Enable"} Upper Case
      </button>
    </div>
  );
}


ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16.13.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>

✔의존관계 관리
✔ 여러 주 및 소품 액세스
는 ✔ API와 동일useState
✔/청소기로 ✔ 더서서 / 스서서서서서서서서
✖ 인라인 리듀서로 인해 성능이 약간 저하됨(경시할 수 있음)
✖ 환원제의 복잡성 약간 증가

부적절한 솔루션

useCallback

다양한 방법으로 적용할 수 있지만useCallback는 이 특정 질문 케이스에 적합하지 않습니다.

추가되었기 에: 의존관계는 다음과 같습니다.userText여기서 - 이벤트청취자는 키를 누를 마다 다시 표시됩니다.최적의 경우 퍼포먼스가 저하되거나 부정합이 발생할 수 있습니다.

const App = () => {
  const [userText, setUserText] = useState("");

  const handleUserKeyPress = useCallback(
    event => {
      const { key, keyCode } = event;

      if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
        setUserText(`${userText}${key}`);
      }
    },
    [userText]
  );

  useEffect(() => {
    window.addEventListener("keydown", handleUserKeyPress);

    return () => {
      window.removeEventListener("keydown", handleUserKeyPress);
    };
  }, [handleUserKeyPress]); // we rely directly on handler, indirectly on userText

  return (
    <div>
      <h1>Feel free to type!</h1>
      <blockquote>{userText}</blockquote>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16.13.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef, useCallback } = React</script>

합니다.useEffect

이벤트 핸들러 기능을 직접 내부에서 선언하는 경우 다음과 같은 문제가 발생합니다.useCallback후자는 의존관계의 간접성을 조금 더 높일 뿐입니다.

,, 음, 음, 다, 다, 다, in, in, in, in.하지 않고 additions를 통해 종속성을 합니다.useCallback을 바로 useEffect가 있기 하게 발생합니다 네, 핸들러 변경이 필요합니다.

in,를 신신면면면면면면면면면면면.handleUserKeyPress에 inside inside inside useEffectESLint "deps "는 "deps").userText를 참조해 주세요.

const App =() => {
  const [userText, setUserText] = useState("");

  useEffect(() => {
    const handleUserKeyPress = event => {
      const { key, keyCode } = event;

      if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
        setUserText(`${userText}${key}`);
      }
    };

    window.addEventListener("keydown", handleUserKeyPress);

    return () => {
      window.removeEventListener("keydown", handleUserKeyPress);
    };
  }, [userText]); // ESLint will yell here, if `userText` is missing

  return (
    <div>
      <h1>Feel free to type!</h1>
      <blockquote>{userText}</blockquote>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16.13.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>

새로운 답변:

useEffect(() => {
  function handlekeydownEvent(event) {
    const { key, keyCode } = event;
    if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
      setUserText(prevUserText => `${prevUserText}${key}`);
    }
  }

  document.addEventListener('keyup', handlekeydownEvent)
  return () => {
    document.removeEventListener('keyup', handlekeydownEvent)
  }
}, [])

「」를 하고 있는 setUserTextprevUserText을 사용하다


오래된 답변:

원래 코드와 동일하게 동작합니다.

useEffect(() => {
  function handlekeydownEvent(event) {
    const { key, keyCode } = event;
    if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
      setUserText(`${userText}${key}`);
    }
  }

  document.addEventListener('keyup', handlekeydownEvent)
  return () => {
    document.removeEventListener('keyup', handlekeydownEvent)
  }
}, [userText])

당신의 냐 your your your your your your your에서useEffect()따라 .userText두 않으면 두 번째 인수에는 넣지 않습니다.그렇지 않으면userText.''[].

이렇게 할 필요는 없습니다.두 번째 솔루션이 작동하지 않는 이유를 알려드리고 싶습니다.

이전 상태를 추적할 수 있는 방법이 필요합니다. useState그럼 현재 상태만 추적할 수 있습니다.문서에서 다른 후크를 사용하여 이전 상태에 액세스하는 방법이 있습니다.

const prevRef = useRef();
useEffect(() => {
  prevRef.current = userText;
});

이것을 사용하기 위해 당신의 예를 업데이트했습니다.그리고 효과가 있다.

const { useState, useEffect, useRef } = React;

const App = () => {
  const [userText, setUserText] = useState("");
  const prevRef = useRef();
  useEffect(() => {
    prevRef.current = userText;
  });

  const handleUserKeyPress = event => {
    const { key, keyCode } = event;

    if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
      setUserText(`${prevRef.current}${key}`);
    }
  };

  useEffect(() => {
    window.addEventListener("keydown", handleUserKeyPress);

    return () => {
      window.removeEventListener("keydown", handleUserKeyPress);
    };
  }, []);

  return (
    <div>
      <h1>Feel free to type!</h1>
      <blockquote>{userText}</blockquote>
    </div>
  );
};

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

예에 는, 「 」를 참조해 주세요.useEffect는 변경을 추적하기 위해 의존관계 어레이가 필요하며, 의존관계에 따라 재설치 여부를 결정할 수 있습니다. '의존 배열'을 '의존 배열'로 하는 것이 .useEffect아래 코드를 참조해 주세요.

했습니다.useCallback

const { useCallback, useState, useEffect } = React;

  const [userText, setUserText] = useState("");

  const handleUserKeyPress = useCallback(event => {
    const { key, keyCode } = event;

    if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
      setUserText(prevUserText => `${prevUserText}${key}`);
    }
  }, []);

  useEffect(() => {
    window.addEventListener("keydown", handleUserKeyPress);

    return () => {
      window.removeEventListener("keydown", handleUserKeyPress);
    };
  }, [handleUserKeyPress]);

  return (
    <div>
      <blockquote>{userText}</blockquote>
    </div>
  );

q98jov5kvq 편집

된 답변은 만, 하고 있는 하고 있습니다.BackHandler 「」, 「」로 해 주세요.return true 안에서handleBackPressoperating. : :

const handleBackPress= useCallback(() => {
   // do some action and return true or if you do not
   // want the user to go back, return false instead
   return true;
 }, []);

 useEffect(() => {
    BackHandler.addEventListener('hardwareBackPress', handleBackPress);
    return () =>
       BackHandler.removeEventListener('hardwareBackPress', handleBackPress);
  }, [handleBackPress]);

다음은 @ford04의 답변을 바탕으로 커스텀 훅으로 이동한 useRef 솔루션입니다.수동 종속성을 추가할 필요가 없고 커스텀 훅의 복잡성을 모두 숨길 수 있기 때문에 가장 마음에 듭니다.

const useEvent = (eventName, eventHandler) => {
  const cbRef = useRef(eventHandler)

  useEffect(() => {
    cbRef.current = eventHandler
  }) // update after each render

  useEffect(() => {
    console.log("+++ subscribe")
    const cb = (e) => cbRef.current(e) // then use most recent cb value
    window.addEventListener(eventName, cb)
    return () => {
      console.log("--- unsubscribe")
      window.removeEventListener(eventName, cb)
    }
  }, [eventName])
  return
}

앱에서의 사용:

function App() {
  const [isUpperCase, setUpperCase] = useState(false)
  const [userText, setUserText] = useState("")

  const handleUserKeyPress = (event) => {
    const { key, keyCode } = event
    if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
      const displayKey = isUpperCase ? key.toUpperCase() : key
      const newText = userText + displayKey
      setUserText(newText)
    }
  }
  useEvent("keydown", handleUserKeyPress)

  return (
    <div>
      <h1>Feel free to type!</h1>
      <label>
        <input
          type="checkbox"
          defaultChecked={isUpperCase}
          onChange={() => setUpperCase(!isUpperCase)}
        />
        Upper Case
      </label>
      <blockquote>{userText}</blockquote>
    </div>
  )
}

편집

에서는 " " "는useEffect 되어 있기 에 1회만 바인드 됩니다.userText업데이트되지 않습니다. 가지 은 로컬 입니다. 변수는 로컬 변수와 됩니다.이 변수는 로컬 변수와 함께 갱신됩니다.userText모든 키 누름에 오브젝트를 지정합니다.

  const [userText, setUserText] = useState('');
  let local_text = userText
  const handleUserKeyPress = event => {
    const { key, keyCode } = event;

    if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
      local_text = `${userText}${key}`;
      setUserText(local_text);
    }
  };

  useEffect(() => {
    window.addEventListener('keydown', handleUserKeyPress);

    return () => {
      window.removeEventListener('keydown', handleUserKeyPress);
    };
  }, []);

  return (
    <div>
      <h1>Feel free to type!</h1>
      <blockquote>{userText}</blockquote>
    </div>
  );

는 개인적으로 그 마음에 들지 .anti-react그리고 첫 번째 방법은 충분히 좋은 방법이고 그렇게 사용할 수 있도록 고안되었다고 생각합니다.

변경된 useText 상태에 액세스할 수 없습니다.prevState에 접속할 수 있습니다.상태는 다음과 같이 변수(예:)에 저장됩니다.

const App = () => {
  const [userText, setUserText] = useState('');

  useEffect(() => {
    let state = ''

    const handleUserKeyPress = event => {
      const { key, keyCode } = event;
      if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
        state += `${key}`
        setUserText(state);
      }   
    };  
    window.addEventListener('keydown', handleUserKeyPress);
    return () => {
      window.removeEventListener('keydown', handleUserKeyPress);
    };  
  }, []);

  return (
    <div>
      <h1>Feel free to type!</h1>
      <blockquote>{userText}</blockquote>
    </div>
  );  
};

언급URL : https://stackoverflow.com/questions/55565444/how-to-register-event-with-useeffect-hooks

반응형