React에서 Controlled Component와 Uncontrolled Component 차이점

NANA·2025년 4월 2일
0
post-thumbnail

Controlled Component와 Uncontrolled Component

Controlled Component

Controlled Component는 리액트 상태(state)를 통해 입력 값을 제어하는 컴포넌트를 말합니다. 이 방식에서는 입력 요소의 값(value)을 리액트 상태와 동기화하고, 사용자가 입력을 변경할 때마다 onChange 이벤트 핸들러를 통해 상태를 업데이트합니다. Controlled Component는 값이 리액트의 state로 관리되므로, 입력 시마다 값을 검증하거나, 값을 자유롭게 변경할 수 있으며, 복잡한 폼 로직을 처리하는 데 유용합니다.

// Controlled Component 예시
function ControlledInput() {
  const [value, setValue] = useState('');
  
  return (
    <input 
      type="text"
      value={value} // React state에서 값을 가져옴
      onChange={(e) => setValue(e.target.value)} // 값이 변경될 때마다 state 업데이트
    />
  );
}

Uncontrolled Component

Uncontrolled Component는 입력 값을 리액트의 상태로 관리하지 않고, DOM을 통해 입력 값을 제어하는 방식입니다. 즉, 입력 요소의 값은 DOM에서 직접 관리되며, 리액트는 이를 제어하지 않습니다. 이 방식에서는 useRef를 사용해 생성된 참조 객체인 ref를 사용하여 DOM 요소에 직접 접근하여 값을 읽거나 조작합니다. Uncontrolled Component는 리액트 상태 관리에 따른 성능 비용이 없으므로 상대적으로 간단한 폼에서 주로 사용됩니다.

// Uncontrolled Component 예시
function UncontrolledInput() {
  const inputRef = useRef();
  
  const handleSubmit = () => {
    // 필요할 때만 DOM에서 값을 가져옴
    console.log('현재 값:', inputRef.current.value);
  };
  
  return (
    <>
      <input 
        type="text"
        ref={inputRef} // DOM 참조 저장
        defaultValue="초기값" // 초기값만 설정
      />
      <button onClick={handleSubmit}>확인</button>
    </>
  );
}

Controlled Component와 Uncontrolled Component는 각각 어떤 상황에서 사용되나요? 🤔

단순한 입력 필드가 포함된 폼에서는 입력 요소의 값을 리액트 상태로 관리할 필요성이 적으므로, Uncontrolled Component를 사용하는 것이 더 간단하고 성능이 좋습니다. 사용자가 제출 버튼을 클릭했을 때만 입력 값을 가져와도 충분한 경우를 예시로 들 수 있습니다.

반면, 값을 입력할 때마다 유효성 검증을 실시간으로 해주어야 하는 경우에는 Controlled Component를 사용해야 합니다.

리액트에서 성능 최적화를 위해 적용할 수 있는 방법

리액트의 memo를 사용하여 컴포넌트를 메모이제이션할 수 있습니다. 이는 컴포넌트의 props가 변경되지 않았을 때, 리렌더링을 방지하여 성능을 최적화합니다. 이는 특히 렌더링 비용이 큰 컴포넌트에서 유용합니다.

또한 useCallbackuseMemo를 활용할 수도 있습니다. useCallback은 함수를 메모이제이션하여 불필요한 함수 재생성을 방지하고, useMemo는 값의 재계산을 방지하여 성능을 최적화합니다. 이를 통해 자식 컴포넌트로 전달되는 함수나 값이 변경되지 않으면 리렌더링을 피할 수 있습니다.

마지막으로 코드 스플리팅을 활용해볼 수 있습니다. 코드 스플리팅은 큰 애플리케이션을 여러 개의 작은 청크로 나누어, 필요한 청크만 로드하게 하여 초기 로드 시간을 줄입니다. React.lazySuspense를 사용하여 동적으로 컴포넌트를 로드할 수 있습니다.

코드 스플리팅은 어떤 경우에 사용해야 할까요? 🤔

첫번째로는 초기 로딩 시간이 길어지는 경우입니다. 애플리케이션이 커지면, 초기 로딩에 모든 코드를 로드하는 것이 비효율적일 수 있습니다. 코드 스플리팅을 사용해 초기 로드 시 필요한 핵심 코드만 로드하고, 이후 추가적인 기능은 필요할 때 로드하도록 하면 초기 로딩 속도를 크게 개선할 수 있습니다.

두번째로는 라우트별 코드 분할이 필요한 경우입니다. SPA에서는 각 페이지가 별도의 기능과 UI를 가지므로, 라우트별로 필요한 코드만 분리하여 로드할 수 있습니다. 이 방식은 리액트의 React.lazySuspense를 사용하여 라우트별 컴포넌트를 동적으로 불러올 때 유용합니다.

Next.js vs React의 차이점
React: 기본적으로 모든 코드를 한 번에 로드하며, 코드 스플리팅은 개발자가 React.lazy를 사용해 직접 구현해야 합니다.
Next.js: 페이지 기반으로 자동 코드 스플리팅이 이루어지며, 서버 사이드 렌더링을 통해 초기 로딩 성능을 최적화합니다.

리액트의 render phase와 commit phase

리액트의 렌더링 과정은 크게 render phasecommit phase로 나눌 수 있다.

먼저 render phase리액트가 변화된 상태나 props에 따라 어떤 UI가 변경되어야 할지를 결정하는 단계입니다. 이 과정에서는 실제로 DOM을 업데이트하지 않고, 변경사항을 가상 DOM에서 계산하여 비교합니다. 이 단계는 순수하게 계산과정이기 때문에 성능에 영향을 주지 않도록 중단되거나 다시 실행될 수 있으며, React 18에서 도입된 Concurrent Mode를 통해 비동기적으로 처리될 수도 있습니다.

다음으로 commit phase실제로 변화된 UI를 DOM에 반영하는 단계입니다. 이때 리액트는 가상 DOM에서 계산된 결과를 실제 DOM에 적용하고, 변화된 UI를 브라우저에 렌더링합니다. DOM 업데이트 이후에는 useEffect와 같은 사이드 이펙트를 발생시키는 훅들이 실행됩니다.

요약하면 render phase는 변화된 UI를 결정하는 계산 과정이고, commit phase는 그 계산된 결과를 실제로 반영하는 단계입니다.

profile
고양이를 좋아하는 개발자입니다

0개의 댓글