☁️ goormTIL | React #27

매루·2025년 10월 17일

goormTIL

목록 보기
25/67
post-thumbnail

📅 2025-10-17

➡️ React useContext, React.memo, useMemo, useCallback에 대해 새롭게 알게 된 것 또는 헷갈리는 부분 정리 + 🤔❓


🔎 학습 리마인드

📌 useContext

필요한 이유

  • 부모 → 자식 → … 순으로 데이터를 계속 넘기면 props drilling 문제가 생김
  • 어디서 온 값인지 추적이 어려움
  • 중간 컴포넌트가 불필요하게 props를 중계
  • 변경 전파 범위가 넓어 디버깅 어려움

💡 Context API 필수 개념

  • createContext : context 생성
  • useContext : context를 구독하고 해당 context의 현재 값을 읽음
  • Provider : context를 하위 컴포넌트에게 전달

주의 사항

  • Context의 value가 바뀌면 그 값을 구독하는 모든 하위 컴포넌트가 리렌더링
  • 따라서 항상 value 부분을 신경 써줘야 함

📌 momoization

  • 렌더링 되는 상황
    • 자신의 state가 바뀔 때
    • props로 전달된 값이 바뀔 때
    • 부모 컴포넌트가 리렌더링될 때

→ 불필요한 비용이 발생할 수 있음

  • 렌더링 최적화 : 불필요한 렌더링을 막는 기술

💡 React.memo

  • 컴포넌트메모리(cache)에 저장해 두었다가 props가 바뀌지 않는다면 이전 결과 값을 재사용하게 해주는 함수
  • 예제
    import { useState } from "react";
    import Box1 from "./components/Box1";
    import Box2 from "./components/Box2";
    import Box3 from "./components/Box3";
    
    function App() {
      console.log("App 렌더링!");
    
      const [count, setCount] = useState(0);
    
      return (
        <div>
          <h3>카운터 예제</h3>
          <p>현재 카운트: {count}</p>
          <button onClick={() => setCount(count + 1)}>+</button>
          <button onClick={() => setCount(count - 1)}>-</button>
    
          <div style={{ display: "flex", marginTop: 10 }}>
            <Box1 />
            <Box2 />
            <Box3 />
          </div>
        </div>
      );
    }
    
    export default App;
    function Box1() {
      console.log("Box1 렌더링!");
      return (
        <div style={{
          width: 100,
          height: 100,
          background: "#91c49f",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          color: "#fff"
        }}>
          Box1
        </div>
      );
    }
    
    export default Box1;
    ❗ 지금 상태에서 + 버튼을 클릭하면 App의 state(count)만 바뀌었는데 box1~3 모두 리렌더링
    • memo 적용
      import React from "react";
      function Box1() {
        console.log("Box1 렌더링!");
        return <div>Box1</div>;
      }
      export default React.memo(Box1);
      • 이러면 버튼을 눌러도 콘솔에는 App 렌더링만 출력되고 자식 컴포넌트 박스들은 다시 렌더링되지 않음

💡 useMemo

  • 값을 기억하기 위한 훅
  • 함수를 매번 실행하지 않고, 이전 계산 결과를 캐싱해두었다가 재사용
  • 복잡한 계산 결과값을 Memoization할 때 많이 사용

사용 방법

const result = useMemo(() => {
  return someHeavyCalculation();
}, [dependency]);
  • 의존성 배열의 값이 바뀌지 않는 한, someHeavyCalculation()은 다시 실행되지 않음

  • 예제

    import "./App.css";
    import HeavyComponent from "./components/HeavyComponent";
    
    function App() {
      const navStyleObj = {
        backgroundColor: "yellow",
        marginBottom: "30px",
      };
    
      const footerStyleObj = {
        backgroundColor: "green",
        marginTop: "30px",
      };
    
      return (
        <>
          <nav style={navStyleObj}>네비게이션 바</nav>
          <HeavyComponent />
          <footer style={footerStyleObj}>푸터 영역이에요</footer>
        </>
      );
    }
    
    export default App;
    import React, { useState, useMemo } from "react";
    
    function HeavyButton() {
      const [count, setCount] = useState(0);
    
      const heavyWork = () => {
        for (let i = 0; i < 1000000000; i++) {}
        return 100;
      };
    
    	// useMemo를 사용하지 않았을 때
      const value = heavyWork();
    
      return (
        <>
          <p>나는 {value}을 가져오는 무거운 작업을 하는 컴포넌트</p>
          <button
            onClick={() => {
              setCount(count + 1);
            }}
          >
             누르면 아래 count가 증가해요
          </button>
          <br />
          {count}
        </>
      );
    }
    
    export default HeavyButton;

    ❗버튼을 클릭할 때마다 heavyWork 연산을 하기 때문에 +1이 오래 걸림

    • useMemo 사용
      import React, { useState, useMemo } from "react";
      
      function HeavyButton() {
        const [count, setCount] = useState(0);
      
        const heavyWork = () => {
          for (let i = 0; i < 1000000000; i++) {}
          return 100;
        };
      
      	// useMemo를 사용하지 않았을 때
        //const value = heavyWork();
        
      	// useMemo를 사용했을 때
        const value = useMemo(() => heavyWork(), []);
      
        return (
          <>
            <p>나는 {value}을 가져오는 무거운 작업을 하는 컴포넌트</p>
            <button
              onClick={() => {
                setCount(count + 1);
              }}
            >
              누르면 아래 count가 증가해요
            </button>
            <br />
            {count}
          </>
        );
      }
      
      export default HeavyButton;
      • 이러면 처음 렌더링 될 때만 오래 걸리고, 이후 버튼을 클릭할 때 바로 +1이 됨

