React
[React] useState & useRef & react-hook-form : 제어/비제어 컴포넌트, useRef 단점, 언제 무엇을 써야 하는가
aotoyae
2024. 8. 13. 21:49
728x90
반응형
💡제어 컴포넌트 useState 와 비제어 컴포넌트 useRef 를 비교해보자!
제어 컴포넌트
- React 에 의해 값이 제어되는 입력 폼 엘리먼트
- 사용자의 입력을 기반으로 state 를 관리하고 업데이트한다.
- 모든 input 이 변경될 때 마다 리렌더링
import React, { useState } from 'react';
function MyInput() {
const [inputValue, setInputValue] = useState(null);
const handleChange = (e) => {
setInputValue(e.target.value);
};
return <input onChange={(e) => handleChange(e)} value={inputValue} />;
}
비제어 컴포넌트
- React 에서 값이 제어되지 않는 컴포넌트
- 값이 변화되더라도 input 을 제외한 필드가 리렌더링 되지 않음
import React, { useRef } from 'react';
function Test() {
return <MyInput />;
}
function MyInput() {
const inputNode = useRef(null);
return <input ref={inputNode} />;
}
export default MyInput;
기능
|
제어 컴포넌트 | 비제어 컴포넌트 |
일회성 정보 검색 (예 : 제출) | ⭕️ | ⭕️ |
제출 시 값 검증 | ⭕️ | ⭕️ |
실시간으로 필드 값 유효성 검사 | ⭕️ | ❌ |
조건부로 제출 버튼 비활성화(disabled) | ⭕️ | ❌ |
실시간으로 입력 형식 적용 (숫자만 가능하게 등) | ⭕️ | ❌ |
동적 입력 | ⭕️ | ❌ |
즉각적으로 값에 대한 피드백이 필요하다면
➡️ 제어 컴포넌트 사용
즉각적인 피드백이 불필요하고 제출 시에만 값이 필요하다면, 불필요한 렌더링과 값 동기화가 싫다면
➡️ 비제어 컴포넌트 사용
🥸 프로젝트 중 했던 고민
회원가입 form 을 만들기 전 처음엔 useRef 를 사용하기로 결정했다.
값을 입력할 때마다 렌더링을 일으키고 싶지 않았고, 유효성 검사를 onBlur 시에만 실행하고 싶었기 때문이다.
const handleBlur = (ref: RefObject<HTMLInputElement>) => {
const refValue = ref.current?.value;
if (refValue) {
const ID_REGEX = /^(?=.*[a-z])[a-z0-9-_]{5,20}$/; // 5~20자, 소문자 하나는 반드시 포함, 숫자/-/_ 는 선택적
const PW_REGEX =
/^(?=.*[a-z])(?=.*\d)(?=.*[@$!%*?&_\-])[A-Za-z\d@$!%*?&_\-]{8,16}$/; // 8~16자, 소문자, 숫자, 특수문자 반드시 포함, 대문자는 선택적
if (ref === idRef) {
setId(refValue);
isValidValue(refValue, ID_REGEX, true);
} else if (ref === pwRef) {
setPw(refValue);
isValidValue(refValue, PW_REGEX, false);
} else {
setPwCheck(refValue);
pw === pwCheck ? setPwCheckIsValid(true) : setPwCheckIsValid(false);
}
}
};
// ...
<input
type="text"
placeholder="아이디"
ref={idRef}
onBlur={() => handleBlur(idRef)}
/>
하지만 진행하면서 화면에 보이는 값과 input 의 상태 값이 같아야 한다는 생각이 강해지면서 결국 useState 로 변경하였다.
// 닉네임 유효성 조건
const NICKNAME_REGEX = /^(?=.*[a-z가-힣])[a-z가-힣0-9]{1,20}$/; // 1~20자, 한글이나 소문자 하나는 반드시 포함, 숫자는 선택적
// 타입에 따라 상태 설정
switch (type) {
case "id":
setId(value);
setIdIsEnterd(isEntered);
handleValidate(value, ID_REGEX, setIdIsValid);
break;
case "nickname":
setNickname(value);
setNicknameIsEnterd(isEntered);
handleValidate(value, NICKNAME_REGEX, setNicknameIsValid);
break;
case "pw":
setPw(value);
setPwIsEnterd(isEntered);
handleValidate(value, PW_REGEX, setPwIsValid);
break;
case "pwCheck":
setPwCheck(value);
setPwCheckIsEnterd(isEntered);
break;
default:
break;
}
};
const handleValidate = (
value: string,
regex: RegExp,
setValidity: React.Dispatch<React.SetStateAction<boolean>>,
) => {
setValidity(regex.test(value)); // 값별로 유효한지 체크
};
그러던 중.. react-hook-form 을 발견했다.
👀 React Hook Form : 빠른 성능과 간편한 사용으로 폼 관리
- form 을 효율적으로 관리, 검증하기 위한 비제어 컴포넌트 기반의 라이브러리
- 상태를 직접 관리하지 않기 때문에 리렌더링을 줄이고 성능을 향상시킨다.
- 높은 유연성으로 다양한 검증 라이브러리와 통합이 가능하다.
npm install react-hook-form or yarn add react-hook-form
😌 내가 느낀 장점
유효성 검사가 정말 간편해졌다.
또한, mode 변경으로 onChange 시 검사를 할 건지 onBlur 시 검사를 할 건지 설정할 수 있다.
input 이 많을 수록 더 편리할 것 같다.
"use client";
import { FieldValues, useForm } from "react-hook-form";
import { collection, addDoc } from "firebase/firestore/lite";
import { db } from "@/shared/firebase";
const AdminPage = () => {
const usersCollection = collection(db, "users");
const { register, handleSubmit, formState } = useForm({
mode: "onChange",
defaultValues: { // 초기값
name: "",
email: "",
password: "",
phone: "",
title: "",
specialization: "",
assigned_leads: "",
},
});
const onSubmit = async (value: FieldValues) => {
const newUser = {
...value,
role: "sherpa",
};
try {
const res = await addDoc(usersCollection, newUser);
console.log(res);
} catch (error) {
console.log(error);
}
};
<form
onSubmit={handleSubmit(onSubmit)}
className="flex flex-col gap-6"
>
<div className="grid grid-cols-4 gap-4">
<div className="flex flex-col gap-2">
<label htmlFor="name" className="w-64 text-left">
Name
</label>
<input
{...register("name", {
required: {
value: true,
message: "필수 정보입니다.",
},
pattern: {
value: /^[가-힣a-zA-Z]{2,20}$/, // 2~20자, 한글, 영어만 허용
message: "2~20자의 한글이나 영어로 작성해 주세요.",
},
})}
type="text"
placeholder="이름"
className="w-64 rounded-lg bg-zinc-600 p-2"
/>
{formState.errors.name && (
<span className="text-sm text-red-600">
{formState.errors.name.message}
</span>
)}
</div>
// ...
<button
disabled={!formState.isValid}
type="submit"
className="w-full self-end rounded-lg bg-zinc-700 p-4"
>
추가하기
</button>
</form>
🔗 https://velog.io/@hyunjine/useState-vs-useRef
useState vs useRef
여러분은 어떻게 생각하시나요?
velog.io
728x90
반응형