[우아한테크코스 FE LEVEL2-1] 제어 컴포넌트 & custom hooks

Gyuhan Park·2024년 5월 2일
1

[ 학습 목표 ]

  • 재사용 가능한 Input Component 를 개발한다.
  • Storybook 을 사용하여 컴포넌트의 다양한 상태를 시각적으로 테스트한다.
  • 카드 정보를 효과적으로 렌더링 하기 위한 상태 관리를 경험한다.
  • 다양한 Form 구성 요소들간의 상태를 효율적으로 관리한다.
  • hooks API를 이용하여 상태 관리 로직을 구현한다.
  • custom hooks를 생성하여 Form 관리 로직을 컴포넌트에서 분리하고 재사용한다.
  • Controlled & Uncontrolled Components에 입각하여 Form을 핸들링한다.

💭 TMI

레벨 2 첫번째 미션이 마무리 되었다. storybook을 처음 사용해봤는데 컴포넌트 단위로 여러 케이스를 확인할 수 있으니까 다른 직군과 협업할 때 유용하겠다는 생각이 들었다. 만드는 데 오래 걸릴 것 같아서 항상 미뤄뒀었는데 시각적으로 변하는 컴포넌트가 많을 때 도입해보면 좋겠다.

미션은 react로 하면 바닐라 자바스크립트보다 훨씬 편하겠다고만 생각했는데 생각보다 힘들었다. 이렇게 사용자 입력값을 많이 처리해본 적이 없었고, 입력값이 많은 경우 react-hook-form을 사용했었다. 라이브러리 없이 인풋들을 모두 state로 관리하고, 이에 대한 유효성 검증 로직 을 추가하는 게 까다로웠다. 아무 생각없이 구현하고 리팩토링하면 진행 속도는 빠르겠지만, 기능이 추가될 경우와 유지보수 측면에서 바라보면 그때 수정하는 비용이 더 클 수도 있다. 이러한 측면에서 꼼꼼한 페어와 꼼꼼한 리뷰어 덕분에 이번 미션을 성공적으로 마무리할 수 있었다.

📘 배운 점 & 느낀 점

레벨 2를 시작한 지 2주 정도 지났는데 가장 많이 바뀐 것 중 하나는 생성형 AI에 대한 시선 이다. 공부에 도움이 안된다 생각하고, 원하는 대답을 알려주지 않아 믿음이 가지 않았다. 하지만 생성형 AI의 발전 속도와 디버깅까지 해주는 AI 영상을 보고, AI보다 똑똑해지려고 하기보단 활용해야겠다고 생각이 바뀌게 되었다. 막힐 때마다 사용하는 것이 아니라 고민해보고 내 생각이 맞는지, 어떻게 개선할 수 있는지 등을 물어보면서 생각을 넓힐 수 있을 것 같다.

✅ 제어 컴포넌트 & 비제어 컴포넌트

input 에 value값과 이벤트 핸들러를 상태로 관리하는 과정에서 value를 넘겨주지 않아도 정상적으로 동작하는 것을 알 수 있었다. 이런 동작이 궁금하여 찾아봤는데 제어 컴포넌트와 비제어 컴포넌트라는 개념이 따로 있었다. 정리하면 상태 관리와 데이터 흐름의 주도권이 어디에 있는지에 따라 제어 컴포넌트와 비제어 컴포넌트를 구분할 수 있다.

🚨 제어 컴포넌트

제어 컴포넌트에서는 React가 사용자의 입력값을 상태로 관리 한다.
상태를 input value 속성으로 넘겨주는데, 이를 value attribute와 state를 결합하여 state를 신뢰 가능한 단일 출처 로 만든다고 표현한다.
input 값을 상태로 관리하면, 값을 입력할 때마다 onChange 이벤트가 발생하여 setState가 되면서 리렌더링이 발생 한다.
제어 컴포넌트는 비제어 컴포넌트와 달리 오류에 대한 즉각적인 피드백이 가능하며, 조건에 따라 동적으로 버튼 상태를 제어할 수 있다. 대신 폼의 모든 상태가 React를 통해 관리되므로, 모든 입력값 변화에 setState가 동작하여 복잡한 폼에서 성능 저하가 발생할 수 있다.