주의 사항

  • 모든 계산에 useMemo를 붙이는건 오히려 비효율적
    • 캐싱을 할때도 메모리와 비교 비용이 들기 때문
  • 정말 계산이 비용이 비싸거나 참조 비교로 인해 불필요하게 렌더링이 자주 발생하는 경우만 사용

💡 useCallback

  • React.memo가 컴포넌트 자체를 기억한다면, useCallback함수(콜백)를 기억하도록 도와줌

  • 리렌더링이 발생할 때마다 함수가 새로 만들어지면 불필요한 렌더링이 전파될 수 있기 때문에 함수를 메모리에 저장해 재사용하는 것이 핵심

  • 예제

    function App() {
      const [count, setCount] = useState(0);
    
      // count를 초기화하는 함수
      const reset = () => setCount(0);
    
      return (
        <>
          <p>현재 카운트: {count}</p>
          <button onClick={() => setCount((n) => n + 1)}>+</button>
          <button onClick={() => setCount((n) => n - 1)}>-</button>
          <Box1 reset={reset} />
        </>
      );
    }

    Box1 내부에서는 단순히 이 함수를 버튼 클릭 시 실행

    function Box1({ reset }) {
      console.log("Box1 렌더링");
      return <button onClick={reset}>초기화</button>;
    }

    App의 count가 바뀔 때마다 reset 함수도 새로 만들어지기 때문에, Box1은 React.memo로 감싸더라도 계속 리렌더링

    → 자바스크립트에서 함수도 객체의 한 종류이기 때문. 그래서 내용이 같아도 새로 만들어지면 다른 객체로 인식하기 때문에 props 변경으로 간주

    • useCallback 사용
      const reset = useCallback(() => {
        setCount(0);
      }, []);
      • 의존성 배열([])이 비어있다면, 컴포넌트가 다시 렌더링되어도 reset 함수는 처음 만든 것 그대로 유지됨
      • 이제 Box1은 React.memo를 썼을 때 실제로 리렌더링되지 않음
  • 현재 값을 기준으로 로직이 달라지는 경우

    const reset = useCallback(() => {
      console.log(`${count} → 0 으로 초기화`);
      setCount(0);
    }, [count]);`

🌊 Deep Dive

🤔 React.memo, useMemo, useCallback 비교

Hook / 함수역할언제 쓰는지기억해야 할 핵심 포인트
React.memo컴포넌트를 메모이제이션부모가 리렌더링돼도 props가 동일하면 자식 컴포넌트를 다시 렌더링하지 않음컴포넌트 단위의 렌더링 최적화
useMemo값(결과)를 메모이제이션계산 비용이 큰 연산 결과를 재사용하고 싶을 때의존 배열이 변하지 않으면 이전 계산 결과를 반환
useCallback함수를 메모이제이션자식 컴포넌트에 props로 함수를 넘길 때, 매번 새로운 함수가 만들어지는 걸 방지useMemo함수 버전

💡 각각 언제 주로 사용할까?

React.Memo

  • 같은 props로 자주 렌더링되는 컴포넌트
  • 불필요한 리렌더링이 성능에 영향을 줄 때
  • 예: 같은 props로 자주 렌더링되는 버튼, 카드 목록 등

useMemo

  • 렌더링마다 다시 계산할 필요 없는 비용 큰 연산
  • 예: 정렬, 필터링, 복잡한 계산, 데이터 변환 등

useCallback

  • 자식 컴포넌트에 콜백 함수를 props로 전달할 때
  • React.memo로 감싼 자식이 불필요하게 리렌더링되는 것을 막고 싶을 때
  • 예: onClick, onChange, 이벤트 핸들러 등

🔗 https://ko.react.dev/reference/react/memo

🔗 https://ko.react.dev/reference/react/useMemo

🔗 https://ko.react.dev/reference/react/useCallback


0개의 댓글