[개발] React 컴포넌트 최적화 기법

컴포넌트 렌더링 최적화
——————————–

컴포넌트 렌더링 최적화는 React 애플리케이션의 성능을 향상시키는 중요한 과정입니다. 여러 최적화 기법을 활용하여 불필요한 렌더링을 방지하고, 효율적인 업데이트를 관리할 수 있습니다.

1. Memoization을 사용한 함수 컴포넌트 최적화

Memoization은 이전에 계산된 값을 저장하고, 동일한 입력이 주어진 경우 이전에 계산된 값을 반환하는 기법입니다. 이를 활용하여 함수 컴포넌트의 결과를 캐싱하고, 같은 입력이 들어온 경우 재계산을 피할 수 있습니다.


import React, { useMemo } from 'react';

function MyComponent({ value }) {
  const expensiveResult = useMemo(() => {
    // 복잡한 계산 수행
    return calculateExpensiveResult(value);
  }, [value]);

  return (
    

Value: {value}

Expensive Result: {expensiveResult}

); }

2. React.memo를 활용한 함수 컴포넌트 최적화

React.memo는 컴포넌트의 props가 변경되지 않으면 이전 결과를 재사용하는 함수 컴포넌트 최적화 기법입니다. 이를 사용하면 동일한 props에 대한 불필요한 렌더링을 방지할 수 있습니다.


import React, { memo } from 'react';

const MyComponent = memo(({ value }) => {
  return (
    

Value: {value}

); });

3. shouldComponentUpdate를 사용한 클래스 컴포넌트 최적화

shouldComponentUpdate는 클래스 컴포넌트에서 렌더링을 제어하기 위해 사용하는 생명주기 메서드입니다. 이를 활용하여 특정 조건에 따라 불필요한 렌더링을 방지할 수 있습니다.


class MyComponent extends React.Component {
  shouldComponentUpdate(nextProps) {
    if (this.props.value === nextProps.value) {
      return false; // value가 변경되지 않으면 렌더링 방지
    }
    return true;
  }

  render() {
    return (
      

Value: {this.props.value}

); } }

키(Key)를 활용한 엘리먼트 식별
———————————-

React에서 리스트를 렌더링할 때 각 요소에 고유한 식별자인 키(Key)를 할당하는 것은 중요합니다. 키를 활용하여 React가 렌더링을 효율적으로 수행하고, 업데이트를 올바르게 관리할 수 있습니다.

1. 키의 역할

React는 기본적으로 key를 통해 엘리먼트의 식별을 수행합니다. key는 컴포넌트들 간의 관계를 유지하고, 업데이트 시에 효율적으로 변화를 감지할 수 있도록 도와줍니다.

2. 사례: 키를 누락한 리스트

키를 누락하면 React는 각 항목의 변경 여부를 판단하지 못하고, 전체 리스트를 재렌더링해야 합니다. 이는 성능상의 이슈를 초래할 수 있습니다.


function MyComponent() {
  const items = [1, 2, 3, 4, 5];

  return (
    
    {items.map(item => (
  • {item}
  • ))}
); }

3. 사례: 키를 포함한 리스트

키를 사용하여 React에게 각 항목을 고유하게 식별하도록 지시할 수 있습니다. 이를 통해 React는 변경된 항목만 업데이트하고, 나머지 항목은 그대로 유지할 수 있습니다.


function MyComponent() {
  const items = [1, 2, 3, 4, 5];

  return (
    
    {items.map(item => (
  • {item}
  • ))}
); }

3.1. 키의 선택

키는 엘리먼트의 고유한 속성이어야 합니다. 일반적으로 데이터의 고유한 ID나 인덱스를 사용하는 것이 좋습니다. 유일성과 안정성을 보장하는 키를 선택해야 합니다.


메모이제이션(Memoization)을 사용한 함수 컴포넌트 최적화
——————————————————-

