본문 바로가기
TIL

11. 05. 21일차 TIL - 리렌더링, 메모이제이션, 조건부 렌더링

by 눈 새 2024. 11. 5.

오늘은 어제 마저 정리하지 못한 메모이제이션에 대한 개념에 대해 복습한 뒤, 챌린지반 과제를 통해 조건부 렌더링에 대해 학습하였다. 메모이제이션(memoization)과 리렌더링(re-rendering)은 성능 최적화와 관련된 개념으로 메모이제이션은 리렌더링 과정에서 발생할 수 있는 성능 저하를 줄이는 데 큰 기여를 하며 두 개념은 효율적인 애플리케이션 성능을 위해 함께 사용될 수 있다.


1. 리렌더링

리렌더링이란 UI 프레임워크에서 컴포넌트의 상태나 데이터가 변경될 때, 해당 컴포넌트를 다시 그리는 과정을 의미한다.

1) 리렌더링 발생 조건

  • 컴포넌트에서 state가 바뀔 때
  • 컴포넌트가 내려받은 props가 변경되었을 때
  • 부모 컴포넌트가 리-렌더링 된 경우 자식 컴포넌트 모두

1-3 예시 사진

2) 최적화

최적화란 브라우저에서 비용이 발생하는 것을 최대한 줄이는 작업을 말한다. 리액트에서 리렌더링이 빈번하게 자주 일어난다는 것은 비용이 많이 발생한다는 것과 같은 의미로 좋은 소식이 아니다.

  • 최적화하는 대표적인 3가지 방법
    • memo ((React.memo) : 컴포넌트를 캐싱
    • useCallback : 함수를 캐싱
    • useMemo : 값을 캐싱

2. memo (React.memo)

1) memo란?

리액트에서 memo는 성능 최적화를 위한 고차 컴포넌트(Higher Order Component)이다. 주로 함수형 컴포넌트에 적용되어, 컴포넌트가 받는 props가 변경되지 않는 한 리렌더링을 방지한다. 이것을 컴포넌트 memoization이라고 한다.

 

2) 주의사항

  • 얕은 비교의 한계
더보기

얕은 비교 : React.memo는 기본적으로 props의 얕은 비교를 사용해야 한다. 객체나 배열과 같은 참조형 데이터의 경우, 내용이 같더라도 참조가 다르면 리렌더링이 발생한다. 이 경우, 객체나 배열을 props로 전달할 때 매번 새로운 인스턴스를 생성하지 않도록 주의해야 한다.

  • props 변경에 대한 이해
더보기

부모 컴포넌트의 리렌더링 : 부모 컴포넌트가 리렌더링될 때 자식 컴포넌트에 전달된 props가 동일하더라도 부모 컴포넌트의 다른 상태나 props가 변경되면 자식 컴포넌트가 리렌더링될 수 있다. 따라서 이 점을 고려하여 컴포넌트 구조를 설계해야 한다.

  • 메모리 사용
더보기

메모리 소비 증가 : React.memo를 사용하여 메모이제이션된 컴포넌트의 수가 ㅁ낳아지면 메모리 사용량이 증가할 수 있다. 이는 특히 중첩된 컴포넌트 구조에서 문제가 될 수 있다.

  • 불필요한 최적화
더보기

최적화의 필요성 평가 : 모든 컴포넌트에 React.memo를 적용하는 것이 항상 좋은 것이 아니다. 컴포넌트가 자주 리렌더링되지 않거나, 렌더링 비용이 낮은 경우에는 오히려 성능에 부정적인 영향을 미칠 수 있다. 성능 분석 도구를 사용하여 최적화가 필요한 부분을 식별하는 것이 중요하다.

  • 커스텀 비교 함수 사용 시 주의
더보기

커스텀 비교 함수의 구현 : React.memo에 커스텀 비교 함수를 제공할 수 있다. 이 경우, 함수의 구현이 복잡해지면 성능이 오히려 저하될 수 있다. 따라서 커스텀 비교 함수는 신중하게 작성해야 한다.

  • 상태 관리와의 상호작용
더보기

