[React] React hook

Im-possible·2025년 6월 16일

Hook

  • 컴포넌트의 최상위 수준이나 커스텀 훅 내부에서만 사용 가능 (조건문, 반복문, 일반 함수 블럭 {} 내부에서 사용 불가능)

useState

  • 컴포넌트에 state 변수를 추가할 수 있는 리액트 훅.

useState()

  • 상태값(컴포넌트에서 관리하는 데이터)를 추가하기 위한 훅
  • 컴포넌트가 렌더링 되는 동안에만 사용할 수 있는 특별한 함수
  • useState가 호출되는 순서대로 배열에 순서대로 저장되므로 리렌더링 될때에도 순서가 정확히 지켜져야 한다.
  • state로 만든 변수는 컴포넌트가 여러번 사용돼도 각각의 값을 따로 관리
const [state, setState] = useState(initialState);

매개변수

  • initialState: 상태값의 초기값(초기 렌더링에 사용되고 리렌더링때는 무시됨)
    리턴값
  • state: 저장된 상태값
  • setState: 상태값을 변경하는 setter함수. setter를 통해 상태가 변경되면 해당 컴포넌트는 다시 렌더링됨
const [count, setCount] = useState(0);

const handleChange = () => {
  // setter 함수를 이용하여 상태값 변경
  setCount(count + 1);
}

useEffect

  • 컴포넌트 생명주기 이벤트를 등록하기 위한 훅
  • 컴포넌트가 렌더링 이후에 수행해야 할 일을 부여하는 훅 -> 렌더링 이후(DOM 업데이트 수행 후) 매번 수행
  • 컴포넌트가 리턴이 된 후(화면 렌더링 이후) 실행되는 것과 같은 효과
useEfect(setup, dependencies?);

매개변수

  • setup: Effect 로직이 포함된 함수
    - cleanup: setup 함수가 리턴한 함수로 컴포넌트가 업데이트 되거나(setup 함수 재실행될 때) 언마운트 될 때 호출
    setup 함수를 사용하면 렌더링이 될 때 이전에 남은 함수가 실행되기 때문에 이런 메모리 누수를 막기 위해 cleanup 함수 사용
  • dependencies (선택사항): setup 함수 코드 내부에서 참조되는 모든 반응형 값들이 포함된 배열 -> 검사하고자 하는 값으로, 해당 배열 속 하나라도 수정되었을 경우 setup 함수 호출됨
    - 빈 배열 \[] 을 지정하면 마운트 된 후 setup 함수 한번만 호출
    • dependencies를 지정하지 않으면 컴포넌트가 업데이트 될 때 항상 setup 함수 호출

리턴값

  • undefined
useEffect(() => {
  // setup 함수 호출
  const timer = setInterval(() => {
    ...
  }, 1000);
  
  // cleanup 함수 호출
  return () => {
    clearInterval(timer)
  };
});

useReducer

  • useState와 같은 상태 업데이트 훅으로, 컴포넌트 외부에서 상태관리를 하고 싶을 때 사용
  • reducer 함수는 순수 함수여야한다.
const [state, dispatch] = useReducer(reducer, initialArg, init?);

매개변수

  • initialArg: state에 전달 할 초기 값
  • init(선택사항): 초기 state를 반환하는 초기화 함수
    - 할당되었을 경우 초기 state는 initialArg를 인자로 받아 호출한 결과가 할당
    - 할당되지 않았을 경우 초기 state는 initialArg로 설정
    반환값
  • 두개의 엘리먼트로 구성된 배열 반환
    1. state: 상태값이 저장된 getter -> 컴포넌트에서 사용될 상태
    2. dispatch: 상태값을 변경하는 setter 함수로, dispatch에 전달된 인자값이 reducer의 두번째 인자값 (action)으로 전달됨
function reducer(state, action){...}

매개변수

  • reducer: stateaction을 인자로 받아 새로운 state를 반환하는 함수
    - state: 리듀서에 전달되는 상태값
    • action: dispatch에 전달한 인자값으로 수행할 작업의 종류와 필요한 인자값을 포함한 동작을 정의한 객체*
      일반적으로 type 속성에 동작, value 속성에 값을 지정
interface CounterAction {
  type: 'UP' | 'DOWN' | 'RESET';
  value: number;
}

// counterReducer 함수 정의
// counterReducer 함수가 현재 상태(state), action을 받아와서
// action의 type에 따라 상태를 변경하고 새로운 상태를 반환
function counterReducer(state: number, action: CounterAction): number {
  let newState = state;
  
  ...
  // action으로 전달 받은 동작(type: UP, DOWN, RESET)과 값(value)으로 state를 변경
  ...
  
  // 변경된 state 반환
  return newState
}

