[React] 최적화 함수

박하늘·2025년 2월 27일

React

목록 보기
13/15
post-thumbnail

React 최적화 함수

React에서 불필요한 렌더링을 방지하고 성능을 최적화하는 방법
: useMemo, useCallback, memo




1️⃣ useMemo()

  • 호출 결과 저장
  • 연산 비용이 큰 계산 결과(값)를 저장하여, 의존성 값이 변경될 때만 재계산

✔️ 기본 사용 코드

전체 컴포넌트가 호출 되었을 때 ...

  • 콜백 함수 : 메모이제이션 해줄 값을 받아서 리턴해줌. 이 리턴 값이 저장됨
  • 배열 : 의존성 배열, 이 요소의 값이 업데이트 될 때만 함수 호출 하여 리렌더링 후 재메모이제이션
const 저장할_호출결과명 = useMemo( ( ) => {
	return 콜백함수_코드
}, [] )

💎 예제

import { useMemo, useState } from 'react'
import './App.css'


function App() {
  const [number, setNumber] = useState(0);
  const [rerender, setRerender] = useState(false)

  
  // 콘솔 확인 때문에 case1 코드로 사용 case2 코드로 사용하여도 무방
  // ▪️ Case : 1
  const plus1 = (n) => {
    console.log('plus1 실행됨')
    return n + 1
  }
  // ▪️ Case : 2
  // const plus1 = (n) => n + 1


  // 🔽 [] 안의 값이 변경되었을 때 useMemo (() => {이 함수를 호출하겠다.},[])
  // ▪️ useMemo 적용 전 : " Rerender " 버튼 클릭 시 App 컴포넌트 리렌더링 => console.log('plus1 실행됨') 실행
  // ▪️ useMemo 적용 후 : [number] 값이 바뀌었을 때만 {numberPlus1} 함수 호출, 화면 값 변경
  // 만약, [rerender] 를 넣고, Rerender 버튼 클릭 -> console.log('plus1 실행됨') 실행, {numberPlus1} 함수 호출
  // number + 1 버튼 클릭 -> 기본 number 값만 변경되고, numberPlus1 함수 값이 변경되지 않음. 예시 : number + 1 버튼 3번 클릭 후 Rerender 버튼 클릭 시 numberPlus1 값이 변경 되어 +3 된 값이 화면상에 렌더링
  const numberPlus1 = useMemo(() => {
    return plus1(number)
  }, [number]) 


  return (
    <>
      {/* 🔽 여기서 처음 실행한 함수가 numberPlus1 : plus1(number) 이므로 첫 렌더링 때 1로 나옴 */}
      <div> numberPlus1 : {numberPlus1} </div>
      <button onClick={() => {setNumber(number + 1)}}> number + 1</button>
      <button onClick={() => {setRerender(!rerender)}}>Rerender</button>
    </>
  )
}

export default App

2️⃣ useCallback()

  • 함수를 메모이제이션하여, 의존성이 변경되지 않으면 동일한 함수 객체를 재사용
  • 콜백 함수가 불필요하게 다시 생성되는 것을 방지하여, 최적화

✔️ 기본 사용 코드

컴포넌트가 호출 되었을 때 ...

  • 콜백 함수 : 저장할 함수 코드 자체
  • 배열 : 해당 배열 값이 변경될 때 콜백 함수 재생성
const 저장할_함수 = useCallback( ( ) => {
	return 콜백 함수 코드
}, [] )

💎 예제

import { useCallback, useEffect, useMemo, useState } from 'react'
import './App.css'

function App() {
  const [number, setNumber] = useState(0);
  const [rerender, setRerender] = useState(false)
  
  // ▪️ 빈배열이면 처음 렌더링 될 때만 !
  const plus1 = useCallback(
    (n) => {
    console.log('plus1 실행됨')
    return n + 1
  }, [] )

  useEffect(() => {
    console.log("plus1 생성됨 / 이 곳에 무거운 작업이 수행될 때 사용하기 유리하다 !")
  }, [plus1])

  const numberPlus1 = useMemo(() => {
    return plus1(number)
  }, [number]) 


  return (
    <<>
      <div> numberPlus1 : {numberPlus1} </div>
      <button onClick={() => {setNumber(number + 1)}}> number + 1</button>
      <button onClick={() => {setRerender(!rerender)}}>Rerender</button>
    </>
  )
}

export default App
  • useCallback()
    - useCallback 함수 안에 들어있는 콜백 함수 자체를 저장
  • useEffect()
    - 첫 Mount , [plus1] 콜백 함수가 Update 될 때 실행
    - {plus1} 함수를 useCallback 을 사용하여 함수를 저장 후 [] 빈 배열을 주었기 때문에 첫 Mount 시에만 실행

3️⃣ memo()

  • 컴포넌트를 메모이제이션하여, props가 변경되지 않으면 리렌더링을 방지
  • 부모 컴포넌트가 리렌더링될 때 불필요한 자식 컴포넌트 리렌더링을 방지

✔️ 기본 사용 코드

컴포넌트를 호출해야 할 때 ...

  • 함수 컴포넌트 : 저장 할 함수 컴포넌트 자체
  • props : 해당 값이 변경 되지 않으면 리렌더링 방지
const 저장할_컴포넌트 = memo( ( props ) => {
	함수 컴포넌트 코드
} )

