제어 vs 비제어 Component

Jin.Dev·2023년 2월 2일
0
post-thumbnail

1. 제어 컴포넌트

HTML에서 input, textarea, select 와 같은 form element는 일반적으로 사용자의 입력을 기반으로 자신의 state를 관리하고 업데이트를 한다. React에서는 변경할 수 있는 state가 일반적으로 컴포넌트 state 속성에 유지되며 setState 함수에 의해 업데이트된다. 이러한 방식으로 React에 의해 값이 제어되는 입력 form element를 제어 컴포넌트(controlled component)라고 한다.
공식문서 - https://ko.reactjs.org/docs/forms.html#controlled-components

1-2 코드 예시

제어 컴포넌트는 공식문서에 적혀있듯이 사용자의 입력을 기반으로 자신의 state를 관리하고 업데이트 한다 라는 부분은 아래에 코드 예시 처럼 event 객체를 이용해 setState 함수를 이용해 state를 저장하는 방식이다.

const Controlled = () => {
  const [test, setTest] = useState<string>("");

  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { value } = e.target;
    setTest(value);
  };
  return <input onChange={onChange} />;
};

export default Controlled;

1-3 로그 확인

console로 확인해보니 새로운 값을 입력할 때마다 state를 계속 update해준다.

2.비제어 컴포넌트

비제어 컴포넌트는 setState 함수를 사용하지않고 ref를 사용해서 값을 얻는 방식이다.

2-1 ref는 언제 써야하나?

리액트로 작업하다 보면 Dom 요소에 직접 접근해야 할 때가 있다. 예를 들어 Dom 요소Focus를 주거나 스크롤 위치를 알고 싶을 때 사용한다. 이 경우에 ref 속성값을 이용하면 컴포넌트 또는 Dom 요소에 직접 접근할 수 있다.

2-2 코드 예시

비제어 컴포넌트에서는 useRef를 사용하여 사용자가 직접 트리거 하기 전 까지는 리렌더링을 하지 않는다.

const Uncontrolled = () => {
  const ref = useRef<HTMLInputElement>(null);

  const onClick = () => {
    if (!ref.current) {
      return;
    }
    console.log(ref.current.value);
  };
  return (
    <>
      <input ref={ref} />
      <button type="submit" onClick={onClick}>
        버튼
      </button>
    </>
  );
};

export default Uncontrolled;

2-3 로그 확인

버튼을 클릭해야 로그가 찍히고 있고 전송 버튼을 클릭하기 전에는 react에서 state값이 변경되지 않아 리렌더링을 일으키지 않는다.

2-4 ref 속성값으로 돔 요소에 직접 접근해보기!

ref 속성값을 이용해 페이지 렌더링 시에 __input focus__를 해보자

const InputFocus = () => {
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    refFn();
  }, []);

  const refFn = () => {
    if (!inputRef.current) {
      return;
    }
    inputRef.current.focus();
  };
  return (
    <div>
      <input type="text" ref={inputRef} />
      <button>저장</button>
    </div>
  );
};

export default InputFocus;
  1. useRef 훅이 반환하는 ref 객체를 이용해서 Dom 요소에 접근하여 요소의 ref 속성값에 ref 객체를 입력한다.
  2. ref 객체의 current 속성을 이용하면 요소에 접근할 수 있다.
  3. 결과적으로 페이지 렌더링 시 input에 focus가 되어있다.

2-5 ref 속성값 사용 시 주의할 점

컴포넌트가 생성된 이후라도 ref 객체의 current 속성이 없을 수 있기 때문에 주의해야 한다.

  const inputRef = useRef<HTMLInputElement>(null);
  const [show, setShow] = useState<boolean>(true);

  const showText = () => {
    setShow((prev) => !prev);
  };

  const goToInput = () => {
    if (!inputRef.current) {
      return;
    }
    inputRef.current.focus();
  };

  return (
    <div>
      {show && <input type="text" ref={inputRef} />}
      <button onClick={showText}>텍스트 보이기 / 가리기</button>
      <button onClick={goToInput}>텍스트로 이동</button>
    </div>
  );
};
  1. ref 속성값을 입력한 input 요소는 show 상태값에 따라 존재하지 않을 수도 있어 조건부 렌더링을 하는 경우에는 컴포넌트가 생성된 이후라도 ref 객체를 사용할 때 주의해야 한다.
  2. input 요소가 존재하지 않는 상태에서 텍스트로 이동 버튼을 누르면 inputRef 객체의 current 속성은 존재하기 않기 때문에 에러가 발생한다.

3. 문제 발생!

3-1 input value update

나는 input을 update하기 위해서 아래와 같이 코드를 만들었지만 각 input에 해당하는 value 값이 바뀌지 않았다

3-2 깨달았던 부분

위에 언급했다 시피 input의 value를 처리하기 위해서는 제어 와 비제어 컴포넌트 방식이 있다고 하였고
제어 컴포넌트는 React에 setState 함수에 의해 값이 처리 된다고 하였다.
그렇다면 제어 컴포넌트 방식으로 input을 제어하려고 했던 나는 setList를 통해서 state값을 update 해주어야 하는데 setTest를 통해 해당 state 값을 update 하려고 했으니 기존에 map을 돌려서 뽑아낸 item 요소들의 값이 바뀌지 않았던 것이였다.
제어 컴포넌트 방식 - value 속성 사용
비제어 컴포넌틑 방식 - defaultValue 속성 사용

3-2 동작되지 않는 코드

// list.tsx

const [list, setList] = useState([
    { id: 1, name: "title", value: "제목", label: "title" },
    { id: 2, name: "id", value: "아이디", label: "ID" },
    { id: 3, name: "email", value: "이메일", label: "Email" },
    { id: 4, name: "name", value: "JIN", label: "Name" },
    { id: 5, name: "mobile", value: "010-0000-0000", label: "Mobile" },
    { id: 6, name: "team", value: "F-Lab", label: "Team" },
  ]);

const [test, setTest] = useState<ListType[]>([]);


const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { value, name } = e.target;

    setTest({
      ...list,
      [name]: value,
    });
  };

  const saveForm = () => {
    setTest(list);
    setEditModal(false);
  };


{list.map((item) => (
            <Input
              key={item.id}
              type="text"
              value={item.value}
              onChange={onChange}
              name={item.name}
            >
              {item.label}
            </Input>
          ))}

3-3 동작되는 코드

setState 함수인 setList를 통해서 각 input에 대한 value를 변경시켜준 뒤에 setTest로 값을 update 시켜준다.


const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { value, id } = e.target;

    const newList = list.map((item) =>
      item.id === Number(id) ? { ...item, value } : item
    );

    setList(newList);
  };

const saveForm = () => {
    setTest(list);
    setEditModal(false);
  };


출처 & 참고
https://ko.reactjs.org/docs/uncontrolled-components.html
https://ko.reactjs.org/docs/uncontrolled-components.html
https://velog.io/@jhplus13/react-input%EC%9A%94%EC%86%8C%EC%97%90-value%EC%99%80-defaultValue%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90

0개의 댓글