상태 관리 라이브러리 : Redux와 같은 상태 관리 라이브러리와 함께 사용할 때, React.memo와의 상호작용을 고려해야 한다. 상태가 변경되면 관련 컴포넌트가 리렌더링되므로 메모이제이션의 효과가 줄어들 수 있다.

  React.memo는 성능 최적화를 위한 강력한 도구이나, 적절한 상황에서 신중하게 사용해야 한다. 각 컴포넌트의 특성과 리렌더링 패턴을 파악하고 필요할 때에만 적용하는 것이 중요하다.


3. useCallback

1) useCallback이란?

useCallback은 React의 훅 중 하나로, 함수 컴포넌트에서 특정 함수를 메모이제이션하여 성능을 최적화하는 데 사용한다. useCallback을 사용하면 컴포넌트가 리렌더링될 때마다 새로운 함수가 생성되는 것을 방지할 수 있다. 이로 인해 자식 컴포넌트에 전달되는 함수가 변경되지 않으면 해당 자식 컴포넌트의 불필요한 리렌더링을 방지할 수 있다.

 

2) 주의사항

  • 의존성 배열 관리
더보기

정확한 의존성 지정 : 의존성 배열에 필요한 모든 변수와 함수를 포함해야 한다. 누락할 경우, stale closure 문제로 이전 상태를 참조하게 되어 예기치 않은 동작이 발생할 수 있다.

 

# stale closure란?

JS에서 클로저와 관련된 개념으로, 클로저가 생성될 당시의 변수 상태를 참조하는 문제를 의미한다. 이는 특히 React와 같은 라이브러리에서 상태(state)나 props가 변경될 때 발생할 수 있다.

 

# stale closure의 발생 원인

useCallback이나 useEffect와 같은 훅을 사용할 때, 의존성 배열에 포함된 변수가 업데이트되지 않고 클로저가 이전 상태를 참조하게 되면 stale closure 문제가 발생한다. 예를 들어 컴포넌트가 리렌더링되더라도 클로저는 이전 상태를 기억하고 있기 때문에 쵯니 상태를 반영하지 못하게 된다.

  • 성능 최적화의 필요성
더보기

단순한 함수에 대한 사용 기피하기 : useCallback은 함수가 복잡하거나 자주 생성되는 경우에 유용하다. 하지만 단순한 함수나 자주 호출되지 않는 함수에 대해 사용하는 것은 불필요한 메모리 사용과 복잡성을 초래할 수 있다.

  • 메모리 소비
더보기

메모리 사용 증가 : 메모이제이션된 함수를 계속해서 저장하므로 많은 수의 함수가 메모이제이션되면 메모리 사용량이 증가할 수 있다. 특히 큰 애플리케이션에서는 이 점을 고려해야 한다.

  • 컴포넌트 리렌더링과의 관계
더보기

부모 컴포넌트 리렌더링 : 부모 컴포넌트가 리렌더링될 때 자식 컴포넌트에 전달된 함수가 동일하더라도 부모의 다른 상태나 props가 변경되면 자식 컴포넌트가 리렌더링될 수 있다. 이 점을 고려하여 컴포넌트 구조를 설계해야 한다.

  • 커스텀 훅과의 조합
더보기

커스텀 훅에서의 사용 : useCallback을 커스텀 훅에서 사용할 때, 해당 훅의 의존성 배열을 신중하게 관리해야 한다. 잘못된 의존성 배열은 예기치 않은 동작을 초래할 수 있다.

 

# 커스텀 훅(Custom Hook)이란?

React에서 재사용 가능한 상태 로직을 만들기 위해 사용하는 사용자 정의 함수이다. React의 훅을 활용하여 상태 관리, 사이드 이펙트 처리, 등을 캡슐화하고 이를 여러 컴포넌트에서 쉽게 재사용할 수 있게 해준다.

  • React.memo와 함께 사용
더보기

조합 사용의 고려 : useCallback과 React.memo를 함께 사용할 때, 두 개념이 서로 잘 동작하는지 확인해야 한다. useCallback으로 메모이제이션된 함수가 자식 컴포넌트에 전달되면, 자식 컴포넌트는 props가 변경되지 않는 한 리렌더링되지 않지만, props가 변경되면 불필요한 리렌더링이 발생할 수 있다.

  useCallback은 React 어플리케이션에서 성능 최적화를 위한 유용한 도구이다. 그러나 적절한 상황에서 신중하게 사용해야 하며, 의존성 배열을 정확하게 관리하고 불필요한 사용은 지양하는 것이 중요하다.성능 분석 도구를 사용하여 최적화의 필요성을 평가하는 것도 좋은 방법 중 하나이다.


