[React] Controlled & Uncontrolled Components

Nine·2022년 5월 8일
2

React

목록 보기
13/22

Controlled Component (제어 컴포넌트)

React에 input값이 완전히 제어되는 Input Element

👇 state를 value로 넘기고 그 state를 다룰 수 있는 핸들러를 콜백으로 넘깁니다.

const [data,setData] = useState('');

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

// input의 값은 항상 React state의 값으로 갱신됩니다.
<input value={data} onChange={handleChange} />

장점

  • 실시간으로 사용자의 입력을 판단하고 검증할 수 있어요.

단점

  • state값이 변경되기 때문에 리렌더링이 게속해서 발생해요.

Uncontrolled Component (비제어 컴포넌트)

React에 input값이 제어되지 않는 Input Element

  • 전통적인 HTML처럼 DOM에서 제어되는 Input Element입니다.
    • 입력하는 값들을 state로 관리하지 않아서 반드시 ref를 통해 가져와야해요. (ref.current.value)
const inputRef = useRef(); // ref 사용

const addStation = () => {
  const newStationName = inputRef.current.value;
  ...

};

return (
  <form onSubmit={addStation}>
    <Input
      type="text"
      labelText="지하철 역 이름을 입력해주세요."
      ref={inputRef}
    />
    <Button>추가</Button>
  </form>
);
  • 오직 사용자만 value와 상호작용합니다.

장점

  • 기존 DOM의 input처럼 리렌더링되지 않습니다.

단점

  • 실시간으로 사용자의 값을 판단하는데 무리가 있어요.

🤔 생각해보기

제어 컴포넌트를 지향하라는 의견이 많은 이유는 무엇일까요?

  • 비제어 컴포넌트를 사용하면 submit할 때만 value(input)에 접근할 수 있어요.

  • 반면에 제어 컴포넌트는 언제든지 state를 통해 value에 접근할 수 있으므로 실시간 검증이나 입력 강제에 대해서 유연합니다.

  • 💪 keep in mind the React docs recommend going with controlled components for most cases.


useImperativeHandle 이 언급되는 이유가 무엇일까요?

  • useImperativeHandle는 ref를 사용하여 자식 컴포넌트에서 부모 컴포넌트로 values를 올려줄 수 있어요.

  • 부모 컴포넌트는 바로 그 values를 사용할 수 있고, 다른 자식 컴포넌트한테도 넘겨줄 수 있게 되겠죠.

👇 사용법을 알아봅시다.

import { useState, useRef, forwardRef, useImperativeHandle } from "react";

// 1. 부모의 ref를 props로 받으려면 forwardRef로 감싸야해요.
// 첫번째 인자는 일반 props고 두번째 인자가 ref입니다.
const ChildOne = forwardRef((props, ref) => {
  const [count, setCount] = useState(0);

  // 2. ChildOne 컴포넌트 내부 state인 count를 ref를 통해 parent 컴포넌트한테 보내줄 수 있어요.
  useImperativeHandle(ref, () => ({
    count,  // 객체로 count를 보내주고 있죠?
  }));

  const updateCount = () => {
    setCount((c) => c + 1);
    console.log(count + 1);
  };

  return <button onClick={updateCount}>Increment</button>;
});

const Parent = () => {
   // 이제 부모의 ref.current에는 count라는 속성이 추가되었습니다. (ref.current.count)
  const ref = useRef();

  return (
    <div>
      <ChildOne ref={ref} />
      <ChildTwo ref={ref} />
    </div>
  );
};

// ref.current.count로 접근하고 있죠?
const ChildTwo = forwardRef((props, ref) => {
  const checkCount = () => console.log("->", ref.current.count);

  return <button onClick={checkCount}>Count</button>;
});


export default Parent;

💭 그럼 다시 본론으로 왜 useImperativeHandle을 언급할까요?

  • form 이 있다면 form에서 해결해줄 수 있어요.

  • 없다면 써서 하나로 묶어버려야죠.

useImperativeHandle는 순수 리액트 API로 form 상태를 구현할 수 있는 최후의 수단이예요.

  • useImperativeHandle로 각 자식 input들의 state, ref를 부모인 form에 넘겨준다면

  • submit할 때 등록된 모든 state, ref에 접근할 수 있어요.

  • 🤔 근데... form onChange에서도 e.currentTarget으로 내부 input들 element 전부 접근 가능한데 굳이 써야하나 싶기도 하네요

  • 뭐.. 여튼 비제어 컴포넌트는 상태값을 저장하지 않아서 실시간 validation이 힘든데 위처럼 useImerpativeHandle을 사용해서 가능하게 할 수 있을 것 같아요.

비제어 컴포넌트는 사용할 일이 없는건가요?

  1. 굳이 실시간 검증이 필요 없을 때 사용할 것 같아요.
  • 리렌더링 안하고 좋죠!
  1. ref를 써야할 때 사용할 것 같아요.
    👉 비제어 컴포넌트는 결국 ref를 사용해서 값을 가져와야한다고 했죠?
1. 포커스, 텍스트 선택영역, 미디어의 재생관리

2. 애니메이션 직접 실행

3. 서드 파티 DOM 라이브러리를 React처럼 사용할때 
profile
함께 웃어야 행복한 개발자 장호영입니다😃

0개의 댓글