카테고리 없음

react 리렌더링

tp134679 2025. 4. 14. 14:56

<목차>

1. 함수형 컴포넌트의 리렌더링

2. memo 와 usemamo,usecallback 

3. React 의 모토 

 

 

1. 함수형 컴포넌트의 리렌더링

함수형컴포넌트는 함수형으로 만들어진 컴포넌트 이기 때문에 함수내부에서 평가와 실행이 실시됩니다.

 

React에서 함수형 컴포넌트는 상태(state)나 props가 변경될 때마다 다시 실행(리렌더링) 됩니다. 이는 React의 핵심적인 동작 방식으로, UI를 항상 최신 상태로 유지하기 위함입니다.

 

리렌더링이 발생하는 주요 원인은 다음과 같습니다:

  1. State 변경 - useState로 관리하는 값이 변하면 해당 컴포넌트가 다시 실행됩니다.
  2. Props 변경 - 부모 컴포넌트에서 받은 props 값이 변경되면 해당 컴포넌트가 리렌더링됩니다.
  3. 부모 컴포넌트 리렌더링 - 부모가 리렌더링되면, 자식 컴포넌트도 같이 리렌더링됩니다.
  4. Context 값 변경 - useContext를 사용하고 있다면, context 값이 변경될 때마다 리렌더링됩니다.
import { memo, useMemo, useCallback, useState } from "react"

const Child = () => {
    console.log("나는 Child! 호출될까?");
   
    return (
        <button>진짜?</button>
    )
}

export const Counter = () => {
    const [count, setCount] = useState(0);

    return (
        <>
            {count}
            <button onClick={() => setCount(count + 1)}>+</button>
            <Child/>
        </>
    )
}

 

해당 코드가 실행이 되고 count 가 바뀌었을때 Child 도 호출이 됩니다. 

 

이유는 함수형 컴포넌트 이므로 count 가 변하면 Counter 컴포넌트를 리렌더링 하게 되는데 그안에 Child 컴포넌트 또한 함수형 이기에 평가와 실행을 거쳐서 log가 찍히는 것을 확인 할 수 있습니다. 그러나 이러한 호출은 성능적으로 비효율을 가집니다. 그래서 React는 효율적인 업데이트를 위해 "가상 DOM(Virtual DOM)"을 사용하지만, 불필요한 렌더링이 많아지면 성능이 저하될 수 있습니다. 이를 방지하기 위해 memoization 기법이 활용됩니다.

 

 

 

2. memo 와 usemamo,usecallback 

 

리액트는 상태나 프롭스가 변경되면 해당 컴포넌트(Counter)와 하위 컴포넌트(Child)를 다시 렌더링하는 방식이기 때문에 

React에서는 불필요한 렌더링을 방지하기 위해 react 16.6 (2018년)

memoization  기법을 만들어서 사용하게 되었고  

memo, useMemo, useCallback등의 기능을 만들었습니다.

 

 

memo

 

memo는 컴포넌트의 리렌더링을 방지합니다. 

 

1. memo는 이전 렌더링의 프롭을 기억(메모이제이션)합니다.
2. 새로운 렌더링이 요청되면 이전 프롭과 현재 프롭을 비교합니다..
3. 프롭이 변하지 않았다면, 기존 결과`값`를 재사용하고 렌더링을 생략합니다.

 

import { memo, useMemo, useCallback, useState } from "react"

const Child = memo(() => {
    console.log("나는 Child! 호출될까?");
   
    return (
        <button>진짜?</button>
    )
})

export const Counter = () => {
    const [count, setCount] = useState(0);

    return (
        <>
            {count}
            <button onClick={() => setCount(count + 1)}>+</button>
            <Child/>
        </>
    )
}

기존에 있던 child 에 memo()를 씌워서 사용 

함수의 평가시 처음 한번만 실행되고 button의 state 값이 아무리 변해도 리렌더링이 일어나지 않음

 

즉 불필요한 렌더링을 방지

 

useMemo

 

useMemo 는 연산의 결과를 캐싱합니다.

 

   1. useMemo(, [])
   2. useMemo(() => {}, [값이변하면 useMemo 실행])

 

 

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

const heavyCalculation = (num) => {
    console.log("무거운 연산 수행...");
    return num * 2;
};

const MemoExample = () => {
    const [count, setCount] = useState(0);
    const [text, setText] = useState("");

    const memoizedValue = useMemo(() => heavyCalculation(count), [count]);

    return (
        <div>
            <button onClick={() => setCount(count + 1)}>Count 증가</button>
            <input
                type="text"
                value={text}
                onChange={(e) => setText(e.target.value)}
            />
            <div>결과값: {memoizedValue}</div>
        </div>
    );
};

