리액트 성능을 어떻게 개선할까?

홍정민·2024년 7월 12일
0

리액트에서 컴포넌트의 리렌더링이 자주 일어난다. 리렌더링이 일어난다는 것은 해당 컴포넌트의 코드가 다시 실행 된다는 것 이다. 따라서 컴포넌트에 대한 성능 최적화가 잘 이뤄져야 부드러운 웹 사이트를 구현할 수 있게 된다.

리액트 컴포넌트의 성능을 향상 시키는 방법에는 무엇이 있을까?

리액트에서 제공하는 다양한 API와 Hook

어플리케이션에서 성능을 개선하는 대표적인 방법은 재사용기억이다. 어떤 것을 재사용하는 행위는 컴퓨터 연산의 비용을 줄이는 것과 같기 때문이다.

따라서 리액트에는 재사용 하는 방법을 제공한다.

값의 재사용

useMemo는 복잡한 연산을 수행한 결과 값을 재사용하여 성능을 향상시킬 수 있게 해준다.

코드 예제

useMemo는 종속성 배열에 있는 값이 바뀌면 첫 번째 인자로 받은 계산 함수를 실행한다.

다음의 컴포넌트에서 1~10000까지 더한 값을 반환하는 함수를 사용 할때, 랜더링 될 때마다 해당 연산을 하면 성능적으로 좋지 않을 것 이다.

const Component = () => {
// ❌ 성능을 고려하지 않은 방법
  const value = () => { 
    let sum = 0;
    for (let i = 0; i < 10000; i++) {
      sum += i;
    }
    return sum;
  }

  return <div>{value}</div>;
};

따라서 다음과 같이 useMemo를 사용하여 복잡한 연산을 최적화 시킬 수 있다.

const Component = () => {
  // 🟢 성능을 고려한 방법
  const value = useMemo(() => {
    let sum = 0;
    for (let i = 0; i < 10000; i++) {
      sum += i;
    }
    return sum;
  }, []);

  return <div>{value}</div>;
};

함수의 재사용

useCallback이라는 훅을 통해 성능을 개선시킬 수 있다. 컴포넌트의 함수는 리렌더링 할 때마다 매 번 다른 함수가 생성(기능은 동일)된다. 이러한 점은 뒤에 나오는 memo의 사용을 방해하고, 종속성 배열에 들어간다면 매 번 변화를 감지할 것 이다.

코드 예제

const Component = () => {
  const [text, setText] = useState('');

  // 메모이제이션된 콜백 함수
  const handleTextChange = useCallback((e) => {
    setText(e.target.value);
  }, []); // 의존성 배열이 빈 배열이므로, 이 함수는 처음 렌더링될 때만 생성됨

  return (
    <div>
      <input type="text" value={text} onChange={handleTextChange} />
    </div>
  );
};

컴포넌트의 재사용

React.memo로 자식 컴포넌트에게 전달되는 props가 이전과 동일할 때, 리렌더링을 시키지 않고, virtual DOM에 계산을 포함하지 않게 되어 성능이 향상된다.

코드 예제

다음의 예제는 text의 상태가 변화해도 <Child />컴포넌트는 리렌더링 시키지 않는 모습을 볼 수 있다.

const Child = React.memo(({ count }) => {
  console.log('Child rendered');
  return <p>Count: {count}</p>;
});

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

  return (
    <div>
      <Child count={count} />
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <input 
        type="text" 
        value={text} 
        onChange={(e) => setText(e.target.value)} 
      />
    </div>
  );
};

useCallback와 memo의 시너지

increment 함수를 useCallback으로 감싼 다음, 인자로 내려 주기 때문에 버튼을 눌러도 버튼 컴포넌트를 리렌더링 하지 않는다.

const IncrementButton = React.memo(({ onIncrement }) => {
  console.log('Button rendered');
  return <button onClick={onIncrement}>Increment</button>;
});

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

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

  return (
    <div>
      <p>Count: {count}</p>
      <IncrementButton onIncrement={increment} />
    </div>
  );
};

결론

useMemo로 값을 재사용하고, useCallback으로 함수를 재사용하고, React.memo로 컴포넌트를 재사용하여 성능을 최적화 하자.

0개의 댓글