React Hooks In Action 책 톫아보기 [2](4강,5강 부수효과 활용하기)

정소현·2025년 2월 19일

React

목록 보기
14/15

TOSS개발자
Effect가 필요하지 않을 수도 있다.

부수효과?

어떤 방식으로든 외부 세계에 영향을 미치는 동작
ex) setInterval, setTimeout , console.log, DOM 엘리먼트 너비, 높이, 위치 측정...

useEffect

리액트에서 제공하는 부수효과 훅

useEffect 훅을 호출하는 방법

  1. 두 번째 인자없음
    => 매번 렌더링이 끝난 다음에 실행됨
useEffect(()=>{})
  1. 빈 배열을 두번째 인자로 지정
    => 컴포넌트가 마운트 될 때 한번만 실행됨
useEffect(()=>{},[])
  1. 두 번째 인자로 의존 관계배열을 지정
    => 의존 관계 배열에 있는 값이 변할 때만 실행됨
useEffect(()=>{},[dep1, dep2])
  1. 함수를 반환
    => 컴포넌트가 언마운트되거나 효과가 다시 실행되기 전 정리 함수를 호출

: 메모리 누수 방지와 불필요한 이벤트 리스너 제거 등의 목적으로 사용한다.

useEffect(()=>{
return ()=> {// clean up 함수 (정리함수)
}
},[])

✔️ Cleanup 함수의 실행 타이밍

  • 컴포넌트가 언마운트 될 때
  • Dependency 배열의 값이 변경되어 Effect가 다시 실행되기전에 정리
  • 리렌더링이 발생하면:
    기존 useEffect의 cleanup 함수가 실행되어 이전 인터벌 정리
    새로운 useEffect가 실행되며 새로운 클로저 생성
    클로저 때문에 setInterval 내부에서는 count의 특정 스냅샷(캡처된 값)만 사용

예시 ) 이벤트 리스너, setTime, setInterval 웹소켓 연결

useEffect와 closer의 관계

  • clean-up 함수는 이전 useEffect 환경을 바라보고 있어(closer) 정리

useLayoutEffect

=> 브라우저가 화면을 그리기 전에 효과를 실행
: DOM이 변경된 직후, 브라우저가 화면을 그리기 전에 동기적으로 실행

⚠️ useEffect는 비동기적으로 실행되지만 useLayoutEffect는 동기적으로 실행되기 때문에
무거운 작업 수행시 UI가 멈출 수 있음..!

사용예시 : DOM크기 측정, 레이아웃 조정


useEffect내부에 async/await를 쓰지 못하는 이유

  1. useEffect의 콜백 함수는 async 함수가 될 수 없음 → React는 동기적인 cleanup을 기대
  2. async가 붙으면 useEffect 내부의 반환값이 Promise가 됨 → React에서 예상치 못한 동작 발생 가능
  3. useEffect는 클린업 함수(예: return () => {})를 반환해야 함
  4. async 함수가 반환하는 Promise는 클린업 함수로 사용할 수 없음
    React에서 경고 메시지가 뜰 수 있음

useEffect 가 개발 모드에서 2번 실행되는 이유

App컴포넌트를 React.strictMode가 감싸고 있다.
React.StrictMode는 React.Fragment와 동일하게 실제로 웹 페이지상으로는 전혀 출력되지 않는다.
React.StrictMode란 개발자들에게 해당 코드가 잠재적인 문제를 가지고 있는지 알리기 위해 개발 모드에서 사용되는 기능이다.

페이지를 오고 갈 때마다 이전 상태를 유지한 상태로 리마운트 한다면 여러번 리마운트 될 때마다 화면이 달라지지 않고 같은 화면을 표시할 수 있어야하기 때문에 문제가 생기는 부분이 있는지 확인할 수 있도록 StrictMode를 제공한다.

참고(참고URL)

useEffect만들어보기

let currentHook = 0;
let hooks = [];

// 호출 순서
const useState = (initialValue) => {
  hooks[currentHook] = hooks[currentHook] || initialValue;
  const hookIndex = currentHook;
  const setState = (newState) => {
    if (typeof newState === "function") {
      hooks[hookIndex] = newState(hooks[hookIndex]);
    } else {
      hooks[hookIndex] = newState;
    }
  };
  return [hooks[currentHook++], setState];
};

const useEffect = (callback, depArray) => {
  const hasNoDeps = !depArray;
  const prevDeps = hooks[currentHook] ? hooks[currentHook].deps : undefined;
  const prevCleanUp = hooks[currentHook]
    ? hooks[currentHook].cleanUp
    : undefined;

  const hasChangedDeps = prevDeps
    ? !depArray.every((el, i) => el === prevDeps[i])
    : true;

  if (hasNoDeps || hasChangedDeps) {
    if (prevCleanUp) prevCleanUp();
    const cleanUp = callback();
    hooks[currentHook] = { deps: depArray, cleanUp };
  }
  currentHook++;
};

const MyReact = {
  render(Component) {
    const instance = Component();
    instance.render();
    currentHook = 0;
    return instance;
  },
};

MyReact.useState = useState;
MyReact.useEffect = useEffect;

export { useState, useEffect };
export default MyReact;

useRef

useRef()가 순수 자바스크립트 객체
값을 유지하지만 상태 변경을 트리거하지 않는 훅
컴포넌트가 리렌더링 되더라도 useRef에 저장된 값은 유지되지만 값이 변경되더라도 컴포넌트가 다시 렌더링 되지 않는다.

같은 메모리 주소를 갖고있기 때문에 자바스크립트의 === 연산이 항상 true 를 반환

  1. 값을 변경해도 렌더링이 발생하지 않음
  2. 컴포넌트가 언마운트될 때까지 값이 유지
  3. DOM요소에 직접 접근 가능
  • 값변경
const countRef = useRef(0);
countRef.current += 1
  • DOM요소 접근
const inputRef = useRef(null);
inputRef.current.focus();

useRef를 사용해서 타이머 만들기

  • 컴포넌트가 리렌더링되더라도 초기화되지 않도록 관리할 수 있다.
const [count, setCount] = useState(0)
const timerRef = useRef(null)

const startTimer = ()=>{
  if(timerRef.current)return;
  timerRef.current = setInterval(()=>{
    setCount((prev)=> prev + 1)
  },1000)
}

// 언마운트 시 타이머 정리
useEffect(()=>{ 
  return () => clearInterval(timerRef.current);
},[])
profile
기술을 넘어 제품의 가치를 만드는 프론트엔드 엔지니어를 지향합니다.

0개의 댓글