메모이제이션은 이전에 계산된 값을 저장하고, 동일한 입력이 주어진 경우 이전에 계산된 값을 반환하는 기법입니다. React에서는 useMemo 훅을 사용하여 함수 컴포넌트의 결과를 캐싱하고, 같은 입력이 들어온 경우 재계산을 피할 수 있습니다.

1. useMemo를 사용한 메모이제이션

useMemo 훅은 첫 번째 매개변수로 전달된 콜백 함수를 실행하고, 그 결과를 저장합니다. 두 번째 매개변수로는 의존성 배열을 전달하여 의존하는 값이 변경되었을 때에만 재계산을 수행할 수 있습니다.


import React, { useMemo } from 'react';

function MyComponent({ value }) {
  const expensiveResult = useMemo(() => {
    // 복잡한 계산 수행
    return calculateExpensiveResult(value);
  }, [value]);

  return (
    

Value: {value}

Expensive Result: {expensiveResult}

); }

2. 사례: 피보나치 수열 계산

메모이제이션을 사용하여 재귀적인 피보나치 수열 계산을 최적화할 수 있습니다. 이전에 계산한 결과를 저장하여 동일한 값이 들어온 경우 재계산을 피할 수 있습니다.


import React, { useMemo } from 'react';

function Fibonacci({ n }) {
  const result = useMemo(() => {
    if (n === 0 || n === 1) {
      return n;
    } else {
      return Fibonacci(n - 1) + Fibonacci(n - 2);
    }
  }, [n]);

  return 
{result}
; }

2.1. 의존성 배열 사용

useMemo의 두 번째 매개변수로 전달되는 의존성 배열은 콜백 함수가 의존하는 값들을 담고 있습니다. 이 값들 중 하나라도 변경되면 콜백 함수가 재실행되고, 변경되지 않으면 이전에 계산된 값을 재사용합니다.


React.memo를 활용한 함수 컴포넌트 최적화
—————————————

React.memo를 사용하면 함수 컴포넌트를 렌더링하는 동안의 성능을 최적화할 수 있습니다. React.memo는 컴포넌트의 속성(props)이 변경되지 않으면 이전에 렌더링한 결과를 재사용합니다.

1. React.memo의 사용

React.memo는 React 컴포넌트를 감싸는 래퍼 함수로, 이전에 렌더링한 결과를 메모리에 저장하여 같은 속성이 전달될 때 재사용할 수 있도록 합니다.


import React from 'react';

const MyComponent = React.memo(function MyComponent({ prop }) {
  // 컴포넌트의 내용
});

export default MyComponent;

2. 사례: 메모된 컴포넌트

React.memo를 사용하여 컴포넌트를 감싸면, 컴포넌트의 속성이 변경되지 않으면 이전에 렌더링한 결과를 재사용할 수 있습니다.


import React from 'react';

function MyComponent({ name }) {
  console.log('Rendering MyComponent');
  return 
Hello, {name}!
; } const MemoizedComponent = React.memo(MyComponent); function App() { const [count, setCount] = React.useState(0); return (
); } export default App;

2.1. 속성(props)의 변화

React.memo는 컴포넌트의 속성이 변경되었는지를 얕은 비교로 확인합니다. 따라서 속성이 객체 또는 배열인 경우, 새로운 객체나 배열을 생성하여 변경이 발생하도록 해야 재렌더링이 수행됩니다.


PureComponent를 활용한 클래스 컴포넌트 최적화
———————————————–

PureComponent는 React의 클래스 컴포넌트를 사용하여 렌더링 성능을 최적화하는 방법 중 하나입니다. PureComponent는 컴포넌트의 props와 state가 변경되지 않으면 이전에 렌더링한 결과를 재사용합니다.

1. PureComponent의 사용

PureComponent를 상속받은 컴포넌트는 내부적으로 shouldComponentUpdate 메서드를 구현하여 얕은 비교를 통해 속성과 상태의 변경 여부를 확인합니다. 변경이 발생하지 않은 경우 불필요한 렌더링을 방지하여 성능을 향상시킵니다.


import React from 'react';

class MyComponent extends React.PureComponent {
  render() {
    // 컴포넌트의 내용
  }
}