4. useMemo

1) useMemo

useMemo는 React의 훅 중 하나로, 계산된 값을 메모이제이션하여 성능을 최적화하는 데 사용된다. 주로 복잡한 계산을 수행할 때 컴포넌트가 리렌더링 될 때마다 같은 계산을 반복하지 않도록 도와준다.

 

2) 주의 사항

  • 불필요한 메모이제이션 피하기
더보기

단순한 계산에 대한 사용 : useMemo는 성능을 최적화하기 위해 사용되지만 간단한 계산이나 자주 변경되는 값에 대해 사용하는 것은 오히려 성능 저하를 초래할 수 있다. 그러므로 메모이제이션이 필요한 복잡한 계산에만 사용하는 것이 좋다.

  • 의존성 배열 관리
더보기

정확한 의존성 지정 : 의존성 배열에 필요한 모든 변수를 포함해야 한다. 누락된 변수가 있을 경우, stale closure 문제나 예기치 않은 동작이 발생할 수 있다.

  • 메모리 소비
더보기

메모리 사용 증가 : 메모이제이션된 값은 메모리에 저장되므로 많은 수의 메모이제이션을 사용하면 메모리 사용량이 증가할 수 있다. 특히 큰 애플리케이션에서는 이 점을 고려해야 한다.

  • 리렌더링과의 관계
더보기

불필요한 리렌더링 피하기 : useMemo는 리렌더링 최적화에 도움을 줄 수 있지만, 컴포넌트가 자주 리렌더링되는 경우에는 오히려 성능이 저하될 수 있다. 메모이제이션이 적용된 값이 변경되지 않더라도, 다른 상태나 props가 변경되면 컴포넌트가 리렌더링될 수 있다.

  • 다른 최적화 기법과의 조화
더보기

useMemo와 React.memo의 조합 : useMemo와 React.memo를 함께 사용할 때, 두 기술이 서로 잘 작동하는지 확인해야 한다. useMemo로 메모이제이션된 값을 자식 컴포넌트에 전달할 때, 자식 컴포넌트가 props가 변경되지 않는 한 리렌더링되지 않도록 해야 한다.

  • 코드 가독성
더보기

복잡한 로직의 분리 : useMemo를 과도하게 사용하면 코드가 복잡해질 수 있다. 복잡한 계산 로직은 별도의 함수로 분리하고 필요한 경우에만 useMemo를 사용하여 코드의 가독성을 유지하는 것이 중요하다.

  useMemo는 성능 최적화를 위한 유용한 도구지만, 적절한 상황에서 신중하게 사용해야 한다. 의존성 배열을 정확하게 관리하고 불필요한 메모이제이션을 지양하며 다른 최적화 기법과 조화롭게 사용하는 것이 중요하다.


5. React 조건부 렌더링

조건부 렌더링이란 특정 조건에 따라 컴포넌트의 일부 또는 전체를 렌더링하는 방법이다. 조건부 렌더링은 브라우저의 동작 원리와 밀접한 관련이 있으며, 효율적인 DOM update, state기반 렌더링, 사용자 경험 향상, 등 여러 측면에서 중요하다. React는 이러한 조건부 렌더링을 통해 사용자에게 더 나은 경험을 제공하고, 성능을 최적화 할 수 있도록 돕는다.

 

💡 조건부 렌더링과 브라우저의 동작 원리와 밀접한 관련이 있다⁉

더보기

브라우저는 HTML 문서를 해석하여 DOM(Document Object Model)을 생성하고 이 DOM을 기반으로 화면에 내용을 렌더링한다. React는 가상 DOM을 사용하여 실제 DOM과의 차이를 최소화하고 효율적으로 UI를 업데이트한다. 조건부 렌더링을 통해 사용자가 필요로 하지 않는 요소를 미리 렌더링하지 않음으로써 불필요한 DOM 조작을 최소화할 수 있다. 이 때 브라우저는 DOM을 업데이트하는 데 소요되는 시간이 단축되므로 이러한 효율성은 전체 성능을 향상시킨다.

 