function App(){
  // useReducer를 사용하여 상태 관리
  // counterReducer 함수에 initCount를 전달하고
  // useReducer은 현재 상태(count)와 상태를 변경하는 함수(countDispatch)를 반환하여 상태 관리
  const [count, countDispatch] = useReducer(counterReducer, initCount);
  ...
  
  return (
    // 버튼 컴포넌트에서 countDispatch 함수 사용
    <Button onClick{ () => countDicpatch({type: 'UP', value: initCount}) }>UP</Button>
}

useRef

  • 컴포넌트가 다시 렌더링되어도 기존 상태값을 유지하는 변수 생성
  • 함수 내부에 정의하는 지역변수는 컴포넌트가 리렌더링 되면 값이 초기화되는데, 이를 막기 위해 사용
const ref = useRef(initialValue);

매개변수

  • initialValue: 초기값
    리턴값
  • {current: initialValue} 객체 반환
    -ref.current 형식으로 사용
// useRef 훅을 사용하여 리렌더링 되어도 값이 유지되는 stepRef를 생성하여 저장
const stepRef = useRef(initCount);

function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
  const newStep = Number(e.target.value);
  // stepRef의 current에 새로운 값 지정
  stepRef.current = newStep
}

return (
  <input defaultValue={ stepRef.current } onChange={ handleChange } />
  }
  • JSX 태그에 ref 속성을 추가하면 브라우저 DOM 엘리먼트에 직접 접근 가능
const refElem = useRef<DOM Type>(null);

매개변수

  • null: 초기값
    리턴값
  • DOM 요소가 있는 속성 하나가 정의된 객체
// useRef 훅을 사용하여 초기값이 null인 ref 객체 선언
const stepElem = useRef<HTMLInputElement>(null);

function handleChange() {
  // input에 접근에 focus()와 같은 메서드 호출
  stepElem.current?.focus();
}

// ref 속성으로 조작하려는 DOM 노드의 JSX에 전달
return (
  <input ref={ stepElem } ... />
)

useState useRef 차이점

  • useState: 리액트에서 직접 상태 관리하는 제어 컴포넌트
    -> 값이 변경되면 즉시 리렌더링
  • useRef: 브라우저에서 입력값을 관리하는 비제어 컴포넌트
    -> 값이 변경되어도 리렌더링이 필요 없을 때 사용

useMemo

  • 지정한 함수를 호출하여 반환받은 결과값을 내부에 저장(캐싱)하는 함수
  • 마지막으로 렌더링된 결과를 메모이제이션한다.
const calculateValue = function(){..};
const cachedValue = useMemo(calculateValue, [dependencies]);

매개변수

  • calculateValue: 캐싱할 값을 계산하여 반환하는 순수 함수
  • dependencies: 의존 객체 배열
    - calculateValue 코드 내에서 참조된 모든 반응형 값들의 목록(props, state, 컴포넌트 바디에 직접 선언된 모든 변수와 함수 포함)
    - 배열의 값 중 하나라도 변경되면 calculateValue 함수 재호출
    - 하나도 변경되지 않으면 캐시된 값 반환 -> 재사용
    - 빈 배열 지정시 매번 캐시된 값을 반환
    리턴값
  • calculateValue 함수를 호출한 결과값
    다음 렌더링중에 dependencies가 변경되지 않으면 캐시된 결과 반환
    변경되었으면 calculateValue 함수를 재호출한 결과값
const isPrime = function(num: number){
  ...
  
  return prime;
};

// 리렌더링 되었을 때 이전 num값과 비교하여 num이 바뀔 경우에만 isPrime 함수를 재호출
// 리렌더링 되었을 때 이전 num값과 비교하여 num이 바뀌지 않을 경우 메모이제이션 된 값 반환
const prime = useMemo(() => isPrime(num), [num])

React.memo

  • 컴포넌트를 메모이제이션 해준다
  • 컴포넌트를 memoize한 후 리렌더링 될 때 props가 변경되지 않으면 memoize된 컴포넌트 반환
  • 리렌더링 될 때 눈에 띄게 지연이 발생하는 경우 사용
  • 리렌더링 될 때 props가 자주 변경되지 않는 컴포넌트에 사용
const MemoizedComponent = React.memo(SomeComponent, arePropsEqual?)

