최근 체크히어 프로젝트에서 기술 스택과 구조 개선을 진행했습니다.
그중 입력 폼 성능 개선을 중점적으로 다뤘으며, 후기를 정리했습니다.
개선 작업 목록
- 전역 상태 기반 입력 폼 → React Hook Form 전환
- Next.js 14 → 15 업그레이드
- React 18 → 19 업그레이드
- Recoil → Zustand 상태 관리 전환
- Yarn → pnpm 패키지 매니저 전환
- ESLint v9 업그레이드 예정
체크히어는 강의 등록/수정 페이지에서
다양한 조건과 반복 구조를 가진 수십 개의 input 필드를 사용하는 프로젝트입니다.
특히 강의 시간표, 요일별 반복, Wi-Fi 설정 등 입력 필드 수가 많고 구조도 복잡했습니다.
입력 필드가 많아지면서 불필요한 리렌더링 문제가 빈번히 발생했고,
기존에는 Recoil을 통해 모든 입력값을 전역 상태로 관리하고 있었기 때문에
필드 하나만 바꿔도 전체 폼이 리렌더링되는 비효율이 있었습니다.
이러한 성능 병목을 해결하기 위해
불필요한 리렌더링을 최소화할 수 있는 React Hook Form으로 구조를 전환하게 되었습니다.
const [value, setValue] = useState('');
<input value={value} onChange={(e) => setValue(e.target.value)} />
<input ref={inputRef} defaultValue="hi" />
RHF는 기본적으로 비제어 컴포넌트 기반입니다. 그러나 아래처럼 제어 방식처럼도 동작 가능하며, 구독 기반 렌더링 최적화를 제공합니다.
const { register, formState: { errors } } = useForm({ mode: 'onChange' });
register()
호출 시 input에 ref
, onChange
, onBlur
주입onChange
이벤트 감지ref.current.value
로 실제 DOM 값 읽어옴formValues
업데이트'onChange'
일 경우 → 유효성 검사이 구조 덕분에 "비제어 기반"이지만 "제어처럼 동작"하며 불필요한 리렌더링을 방지할 수 있습니다.
React에서 리렌더링을 유발하는 건 state
나 props
의 변경이지만, RHF는 자체적으로 구독(subscription) 기반 시스템을 사용합니다.
구분 | 설명 |
---|---|
내부 상태 | RHF는 formState, errors 등을 일반 객체로 유지 (React state X) |
구독 시스템 | formState.errors.name , useWatch('name') 처럼 특정 값에 구독 등록 |
리렌더링 | 값이 바뀌면, 해당 필드에 한해 forceUpdate 로 강제 리렌더링 |
즉, input은 리렌더링 되지 않고, 예: formState.errors.name
을 사용하는 p
태그만 리렌더링됩니다.
function LoginForm() {
const { register, formState: { errors } } = useForm({ mode: 'onChange' });
return (
<form>
<input {...register('id', { required: 'ID 필수' })} />
{errors.id && <p>{errors.id.message}</p>}
<input {...register('password', { required: 'PW 필수' })} />
{errors.password && <p>{errors.password.message}</p>}
</form>
);
}
구분 | 설명 |
---|---|
input | 비제어, 리렌더링 X |
p 태그 | errors 구독 중 → 변경 시 리렌더링 |
컴포넌트 전체 | 리렌더링 X |
const [id, setId] = useState('');
<input value={id} onChange={e => setId(e.target.value)} />
구분 | 설명 |
---|---|
input | React가 value 직접 관리 |
모든 입력 | state 변경 → 컴포넌트 전체 리렌더링 발생 |
useWatch
, Controller
를 사용해 제어 컴포넌트처럼도 사용 가능reset
, setValue
, getValues
, useFieldArray
등 유틸 제공