1) 조건부 렌더링을 하는 이유

  • 사용자 경험 향상
    • 사용자의 상태나 행동에 따라 맞춤형 콘텐츠를 제공할 수 있다
  • 효율적인 리소스 관리
    • 필요하지 않은 요소를 렌더링하지 않음으로써 성능을 최적화할 수 있다.
  • 상태 관리
    • 애플리케이션의 상태에 따라 UI를 동적으로 변경할 수 있다.
    • ex) 로딩 상태, 오류 메시지, 성공 메시지, 등
  • 코드 구조화
    • 조건부 렌더링을 통해 코드의 구조를 명확하게 유지하고, 컴포넌트를 더 잘 분리할 수 있다.
    • 컴포넌트를 분리하는데 효율적 → 유지보수성 향상
  • 접근성 개선
    • 사용자의 필요에 따라 UI를 조정함으로써 접근성을 향상시킬 수 있다
  • 디버깅 용이
    • 예상되는 UI 상태를 쉽게 확인하고 잘못된 상태를 추적하는 데 도움이 된다.

조건부 렌더링은 사용자 경험을 개선하고, 성능을 최적화하며,

    코드의 가독성과  유지보수성을 높이는 데 중요한 역할을 한다.

 

2) 조건부 렌더링 방법과 장단점

  • if문 사용하기
function MyComponent({ isLoggedIn }) {
  if (isLoggedIn) {
    return <h1>환영합니다!</h1>;
  } else {
    return <h1>로그인 해주세요.</h1>;
  }
}
  • 장점
    • 조건을 명확하게 구분하여 작성할 수 있어, 전체적인 흐름을 이해하기 쉽다.
  • 단점
    • 조건이 복잡해지거나 여러 개의 if문이 중첩될 경우, 가독성이 저하될 수 있다.
    • JSX 내부에서 직접적으로 사용하기 어려워 렌더링 조직과 UI코드가 분리될 수 있다.

if문은 간단한 조건에서는 가독성이 좋으나

    조건이 복잡해질수록 가독성이 저하되고 JSX 내부에서는 사용할 수 없다.

 

  • 삼항 연산자 사용하기
function MyComponent({ isLoggedIn }) {
  return <h1>{isLoggedIn ? "환영합니다!" : "로그인 해주세요."}</h1>;
}
  • 장점
    • 간결함 : 간단한 조건을 한 줄로 표현할 수 있어 코드가 깔끔해진다.
    • JSX 내부에서 직접 사용 가능하여, 조건부 렌더링을 쉽게 구현할 수 있다.
  • 단점
    • 조건이 복잡해지면 코드가 난잡해질 수 있다.
    • 중첩된 삼항 연산자는 가독성을 크게 저하시킨다.

✔  삼항 연산자는 간단한 조건부 렌더링에 매우 유용하지만,

    복잡한 조건을 처리할 때에는 다른 방법을 고려하는 것이 좋다.

 

  • 논리 AND 연산자 사용하기
function MyComponent({ isLoggedIn }) {
  return (
    <div>
      {isLoggedIn && <h1>환영합니다!</h1>}
    </div>
  );
}
  • 장점
    • 조건이 참인 경우에만 렌더링되므로 불필요한 요소를 제거할 수 있다.
    • JSX 내부에서 직접 사용할 수 있어 조건부 렌더링을 간편하게 구현할 수 있다.
  • 단점
    • 조건이 거짓일 경우에는 아무것도 렌더링 되지 않아 다른 요소와 함께 사용하기 어렵다.
    • 여러가지 조건을 처리해야 할 경우 AND 연산자만을 사용하는 데에 한계가 있다.
    • 좌항이 falsy한 값일 경우 falsy한 값이 반환될 수 있음을 유의해야 한다.

 

  • AND 연산자 좌항이 falsy한 값일 경우 원하는 값이 반환되는 것이 아닌 falsy한 값이 반환됨을 유의해야 한다.

 AND 연산자 또한 간단한 조건부 렌더링에 매우 유용하지만,

    복잡한 조건을 처리할 때에는 다른 방법을 고려하는 것이 좋다.

 

  • Switch문 사용하기
