Dandy Now!
  • [React.js] React useCallback, 함수 인수는 어떻게 동작할까? (feat. 빈 의존성 배열 `[]`)
    2025년 04월 04일 17시 30분 18초에 업로드 된 글입니다.
    작성자: DandyNow
    728x90
    반응형

    React useCallback, 함수 인수는 어떻게 동작할까?

    React 개발 중 성능 최적화를 위해 useCallback 훅을 자주 사용하게 된다. 그런데 useCallback(fn, [])처럼 빈 의존성 배열을 사용하면, 혹시 함수가 처음 호출될 때 사용된 인수가 고정되어 버리는 건 아닐까 하는 의문이 들 때가 있다. 마치 스냅샷처럼.

    그래서 이 useCallback과 함수 인수 전달의 관계에 대해 명확하게 알아고자 한다. 결론부터 말하면, 빈 의존성 배열은 인수를 고정시키지 않는다!

     

     

    1. useCallback이란?

    • 개념: 함수 자체를 기억(메모이제이션)하여, 특정 조건 하에서 함수의 재생성을 방지하는 React 훅이다.
    • 목적: 주로 자식 컴포넌트에 props로 함수를 전달하거나 useEffect의 의존성 배열에 함수를 포함할 때, 불필요한 리렌더링 또는 useEffect 재실행을 막아 성능 최적화에 기여한다.
    • 동작:
      • useCallback(fn, deps) 형태로 사용한다.
      • deps 배열 안의 의존성 값들이 변경되지 않는 한, useCallback은 이전에 생성했던 동일한 함수 인스턴스를 반환한다.
      • deps가 빈 배열 []이면, 컴포넌트가 처음 마운트될 때 단 한 번만 함수를 생성하고 이후에는 계속 그 함수를 재사용한다.

    2. 흔한 오해: "빈 배열 []이 인수를 고정시킨다?"

    • 가끔 useCallback(fn, [])을 쓰면, fn 함수가 처음 호출될 때 사용된 인수(예: fn('첫번째 인수'))가 그대로 기억되어, 나중에 fn('두번째 인수')처럼 다른 인수로 호출해도 계속 '첫번째 인수'가 사용될 거라고 생각하는 경우가 있다.
    • 하지만 이것은 잘못된 이해이다. useCallback은 함수 호출 시점의 인수를 저장하지 않는다.

    3. 실제 동작 방식과 예제 코드

    • useCallback은 함수 정의(코드 자체)를 메모리에 저장해두고 재사용하는 것이다.
    • 메모이제이션된 함수가 호출되는 시점에 어떤 인수가 전달되었는지에 따라, 함수 내부에서는 그때 전달된 인수를 사용한다.
    • 예제 코드를 통해 직접 확인해 보자!
    import React, { useState, useCallback, useEffect } from 'react';
    
    function CallbackArgDemo() {
      const [text, setText] = useState('');
    
      // useCallback으로 메모이제이션된 함수 (인수: prefix)
      // 빈 의존성 배열을 사용 -> 이 함수 인스턴스는 한 번만 생성됨
      const logMessage = useCallback((prefix) => {
        // 함수 본문에서는 호출 시점에 전달된 'prefix' 인수를 사용함
        // text 상태는 사용하지 않음 (인수 전달에 집중하기 위함)
        console.log(`[Callback] ${prefix}: 이 메시지는 useCallback 함수로부터 왔습니다.`);
      }, []); // 의존성 없음
    
      // 비교: useCallback 없이 정의된 함수
      const logMessagePlain = (prefix) => {
        // 이 함수는 리렌더링될 때마다 새로 생성됨
        console.log(`[Plain] ${prefix}: 이 메시지는 일반 함수로부터 왔습니다.`);
      };
    
      console.log("--- 컴포넌트 리렌더링 ---");
    
      // useEffect에서 logMessage 함수 참조 (참조 안정성 테스트용)
      useEffect(() => {
        console.log("logMessage 함수 참조 변경됨 (useCallback)");
        // 이 로그는 logMessage 함수 참조가 변경될 때만 출력됨
        // 빈 배열 useCallback 덕분에 마운트 시 1회만 출력될 것임
      }, [logMessage]);
    
      useEffect(() => {
        console.log("logMessagePlain 함수 참조 변경됨 (Plain)");
        // 이 로그는 리렌더링 시마다 매번 출력될 것임
      }, [logMessagePlain]);
    
    
      return (
        <div>
          <h1>useCallback 인수 전달 테스트</h1>
          <input
            type="text"
            value={text}
            onChange={(e) => setText(e.target.value)}
            placeholder="리렌더링 유발용 입력창"
          />
    
          <hr />
          <h2>useCallback 함수 호출</h2>
          {/* 동일한 logMessage 함수를 다른 인수로 호출 */}
          <button onClick={() => logMessage('INFO')}>정보 로그 (Callback)</button>
          <button onClick={() => logMessage('WARN')}>경고 로그 (Callback)</button>
    
          <hr />
          <h2>일반 함수 호출</h2>
          <button onClick={() => logMessagePlain('INFO')}>정보 로그 (Plain)</button>
          <button onClick={() => logMessagePlain('WARN')}>경고 로그 (Plain)</button>
        </div>
      );
    }
    
    export default CallbackArgDemo;

    4. 예제 코드 분석

    • CallbackArgDemo 컴포넌트는 입력(text) 상태를 가지고 있어, 입력할 때마다 리렌더링이 발생한다.
    • logMessage 함수는 useCallback(..., [])으로 정의되어, 컴포넌트가 처음 렌더링될 때 단 한 번만 생성된다. 이후 리렌더링 시에도 동일한 함수 인스턴스가 유지된다. (useEffect 로그 참조)
    • logMessagePlain 함수는 useCallback 없이 정의되어, 컴포넌트가 리렌더링될 때마다 매번 새로운 함수 인스턴스가 생성된다. (useEffect 로그 참조)
    • 핵심 포인트:
      • "정보 로그 (Callback)" 버튼과 "경고 로그 (Callback)" 버튼은 동일한 logMessage 함수 인스턴스를 호출한다.
      • 하지만 버튼을 클릭할 때 각각 다른 prefix 인수('INFO', 'WARN')를 전달한다.
      • logMessage 함수는 호출될 때 전달받은 그 prefix 인수를 사용하여 콘솔에 로그를 정확히 출력한다. 이는 useCallback이 인수를 고정하지 않음을 보여준다.

    5. 그럼 useCallback은 왜 사용할까?

    • 주된 이유는 참조 안정성(Referential Stability) 확보이다.
    • 언제 유용한가?
      • React.memo로 감싸진 자식 컴포넌트에 함수를 props로 내려줄 때: 함수 참조가 동일하게 유지되므로 자식 컴포넌트의 불필요한 리렌더링을 방지할 수 있다.
      • useEffect의 의존성 배열에 함수를 포함할 때: 함수 참조가 변경되지 않으면 useEffect가 불필요하게 재실행되는 것을 막을 수 있다. (예제 코드의 useEffect 참고)

    6. 결론

    • useCallback(fn, [])은 함수 fn정의를 메모이제이션하여 참조 안정성을 제공하는 것이지, 함수 호출 시의 인수를 저장하거나 고정하는 것이 아니다.
    • 메모이제이션된 함수도 일반 함수와 마찬가지로, 호출될 때 전달받는 인수를 사용하여 동작한다.
    • 그러니 안심하고 useCallback으로 메모이제이션된 함수에 다양한 인수를 전달하여 사용해 보자!
    728x90
    반응형
    댓글