매개변수

  • SomeConponent: memoize할 컴포넌트
  • arePropsEqual?: memoize된 컴포넌트를 반환할지, 컴포넌트를 다시 호출할지 결정하는 함수
    - 컴포넌트의 이전 props 및 새로운 props를 인자로 받는 함수
    - true: memoize된 컴포넌트 사용
    - false: 컴포넌트를 다시 호출한 결과값 아요
    - 생략 시 이전 props와 새로운 props를 얕은 비교하여 같으면 true, 다르면 false를 반환
    리턴값
// 컴포넌트 export할 때 사용
export default React.memo(Component);
  • 부모가 정의한 이벤트 리스터를 자식에게 props로 전달할 때 부모가 리렌더링 되는 경우 자식도 리렌더링 되지만, 이때 props가 바뀌지 않으면 자식은 기존 DOM을 재사용하도록 메모이제이션 할 수 있음
    - 이벤트 리스터를 컴포넌트 내부에서 정의하면 부모가 리렌더링될 때 리스너 함수도 새로 생성되므로 자식에 전달하는 props가 바뀌어 메모이제이션 되지 않고 자식도 리렌더링 발생
    • useCallback을 사용하면 부모 컴포넌트가 재호출되어도 리스너가 수정되지 않고 유지되므로 자식도 기존 DOM을 재사용하여 성능이 향상됨

useCallback

  • 컴포넌트 내부에서 정의한 함수를 캐시
  • 컴포넌트가 다시 렌더링되어도 함수가 다시 생성되지 않고 캐시된 함수 사용
  • useCallback을 사용하면 부모 컴포넌트가 재호출되어도 리스너가 수정되지 않고 유지되므로 자식도 기존 DOM을 재사용하여 성능이 향상됨
const cachedFn = useCallback(fn, dependencies);

매개변수

  • fn: 캐싱할 함수
  • dependencies: 의존 객체 배열
    - fn내에서 참조되는 모든 반응형 값(proprs, state, 컴포넌트 안에서 직접 선언된 모든 변수와 함수)
    - dependencies의 값이 바뀌면 새로운 함수를 생성하여 반환
    - 빈 배열 지정시 매번 캐시된 함수를 반환
    리턴값
  • 최초에는 fn 함수 반환
  • 다음 렌더링부터는 dependencies가 변하지 않았다면 이전에 캐시된 함수 반환
  • dependencies가 변했다면 새로 생성된 fn함수 반환
const handlePayment = useCallback(() => {
  alert(`배송비 ${shippingFees}원이 추가됩니다. 상품을 결제하시겠습니까?`);
}, [shippingFees]);

useMemo vs. React.memo vs. useCallback

useMemo

함수를 인자로 전달하고, 전달된 함수의 실행 결과(리턴값)를 memoize

React.memo

컴포넌트를 인자로 전달하고, 전달된 컴포넌트를 memoize

useCallback

함수를 인자로 전달하고, 전달된 함수를 memoize

차이점: 함수의 리턴 값 vs. 컴포넌트 vs. 함수 자체

리액트 컴파일러

  • 자동 메모이제이션
    useMemo, useCallback, React.memo를 사용하지 않아도 최적화된 코드 생성해줌
  • 정밀한 의존성 추척
    객체 전체가 아닌 실제 사용되는 속성만 추적
  • JSX 레벨 최적화
    JSX 요소들을 개별적으로 캐싱

설치

npm i -D babel-plugin-react-compiler@rc

Vite 설정 - vite.config.ts

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: [
          ["babel-plugin-react-compiler", {
            // React 17, 18 사용 시
            runtimeModule: "react-compiler-runtime"
          }]
        ]
      }
    })
  ],
  // ...
});

use no memo

  • 메모이제이션을 사용하고 싶지 않을 경우 명시
function Product({ ... }) {
  "use noe memo";
  ...
}

ESLint

  • 리액트 컴파일러를 사용하기 위해서는 리액트 규칙을 준수해야한다
  • 따라서 ESLint 규칙을 추가해서 사용

설치

npm install -D eslint-plugin-react-hooks@^6.0.0-rc.1

Eslint 설정 - esline.config.js

// eslint.config.js
import * as reactHooks from 'eslint-plugin-react-hooks';
// ...

export default tseslint.config(
  // ...
  {
    // ...
    plugins: {
      'react-hooks': reactHooks,
      // ...
    },
    rules: {
      ...reactHooks.configs.recommended.rules,
      // ...
    },
  },
)

0개의 댓글