💎 예제

import { memo, useCallback, useEffect, useMemo, useState } from 'react'
import './App.css'

// ▪️ memo 선언
const NumberDisplay = memo(({ number }) => {
  console.log('Display 렌더링');
  return <div> number: {number} </div>;
});


function App() {
  const [number, setNumber] = useState(0);
  const [rerender, setRerender] = useState(false)

  const plus1 = useCallback(
    (n) => {
    console.log('plus1 실행됨')
    return n + 1
  }, [number] )

  useEffect(() => {
    console.log("plus1 생성됨")
  }, [plus1])

  const numberPlus1 = useMemo(() => {
    console.log('numberPlus1 변경')
    return plus1(number)
  }, [number]) 
  

  return (
    <>
      <NumberDisplay number={number}/>
      <div> numberPlus1 : {numberPlus1} </div>
      <button onClick={() => {setNumber(number + 1)}}> number + 1</button>
      <button onClick={() => {setRerender(!rerender)}}>Rerender</button>
    </>
  )
}

export default App
  • {props} 로 들어간 값이 변경 되어야 컴포넌트 재선언
  • <App> 컴포넌트가 리렌더링 되어도 자식 컴포넌트인 <NumberDisplay> 는 재선언(리렌더링) 되지 않음



🚨 추가 내용 - 원시값과 객체타입의 차이

✔️ 원시 값

[의미]
| 값이 불변(Immutable)하고, 직접 데이터를 저장하는 타입
| 변수를 복사하면 값이 그대로 복사됨 (값 복사, Call by Value)

[종류]
number, string, boolean, null, undefined, symbol, bigint

[특징]

  • 불변성 (Immutable): 값을 변경할 수 없음.
  • 값 자체를 저장함 (메모리에 직접 할당됨).
  • 변수를 다른 변수에 할당하면 값이 복사됨 (값 복사, Call by Value).

[기본 예제]

const memo1 = "park"
const memo2 = "park"

console.log(memo1 === memo2)
=> true

✔️ 객체 타입

[의미]
| 여러 개의 값을 저장할 수 있는 컨테이너. 메모리 주소(참조 값)를 저장
| 변수를 복사하면 참조가 복사됨 (참조 복사, Call by Reference)

[종류]
Object, Array, Function, Date •••

[특징]

  • 가변성 (Mutable): 값을 변경할 수 있음.
  • 변수에는 실제 값이 아니라 메모리 주소(참조 값) 가 저장됨.
  • 변수를 다른 변수에 할당하면 참조 값이 복사됨 (참조 복사, Call by Reference).

[기본 예제]

const memo1 = {
	name: "park"
    }
const memo2 = {
	name: "park"
    }

console.log(memo1 === memo2)
=> false

✔️ 추가 예제

import { useEffect, useMemo, useState } from 'react'
import './App.css'

function App() {
  const [number, setNumber] = useState(0)
  const [isKorea, setIsKorea] = useState(true)

  // CASE : 1
  const country = useMemo(() => {
    return{
          location: isKorea ? "한국" : "외국"
    }
  }, [isKorea ])
  
  // CASE : 2
  // const country = isKorea ? "한국" : "외국"

  // CASE : 2 처럼 원시 데이터를 사용하면 useMemo로 묶어주지 않아도 useEffect에서 country 값이 변하지 않는다고 생각해서 console.log("오래 걸리는 작업 ...") 이 작동하지 않지만,
  // CASE : 1 처럼 배열이나 객체인 객체 데이터를 사용하면 주소 값이 변하므로 변경된다고 생각하여 작업이 진행된다.


  // useEffect 에서 오래 걸리는 작업이 필요하다면 위에 useMemo 를 써주는 것이 좋다
  useEffect(() => {
    console.log("오래 걸리는 작업 ...")
  }, [country])

  return (
    <>
      <h3> 하루에 몇끼 먹어요 ? </h3>
      <input
        type='number'
        value={number}
        onChange={(e) => setNumber(e.target.value)}></input>
      <br/>
      <h3> 어느 나라에 있어요 ? </h3>
      <div> 나라 : {country.location}</div>
      <button onClick={() => setIsKorea(!isKorea)}>비행기 타자</button>
    </>
  )
}

export default App
  • [CASE : 2] 처럼 원시 데이터를 사용하면 useMemo로 묶어주지 않아도 useEffect에서 country 값이 변하지 않는다고 생각해서 console.log("오래 걸리는 작업 ...") 이 작동하지 않는다
  • [CASE : 1] 처럼 배열이나 객체인 객체 데이터를 사용하면 주소 값이 변하므로 값 자체가 변경된다고 생각하여 useMemo 를 사용하여도 console.log("오래 걸리는 작업 ...") 작업이 진행된다.



➕ 컴포넌트 리렌더링 확인하기 좋은 chrome 확장 프로그램

[React developer tools]

: React 컴포넌트 구조를 시각적으로 탐색하고, props, state, hooks 등을 실시간으로 확인하고 변경 가능

[설치 후 사용 방법]

  • React 웹사이트에서 F12 (개발자 도구) 실행
  • ⚛️ Components 탭과 ⚡ Profiler 탭이 추가됨
  • React 프로젝트가 제대로 인식되면 Components 탭에서 컴포넌트 트리 확인 가능

0개의 댓글