Tech/ReactJS

제어 컴포넌트 최적화 방법 3가지 (+ 비제어 컴포넌트와 비교)

닝닝깅 2024. 6. 3. 22:38

폼 데이터를 관리하다가 제어 컴포넌트와 비제어 컴포넌트에 대해 공부해보게되었다.

📌 제어 컴포넌트와 비제어 컴포넌트

제어 컴포넌트

리액트 상태에 의해 값이 제어되는 컴포넌트이다.

 

사용자 입력값으로 자신의 상태를 관리하고 업데이트하고, 상태가 업데이트 될 때마다 컴포넌트는 리렌더링된다.

폼 데이터가 상태에 저장되어 있어 데이터의 흐름이 명확하고, 로직 구현도 수월하지만, 빈번한 리렌더링으로 인해 불필요한 api 호출 등 자원 낭비가 발생할 수 있다.

import React, { useState } from 'react';

function ControlledComponent() {
  const [inputValue, setInputValue] = useState('');

  const handleChange = (event) => {
    setInputValue(event.target.value);
  };

  return (
    <form>
      <input type="text" value={inputValue} onChange={handleChange} />
    </form>
  );
}

 

비제어 컴포넌트

입력 요소 자체가 값을 제어하며 리액트와 독립적으로 동작하는 컴포넌트이다. 입력값이 DOM 요소에 저장되기 때문에 DOM에서 이루어지는 컴포넌트라고 할 수 있다.

 

입력값을 가져오고 싶은 경우, ref를 사용하여 입력 값에 접근하고 제어한다.

사용자 입력에 따라 상태가 변경되지 않기 때문에 이벤트마다 리렌더링은 발생하지 않으나 폼 데이터 변경 추적이 어렵다는 단점이 있다.

import React, { useRef } from 'react';

function UncontrolledComponent() {
  const inputRef = useRef(null);

  const handleSubmit = (event) => {
    event.preventDefault();
    console.log('Submitted value:', inputRef.current.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" ref={inputRef} />
      <button type="submit">Submit</button>
    </form>
  );
}

 

❓ 각각 언제 사용되는가?

제어 컴포넌트는 데이터 흐름이 예측 가능하고 관리하기 쉬워 복잡한 폼이나 유효성이 필요한 경우에 적합하고, 비제어 컴포넌트는 간단한 입력 폼이나 초기값 설정이 중요한 경우에 성능면에서 제어 컴포넌트보다 유리할 수 있다.

하지만 제어 컴포넌트 최적화가 가능하다면?

 

📌 제어 컴포넌트 최적화 방법

여러 방법의 제어 컴포넌트 최적화로 성능을 개선시킬 수 있다.

1. 쓰로틀링, 디바운싱

가장 흔하게 사용되는 제어 컴포넌트 최적화 방법으로,  불필요한 연속 호출을 줄이는 데 효과적이다.

 

우선, 쓰로틀링은 이벤트 호출이 반복될 때 일정한 시간 간격동안 특정함수를 한번만 실행되도록 제어하는 방법이다.

function throttle(func, delay) {
    let lastCall = 0;
    return function(...args) {
        const now = new Date().getTime();
        if (now - lastCall < delay) return;
        lastCall = now;
        return func(...args);
    };
}

 

디바운싱은 연속적으로 발생하는 이벤트 중에서 마지막 이벤트만 처리하도록 하는 방법이다.

function debounce(func, delay) {
    let debounceTimer;
    return function(...args) {
        clearTimeout(debounceTimer);
        debounceTimer = setTimeout(() => func(...args), delay);
    };
}

 

2. 트라이 자료구조

문자열 검색을 위한 트리형태의 자료구조로, 문자열 탐색 및 자동 완성 기능 구현에 유용하다. 공통적인 접두사를 공유하는 문자열들을 효율적으로 저장하고 검색하여 성능을 개선시킨다.

 

3. useDeferedValue

일부 상태 업데이트를 지연시켜 UI 성능을 개선할 수 있는 리액트 훅이다.

우선순위가 낮은 상태 업데이트를 뒤로 미뤄 중요한 작업부터 업데이트를 진행시킬 수 있다.

import { useDeferredValue } from 'react';

function MyComponent({ value }) {
    const deferredValue = useDeferredValue(value);
    
    return (
        <div>
            <p>Immediate value: {value}</p>
            <p>Deferred value: {deferredValue}</p>
        </div>
    );
}

 

4. useTransition : UI를 차단하지 않고 상태를 업데이트

UI를 차단하지 않으면서 비동기적으로 상태 업데이트를 시킬 수 있는 리액트 훅이다.

중요하지 않은 상태 업데이트는 중요한 작업이 끝난 후 반영되기 때문에 UI가 부드럽게 동작할 수 있다.

 

아래의 예시는 사용자 입력 시 호출되는 handleChange 함수 내부의 setValue가 트랜지션으로 처리되는 과정이다. 이를 통해 value는 비동기적으로 업데이트 된다.

import { useTransition, useState } from 'react';

function MyComponent() {
    const [isPending, startTransition] = useTransition();
    const [value, setValue] = useState('');

    const handleChange = (e) => {
        startTransition(() => {
            setValue(e.target.value);
        });
    };

    return (
        <div>
            <input type="text" onChange={handleChange} />
            {isPending ? <p>Loading...</p> : <p>{value}</p>}
        </div>
    );
}