function MyComponent({ status }) {
  switch (status) {
    case 'loading':
      return <h1>로딩 중...</h1>;
    case 'success':
      return <h1>성공적으로 로드되었습니다!</h1>;
    case 'error':
      return <h1>오류가 발생했습니다.</h1>;
    default:
      return null;
  }
}
  • 장점
    • 여러 조건을 처리하기 쉬워 복잡한 로직을 명확하게 표현할 수 있다.
    • 각 케이스를 독립적으로 관리할 수 있어 유지보수가 용이하다
  • 단점
    • JSX 외부에서 사용해야 하므로 렌더링 로직이 분리되어 가독성이 떨어질 수 있다.
    • 각 케이스에 대한 반환 값이 모두 JSX여야 하므로 반환하는 내용이 복잡해질 수 있다.

 Switch 구문은 복잡한 조건부 렌더링에 유용하지만

    코드 구조와 가독성을 고려하여 적절한 상황에서 사용하는 것이 좋다.

 

  • 컴포넌트 분리하기
function LoggedIn() {
  return <h1>환영합니다!</h1>;
}

function LoggedOut() {
  return <h1>로그인 해주세요.</h1>;
}

function MyComponent({ isLoggedIn }) {
  return isLoggedIn ? <LoggedIn /> : <LoggedOut />;
}
  • 장점
    • 동일한 조건부 로직을 여러 곳에서 사용할 수 있다. → 코드의 재사용성을 높일 수 있다.
    • 각 컴포넌트를 독립적으로 관리할 수 있어, 코드의 가독성과 유지보수성이 향상된다.
    • 상태 관리가 명확해져 각 컴포넌트의 책임이 분리된다.
  • 단점
    • 작은 조건부 렌더링에는 오히려 과한 구조가 된다.
    • 코드의 구조가 더 복잡해질 수 있다.
    • 각 컴포넌트의 상태나 속성을 관리하기 위해 추가적인 코드가 필요할 수 있다.

 컴포넌트를 분리하여 조건부 렌더링을 구현하는 것은 코드의 가독성과 유지보수성을 높이는 데

     도움이 되지만, 구조의 복잡성과 상태 관리 측면에서 주의가 필요하다.


★ 21일차 소감

 

오늘은 아침 9시에 특강 이후 반 이동에 대한 고민을 다시 하게 되었다. 처음 반선택을 할 때 내가 react의 기초도 모르는 상태로 챌린지 반을 들어가서 제대로 수업을 따라갈 수 있을까 하는 걱정으로 스탠다드반을 선택했었다. 그래도 계속 고민하고 있었다. 내가 왜 퇴사를 하며 부트캠프에 참여했는지에 대해 고민해보았다. 나는 작년에 1년정도 코딩에 관심이 생겨 혼자 독학을 했었던 경험이 있다. 어떻게 공부해야할지 몰랐기에 문법과 개념 공부를 위주로 html,css.js를 공부하였다. 나는 부트캠프에서 react를 배울 수 있다는 것과 더불어 협업 환경에서의 팁을 체득하고 싶었다. 아침 특강이 끝난 이후 챌린지반 수업 영상을 호기심 삼아 시청해보았고 혼자서 공부할 때는 알 수 없었던 내가 원하던 그런 정보들을 챌린지 반 수업을 통해서 얻을 수 있을 것이라고 생각했다. 2일차 수업 영상을 모두 시청한 뒤 튜터님들께 자문을 구했고, 바로 반을 옮겼다.. 앞으로는 지금보다 더 많이 바빠질 것이다. 챌린지 반인 만큼 잘하는 사람들이 많이 모여있는 환경에서 동기부여나 자극을 받을 수 있다는 건 정말 좋은 상황이라고 생각한다. 하지만 그런 자극에 반비례하는 노력으로 수업 진도를 제대로 따라가지 못한다거나 프로젝트의 퀄리티가 떨어지게 된다면 내 의도와는 상반되게 오히려 챌린지반을 선택한 것을 후회할 것이고 아까운 시간을 낭비하게 되는 멍청한 결정이 될 것이다. 나는 지금 선택이 내 미래를 위해서 더 올바른 선택이라고 믿는다. 앞으로는 지금보다 더욱 더 많이 복습하고, 실습하고 혹여나 모르는 것이 생기면 튜터님들을 적극적으로 찾아뵙고 질문해야겠다. 그렇게 좋은 개발자로서 성장할 것이다.