ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 리액트 성능 개선에 대해
    프론트엔드 2025. 8. 18. 21:34

    리액트 성능개선

    - 리액트 에서 기본적으로 성능 개선을 할 때, useMemo, useCallback, React.memo를 종종 사용한다. 

    - 메모이제이션을 하는게 성능개선에 무조건적으로 좋다고 생각하는 나의 경우 무조건 사용하는게 좋다고 생각했었다. 🤔

    - 무조건적으로 좋을까? 과연?? 한번 알아보자 

     

    리액트에서 리 렌더링 

    - 리액트에서 리 렌더링은 매우 중요하다. 

    - 리액트는 리 렌더링 작업을 통해 새 값을 읽고 UI를 만든다. -> 이 리 렌더링을 통해 변화된 값이 반영이 된다. 

    - 리 렌더링을 일으킨다고 해서 무조건 적으로 안좋은 것은 아니다.!! -> 불 필요한 리 렌더링을 방지하는것이 중요하다고 한다. 

     

    문제 

    - 리액트에서 메모이제이션이 왜 필요할까? 

    - 자바스크립트에서 원시값(문자열, 숫자, 불리언)은 실제 값으로 비교가 가능하지만, 객체의 경우는 다르다. 

    - 객체는 참조로 비교가 된다. 

    const data = {
    a: 1,
    };

    const b = data;
    console.log(b === data);

    -> 결과값은 뭐가 나올까 정답은 false가 나온다. b와 data가 서로 다른 참조값(메모리 주소)을 보고 있기 때문이다. 

    - 위 문제가 리액트에서 까지 문제가 되는데,

       1. 컴포넌트는 자신의 상태가 변경되거나, 부모 컴포넌트가 리 렌더링 될 때, 다시 렌더링 된다.

       2. 컴포넌트가 리 렌더링이 진행되면, 모든 지역변수(객체 함수 포함)는 새로운 참조로 다시 생성이 된다. 

       3. 이 새로운 참조가 의존성 배열이나 프로퍼티로 전달되면, 불필요한 리렌더링이나 이펙트 실행이 발생할 수 있다.

     

    문제해결

    - 위 문제를 해결하기 위해 메모이제이션 훅이 있다. useMemo, useCallback이다. 

    - 이 두가지 훅은 참조의 안정을 유지하기 위해 존재한다고 한다. 위 훅은 값을 캐싱하고, 지정한 의존성이 변경될 때만 다시 계산한다. 

    const memoizedFn = useCallback(() => {
    doSomething();
    }, [deps]);

    - useCallback 부터 확인하면, 함수 자체를 캐싱하는 훅이다. 

    - doSomething 함수가 매번 새로 만들어지는 것을 방지할 뿐만 아니라, 참조가 유지된다.

    - useCallback은 주로 자식 컴포넌트에 콜백 props를 넘길때 자주 사용한다고 한다. 

     

    import React, { useState } from "react";

    const Child = React.memo(({ onClick }) => {
    console.log("✅ Child 렌더링됨");
    return <button onClick={onClick}>클릭</button>;
    });

    export default function Parent() {
    const [count, setCount] = useState(0);

    // 🔴 매번 새로운 함수 객체가 만들어짐
    const handleClick = () => {
    console.log("Clicked!");
    };

    return (
    <div>
    <p>카운트: {count}</p>
    <button onClick={() => setCount(count + 1)}>부모 count 증가</button>
    <Child onClick={handleClick} />
    </div>
    );
    }

    - 예시를 보면 Child 컴포넌트를 React.memo를 사용해 리 렌더링을 방지하고자 했지만 부모 컴포넌트가 state가 변경되면서, 함수 참조달라졌다고 인식해 자식도 강제로 리 렌더링이 된다. 


    const handleClick = useCallback(() => {
    console.log('Clicked!');
    }, []);

    -  위 처럼 useCallback을 사용해 전달하면 함수가 같은 참조를 유지하니, 리 렌더링이 발생하지 않는다. 

     

    - useMemo는 비싼 계산을 매번 새로 하지 않고, 의존성 배열이 변화하지 않으면 이전 계산 결과를 재사용한다.

    import React, { useState, useMemo } from "react";

    export default function Example() {
    const [count, setCount] = useState(0);
    const [other, setOther] = useState(0);

    // 🔴 매번 렌더링마다 무거운 계산 수행
    const expensiveCalculation = (num) => {
    console.log("💡 무거운 계산 실행 중...");
    for (let i = 0; i < 1000000000; i++) {} // 시간 잡아먹는 연산
    return num * 2;
    };

    // ✅ useMemo로 최적화
    const doubled = useMemo(() => expensiveCalculation(count), [count]);

    return (
    <div>
    <p>count: {count}</p>
    <p>other: {other}</p>
    <p>doubled: {doubled}</p>
    <button onClick={() => setCount(count + 1)}>count 증가</button>
    <button onClick={() => setOther(other + 1)}>other 증가</button>
    </div>
    );
    }

    - 만약 무거운 계산을 하고 있다면, 메모로 최적화가 가능한데, count의 값이 변경되면, 그 때 리 렌더링이 발생한다. 

    useMemo vs React.Memo

    - useMemo와 React.Memo의 차이는 useMemo는 의존성 배열의 값이 변화하지 않으면, 캐싱된 값을 반환하고

    - React.Memo는 props가 바뀌지 않으면, 컴포넌트 리 렌더링을 스킵한다. 

     

    • React.memo = 컴포넌트 리렌더링 여부를 props 비교로 제어
    • useMemo = 값 계산/객체 참조를 캐싱해서 deps 안 바뀌면 재계산 방지

     

    React.Memo 함정

    - React.memo의 함정이 있다. 

    1. 스프레드 연산자 

    const Child = React.memo(({ data }) => {
    // 컴포넌트 구현
    });

    // 프로퍼티가 변경되기 때문에 메모이제이션이 깨짐
    const Parent = props => {
    return <Child {...props} />;
    };

    - 위 처럼 스프레드 연산자를 사용해 프로퍼티를 전달하면 프로퍼티 변경으로 인해 메모이제이션이 깨진다. 

     

    2. children 프로퍼티 


    const MemoComponent = React.memo(({ children }) => {
    // 컴포넌트 구현
    });

    const Parent = () => {
    // 이것은 메모이제이션을 깨뜨립니다! children은 매 렌더마다 새로 생성
    return (
    <MemoComponent>
    <div>Some content</div>
    </MemoComponent>
    );
    };

    - 위 처럼 자식 컴포넌트를 Memo로 사용해도 부모 컴포넌트가 렌더링 되면 자식도 무조건 리 렌더링이 발생한다는 특징이 있기 때문에 결국 리 렌더링이 발생한다.!!

     

    const Parent = () => {
    const content = useMemo(() => <div>Some content</div>, []);

    return <MemoComponent>{content}</MemoComponent>;
    };

    - 그래서 children 자체를 메모이제이션을 해야한다!!

     

    정리

    - 최적화 시에는 각자 어떤 역할을 하는지 알고 수정하는 것이 필요하다 

    '프론트엔드' 카테고리의 다른 글

    자바스크립트 딥다이브 1일차  (1) 2025.09.07
    Next.js SEO 최적화를 해보자  (3) 2025.08.30
    자바스크립트 함수  (2) 2025.08.18
    Prisma에 대해  (2) 2025.08.15
    package.json + package.lock.json  (3) 2025.08.15
Designed by Tistory.