Study
react
Useeffect

useEffect

useEffect 기본 문법

  • useEffect(setup, dependencies?)
  • useEffect 렌더링 후 mount 시점에 실행
    • 클래스형 컴포넌트 : componentDidMount 시점에 비동기로 실행
    • 함수형 컴포넌트 : return JSX(render) 다음 실행
  • cleanup 코드componentWillUnMount 시점에 실행
    • re-render 후에 React는 cleanup 함수를 이전 값으로 실행한 다음, 새 값으로 설정 함수를 실행함
    • 컴포넌트가 DOM에서 제거된 후, React는 정리 함수를 실행
  • 의존성 배열 전달과 리렌더링의 연관 관계
    • 종속성 배열 전달 : 초기 렌더링에 실행, 종속성이 변경되어 다시 실행된 경우에도 실행
    • 빈 종속성 배열 전달 : 효과가 반응형 값을 전혀 사용하지 않는 경우 초기 렌더링에만 실행(1회만)
    • 종속성 배열을 전혀 전달하지 않을 경우 : 렌더링 및 리렌더링 할 때마다 실행
  • 의존 관계 지정 시 primitive는 값, reference는 주소임

useEffect와 <StrictMode>

  • useEffect의 경우 의존성 배열이 비워([])져있을 때 두 번 호출됨
    • Mount - UnMount - Mount
  • 2회 마운트 / 호출 가능한 부분의 안전한 코딩을 위해!
    • useState, useEffect, useReduce의 callback을 2회 실행
  • Strict Mode를 꺼두면 잘못된 코드를 작성할 가능성이 높기 때문
    • clean-up 함수 유실, 중복 데이터 처리 등

⇒ clean-up 해줄 것이 있는지 항상 염두하고 코드 짜기

useEffect와 비동기(Promise)

  • Promise는 실행될 때 callback 함수를 background로 제어를 넘김
  • re-render 등으로 UnMount시에 Promise는 background에 존재하므로 clean-up되지 않음

⇒ clean-up 코드에서 **Promise를 중지(abort)**해줘야 리렌더링되지 않음 (정석)

(단, request server는 2회)

controller = new AbortController();     // non-signal: 3회 render (TQ에 2번 들어감)
 
const { signal } = controller;          // with-signal: 2회 render (처음 cb가 abort → TQ 1번)
 
fetch(url, { signal });   //  ~~~     controller.abort() at cleanup function!!

BAD CODE

useEffect(() => {
  const url = '/data/sample.json';
  fetch(url)
    .then((res) => res.json())
    .then((data) => {
      setSession(data);
    })
    .catch(console.error);
 
}, []);
 

⇒ SWR, React-Query 고려

GOOD CODE

useEffect(() => {
  const controller = new AbortController();
  const { signal } = controller;
  fetch(url, { signal })
    .then((res) => res.json())
    .then((data) => {
      setSession(data);
    })
    .catch(console.error);
 
  return () => controller.abort();
}, []);

useRef와 useEffect

  • 렌더링 후 DOM 제어 시, useRef는 useEffect를 필요로 함
  • componentDidMount 되기 전에 DOM 참조 안됨
  • element가 마운트되기 전에는 ref는 undefined | null
  • 자식 컴포넌트의 DOM을 useRef로 참조 시 forwardRef + useEffect 필수!
const Login = () => {

  useEffect(() => {
    console.log('Login, please!');
    userIdRef.urrent?.focus();
    return () => {
      console.log('로그인 되셨어요');
    };
  }, []);  // 단 1번의 효과!
}
{session.loginUser ? <Profile ref={logoutBtnRef} /> : <Login />}
// Profile.tsx (18.3+에서는 forwardRef 불필요!)
const Profile = forwardRef((props, ref) => {

<button ref={ref}>Logout</button>