export default MyComponent;

2. 사례: 숫자 카운터

PureComponent를 사용하여 숫자 카운터를 구현할 수 있습니다. 속성과 상태가 변경되지 않으면 이전에 렌더링한 결과를 재사용하여 성능을 최적화합니다.


import React from 'react';

class Counter extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  handleClick() {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    console.log('Rendering Counter');
    return (
      

Count: {this.state.count}

); } } export default Counter;

2.1. 속성(props)과 상태(state)의 변화

PureComponent는 속성(props)과 상태(state)의 변경을 얕은 비교로 확인합니다. 따라서 객체 또는 배열과 같은 참조 타입의 속성이나 상태의 경우, 새로운 객체나 배열을 생성하여 변경이 발생하도록 해야 재랜더링이 수행됩니다.


shouldComponentUpdate를 사용한 클래스 컴포넌트 최적화
——————————————————-

shouldComponentUpdate는 React의 클래스 컴포넌트에서 렌더링 성능을 최적화하는 방법 중 하나입니다. 이 메서드를 구현하여 컴포넌트의 속성이나 상태가 변경되었을 때 실제로 재렌더링이 필요한지 여부를 결정할 수 있습니다.

1. shouldComponentUpdate의 사용

shouldComponentUpdate 메서드는 컴포넌트가 업데이트되기 전에 호출되며, 불리언 값을 반환해야 합니다. 반환된 값이 true인 경우에만 재렌더링이 수행됩니다.


import React from 'react';

class MyComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    // 재렌더링 여부를 결정하는 로직
    // true를 반환하면 재렌더링이 수행됨
  }

  render() {
    // 컴포넌트의 내용
  }
}

export default MyComponent;

2. 사례: 숫자 카운터

shouldComponentUpdate를 사용하여 숫자 카운터를 구현할 수 있습니다. count 속성이 변경되지 않았을 때에만 재렌더링이 수행되도록 최적화됩니다.


import React from 'react';

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  handleClick() {
    this.setState({ count: this.state.count + 1 });
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (this.state.count !== nextState.count) {
      return true; // count가 변경되면 재렌더링
    }
    return false;
  }

  render() {
    console.log('Rendering Counter');
    return (
      

Count: {this.state.count}

); } } export default Counter;

2.1. 속성(props)과 상태(state)의 변화

shouldComponentUpdate 메서드는 속성(props)과 상태(state)의 변경 여부를 확인하여 재렌더링 여부를 반환하는 로직을 구현할 수 있습니다. 이를 통해 변경이 없는 경우에는 재랜더링을 방지하여 성능을 향상시킬 수 있습니다.


무상태 함수 컴포넌트와 PureComponent의 차이점
————————————————

무상태 함수 컴포넌트와 PureComponent는 React에서 컴포넌트를 작성하는 두 가지 방법입니다. 둘 다 렌더링 성능을 향상시키는데 사용될 수 있지만, 몇 가지 차이점이 존재합니다.

1. 무상태 함수 컴포넌트

무상태 함수 컴포넌트는 React의 함수 컴포넌트를 사용하여 컴포넌트를 작성하는 방법입니다. 상태(state)를 가지지 않으며, 간단한 UI를 표현하기 위해 사용됩니다. 또한, 컴포넌트 생명주기 메서드를 사용할 수 없습니다.


import React from 'react';

function MyComponent(props) {
  // UI를 구성하는 JSX 코드
  return (
    // 컴포넌트의 내용
  );
}

export default MyComponent;

2. PureComponent

PureComponent는 React의 클래스 컴포넌트를 사용하여 렌더링 성능을 최적화하는 방법 중 하나입니다. shouldComponentUpdate를 자동으로 구현하기 때문에 속성(props)과 상태(state)가 변경되지 않으면 재렌더링을 건너뛰게 됩니다.


import React from 'react';

class MyComponent extends React.PureComponent {
  render() {
    // 컴포넌트의 내용
  }
}

export default MyComponent;

3. 차이점