import React, { useState } from 'react';

function ControlledForm() {
  const [value, setValue] = useState("");

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

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

🚨 비제어 컴포넌트

리액트가 입력값을 제어하지 않고 HTML Element 에서 관리된다. (DOM)
React에서는 ref를 사용하여 DOM에 직접 접근할 수 있고, 값이 필요할 때 DOM에서 직접 가져올 수 있다.
ref를 사용하며, state로 입력값을 상태로 관리하지 않기 때문에 입력값이 바뀌더라도 컴포넌트는 리렌더링 되지 않는다.
폼을 외부에서 제어하기 어렵고, 외부 코드와 폼 상태를 동기화하기 어렵다. 또한 실시간으로 유효성 검증 또는 조건부 처리가 어렵다는 단점이 있다.

const UncontrolledForm = () => {
	const inputRef = useRef(null);
	
	return (
		<input type="text" ref={inputRef} />
	)
}

react-hook-form 라이브러리는 비제어 컴포넌트 기반으로 동작하여 입력할 때마다 리렌더링 되지 않고, 제어 컴포넌트의 장점인 즉각적인 피드백도 제공한다고 한다. 나중에 코드를 직접 뜯어봐도 좋을 것 같다.

✅ custom hooks

커스텀 훅은 유틸 함수처럼 범용적으로 사용될 수 있는 코드 또는 재사용될 수 있는 로직을 분리하는 역할이라고 생각했다. 하지만 강의를 듣고 리뷰를 받으면서 재사용에 초점을 맞추지 않아도 된다는 생각이 들었다. 가장 기억에 남는 단어는 관심사의 분리 다. 컴포넌트와 마찬가지로 useInput 처럼 재사용될 수 있는 커스텀 훅도 존재하고, 이를 활용하여 해당 도메인에서 사용되는 커스텀 훅도 만들 수 있다. 이렇게 분리하면 기능을 추가하거나 수정할 때 다른 파일에 영향을 크게 주지 않고 해당 부분만 처리할 수 있다.

두번째는 비즈니스 로직과 UI 로직 분리 다. 레벨 1 에서 도메인과 UI를 완전히 분리하고 이를 controller단에서 연결해주는 작업을 했었다. 이와 같이 react에서는 비즈니스 로직을 커스텀 훅으로 분리하여 컴포넌트가 UI 로직에만 집중할 수 있도록 할 수 있었다.

한가지 예를 든다면 아래 useInput 커스텀 훅이 있을 때 유효성 검증 로직이 다 다른 것을 감안해서 validator를 인자로 받았다. 그리고 도메인 로직에 맞게 이벤트 핸들러를 커스텀 할 수 있도록 setValue와 setErrorInfo를 모두 반환하였다.

const useInput = (initialValue = '', validator) => {
	const [value, setValue] = useState(initialValue);
	...
	return {
      value,
      setValue,
      handleChange,
      errorInfo,
      setErrorInfo,
      updateErrorMessage,
  	};
}

위의 useInput을 활용한 도메인 커스텀 훅이다. validate 함수도 인자로 넣어줄 수 있고, 반환받은 setValue와 setErrorInfo를 활용하여 해당 도메인에서만 적용되는 이벤트 핸들러를 만들어 낼 수 있다.

const useName = () => {
	const { ... } = useInput('', {
    	onChange: validateChange,
    	onBlur: validateBlur,
  	});
  
	const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
      const validationResult = validateEnglish(e.target.value);
      setErrorInfo(validationResult);
      if (validationResult.isError) return;
      setValue(e.target.value.toUpperCase());
  	};
}

테코톡 : 제어 컴포넌트와 비제어 컴포넌트
https://goshacmd.com/controlled-vs-uncontrolled-inputs-react/

profile
단단한 프론트엔드 개발자가 되고 싶은

0개의 댓글