export default MemoExample;

해당 함수 실행시 count 가 변경될때만 heavyCalculation 이 실행되고 text가 변경되면 useMemo 로 감싼 memoizedValue가 실행되지 않습니다.

import { memo, useMemo, useCallback, useState } from "react"

const Child = memo((props) => {
    console.log("나는 Child! 호출될까?");
    console.log(props.parentCount);
   
    return (
        <>
            {props.parentCount}
            <button>진짜?</button>
        </>
       
    )
})

export const CounterUseMemo2 = () => {
    const [count, setCount] = useState(0);

    const number = useMemo(() => {
        console.log("나는 비싼 연산");
        let number = 0;
        for(let i = 1; i <= 10000000; i++) {
            number = number + i;
        }
        // 500000000067109000
        console.log(number);
        return number + count;
    }, [])
    // 500000000067109001
    console.log(number);
   
    return (
        <>
            {count}
            <button onClick={() => setCount(count + 1)}>+</button>
            <Child parentCount={count} />
        </>
    )
}

해당 코드는 처음 한번만 랜더링 되고 그뒤로 카운트가 증가해도 counteruseMemo는 실행되지않습니다. [] 처음의 빈값을 넣어놨기 때문에 count 값이 변해도 처음 계산한 값만 렌더링  

 

useCallback

 

useCallback은 특정 함수를 메모이제이션하여 불필요한 함수 재생성을 방지합니다. 주로 React.memo와 함께 사용하여 함수가 변경되지 않도록 최적화하는 데 활용됩니다.

 

    import React, { useState, useCallback, memo } from "react";

    const Child = memo(({ handleClick }) => {
        console.log("Child 컴포넌트 렌더링!");
        return <button onClick={handleClick}>증가</button>;
    });
   
    const CallbackExample = () => {
        const [count, setCount] = useState(0);
   
        const increment = useCallback(() => {
            setCount((prev) => prev + 1);
        }, []);
   
        return (
            <div>
                <p>Count: {count}</p>
                <Child handleClick={increment} />
            </div>
        );
    };
   
    export default CallbackExample;

위 코드에서 increment 함수는 useCallback을 사용하여 메모이제이션되었기 때문에 Child 컴포넌트가 불필요하게 리렌더링되지 않습니다.

3. React 의 모토 

 

이를 기반으로 React는 다음과 같은 원칙을 따릅니다

선언형(Declarative): UI를 선언적으로 기술하여 코드의 가독성과 유지보수성을 높입니다

 상태(State)가 바뀌면, React가 UI를 다시 그려준다는 선언적(Declarative)방식

 

컴포넌트 기반(Component-Based): 독립적인 재사용 가능한 컴포넌트 단위로 UI를 구성한다.

 

일관된 상태 관리(State Management): 컴포넌트의 상태를 효율적으로 관리하여 UI를 최신 상태로 유지한다.

 

가상 DOM(Virtual DOM) 활용: 변경된 부분만 업데이트하여 성능을 최적화한다.

 

단방향 데이터 흐름(One-way Data Flow): 데이터 흐름을 명확하게 하여 디버깅을 쉽게 만든다.

이러한 원칙 덕분에 React는 웹, 모바일, 데스크톱 등 다양한 환경에서 사용될 수 있으며, 성능 최적화를 통해 빠르고 효율적인 애플리케이션 개발이 가능하다.

 

필요할 때만 최적화 Hooks 사용

React의 기본 철학은 **"상태가 바뀌면 재렌더링"**이지만, 불필요한 렌더링을 막아야 할 때 memo, useMemo, useCallback 같은 **최적화** 도구를 제공합니다.

- 렌더링은 React가 책임지고, 개발자는 어떤 상황에서 최적화를 할지" 결정

React.memo 자식 컴포넌트가 **불필요하게 리렌더링**되지 않도록 캐싱

useMemo값(연산 결과)**를 캐싱해 **비싼 연산 재실행 방지**

 

정리

 

즉, React는

 

1. Declarative UI로 UI 코드를 단순화하고,

2. Virtual DOM으로 DOM 조작을 효율적으로 처리하며,

3. Hooks를 통해 상태와 로직을 유연하게 관리하고,

4. memo/useMemo/useCallback으로 필요할 때만 최적화하는 모토를 갖고 있습니다.

 

"데이터가 바뀌면 UI도 알아서 바뀌지만, 성능 최적화도 React가 편리한 방법들을 제공해준다!" – 이것이 React가 궁극적으로 지향하는 방향이라 볼 수 있습니다.