– 상태(State): 무상태 함수 컴포넌트는 상태를 가지지 않습니다. 반면 PureComponent는 상태를 가질 수 있습니다.

– 렌더링 최적화: PureComponent는 shouldComponentUpdate를 자동으로 구현하여 속성(props)과 상태(state)의 변경 여부를 확인하고, 변경이 없으면 재렌더링을 하지 않습니다. 이를 통해 성능을 향상시킬 수 있습니다. 무상태 함수 컴포넌트는 shouldComponentUpdate 메서드를 직접 구현할 수 없으므로 항상 렌더링이 수행됩니다.

– 생명주기 메서드: 무상태 함수 컴포넌트는 생명주기 메서드를 사용할 수 없습니다. PureComponent는 React의 클래스 컴포넌트이므로 생명주기 메서드를 사용할 수 있습니다.


useMemo와 useCallback을 활용한 값 캐싱과 함수 캐싱
—————————————————

useMemo와 useCallback은 React Hooks의 일부로서 값을 캐싱하고 재사용하는데 사용됩니다. 이를 통해 렌더링 성능을 최적화할 수 있습니다.

1. useMemo

useMemo는 계산량이 많은 함수 호출의 결과 값을 기억하고 재사용할 때 사용됩니다. 이를 통해 동일한 인수로 함수를 여러 번 호출해야 하는 상황에서 성능을 향상시킬 수 있습니다.


import React, { useMemo } from 'react';

function MyComponent() {
  const expensiveValue = useMemo(() => {
    // 계산량이 많은 연산
    return /* 결과 값 */;
  }, []);

  return (
    // 결과 값을 사용하는 JSX 코드
  );
}

export default MyComponent;

2. useCallback

useCallback은 함수 콜백을 기억하고 재사용할 때 사용됩니다. 이를 통해 동일한 함수를 매번 새로 생성하지 않고 재사용함으로써 성능을 향상시킬 수 있습니다.


import React, { useCallback } from 'react';

function MyComponent() {
  const handleClick = useCallback(() => {
    // 클릭 이벤트 핸들러
  }, []);

  return (
    // 클릭 이벤트 핸들러를 사용하는 JSX 코드
  );
}

export default MyComponent;

3. 사용 예시

다음은 useMemo와 useCallback을 사용하여 숫자를 증가시키는 컴포넌트 예시입니다. increment 함수는 useCallback을 통해 재사용되며, count는 useMemo를 통해 캐싱됩니다.


import React, { useState, useMemo, useCallback } from 'react';

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

  const increment = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  const doubledCount = useMemo(() => {
    return count * 2;
  }, [count]);

  return (
    

Count: {count}

Doubled Count: {doubledCount}

); } export default Counter;

React.memo와 useCallback을 함께 사용한 최적화
————————————————-

React.memo와 useCallback은 함께 사용하여 컴포넌트를 최적화하는데 도움을 줄 수 있습니다. React.memo는 컴포넌트의 속성(props)이 변경되지 않으면 재렌더링을 하지 않도록 memoization을 수행하며, useCallback은 콜백 함수를 기억하고 재사용함으로써 성능을 향상시킵니다.

1. React.memo

React.memo는 다음과 같은 형식으로 사용됩니다.


import React from 'react';

const MyComponent = React.memo(() => {
  // 컴포넌트의 내용
});

export default MyComponent;

React.memo로 감싼 컴포넌트는 속성(props)이 변경되지 않으면 이전에 렌더링된 결과를 재사용합니다. 속성이 변경되면 컴포넌트는 새로 렌더링됩니다.

2. useCallback과 React.memo 함께 사용하기

useCallback과 React.memo를 함께 사용하여 콜백 함수와 컴포넌트를 최적화할 수 있습니다. useCallback으로 생성된 함수는 속성이 변경되지 않으면 이전에 생성된 콜백 함수를 재사용하고, React.memo로 감싼 컴포넌트는 속성(props)이 변경되지 않으면 이전에 렌더링된 결과를 재사용합니다.


import React, { useCallback } from 'react';

const MyComponent = React.memo(({ onClick }) => {
  // 컴포넌트의 내용
});

const ParentComponent = () => {
  const handleClick = useCallback(() => {
    // 클릭 이벤트 핸들러
  }, [/* 의존성 배열 */]);

  return ;
}

export default ParentComponent;

ParentComponent에서 handleClick은 useCallback을 사용하여 캐싱됩니다. 이는 속성(props)이 변경되지 않으면 이전에 생성된 콜백 함수를 계속해서 사용할 수 있도록 합니다. 또한, MyComponent는 React.memo로 감싸져 있으므로 onClick이 변경되지 않으면 이전에 렌더링된 결과를 재사용합니다.

3. 사용 예시

다음은 useCallback과 React.memo를 함께 사용하여 숫자를 증가시키는 컴포넌트를 최적화한 예시입니다. increment 함수와 Counter 컴포넌트는 모두 useCallback과 React.memo로 최적화되었습니다.


import React, { useState, useCallback } from 'react';

const IncrementButton = React.memo(({ onClick }) => {
  return ;
});

const Counter = React.memo(() => {
  const [count, setCount] = useState(0);

  const increment = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  return (
    

Count: {count}

); }); export default Counter;

위의 예시에서 IncrementButton 컴포넌트는 React.memo로 감싸여 속성(props)이 변경되지 않으면 이전에 렌더링된 결과를 재사용합니다. Counter 컴포넌트는 useCallback을 사용하여 increment 함수를 캐싱하고, React.memo로 감싸여 속성(props)이 변경되지 않으면 이전에 렌더링된 결과를 재사용합니다.


React DevTools를 활용한 컴포넌트 최적화 확인
————————————————

React DevTools는 리액트 애플리케이션을 디버깅하고 프로파일링하는데 도움을 주는 도구입니다. 컴포넌트의 렌더링 동작을 확인하고 성능을 최적화하기 위해 React DevTools를 사용할 수 있습니다.

1. React DevTools 설치

React DevTools는 크롬 확장 프로그램으로 제공되며, 크롬 웹 스토어에서 설치할 수 있습니다. 설치 후, 크롬 개발자 도구를 열고 React 탭을 확인할 수 있습니다.

2. 컴포넌트의 렌더링 확인

React DevTools를 통해 컴포넌트의 렌더링 동작을 확인할 수 있습니다. 렌더링 동작의 흐름을 시각적으로 확인하여 무의미한 렌더링이 발생하는지, 너무 많은 렌더링이 발생하는지 등을 파악할 수 있습니다.

예시:

다음은 Counter 컴포넌트를 예시로 살펴봅니다.


import React, { useState } from 'react';

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

  const increment = () => {
    setCount(count + 1);
  };

  return (
    

Count: {count}

); } export default Counter;

React DevTools에서 Components 탭을 확인하면 현재 컴포넌트 트리와 렌더링된 컴포넌트의 상태를 확인할 수 있습니다. 렌더링된 컴포넌트의 상태를 변경하면 해당 컴포넌트가 다시 렌더링되는 것을 확인할 수 있습니다.

3. 성능과 최적화 확인

React DevTools를 사용하여 컴포넌트의 성능과 최적화 여부를 확인할 수 있습니다. Profiler 탭을 사용하여 애플리케이션의 퍼포먼스를 측정하고, 어느 컴포넌트에서 시간이 많이 소요되는지 파악할 수 있습니다.

예시:

다음은 애플리케이션에서 사용자 목록을 랜더링하는 예시입니다.


import React from 'react';

function UserList({ users }) {
  return (
    
    {users.map((user) => (
  • {user.name}
  • ))}
); } export default UserList;

React DevTools의 Profiler 탭을 활용하여 UserList 컴포넌트의 렌더링 속도를 측정하고 최적화할 수 있습니다. 컴포넌트의 렌더링 속도에 문제가 있을 경우, useMemo나 useCallback을 사용하여 불필요한 렌더링을 최소화하거나, 렌더링 최적화 기법을 적용할 수 있습니다.


Leave a Comment