필자는 React를 처음 공부하기 시작했을 때, 클래스 컴포넌트의 this.state와 this.setState가 너무 복잡하게 느껴졌다.
그러던 중 함수형 컴포넌트와 함께 등장한 useState라는 것을 알았고 기억을 더듬어 useState에 대해 정리해보고자 한다.
필요했던 순간은 언제였을까??? 🤔
토이 프로젝트에서 사용자의 입력값을 관리해야 했었다.
단순히 변수에 값을 저장하면 리렌더링이 되지 않아 화면이 업데이트되지 않았고, 이때 useState의 필요성을 절실히 느꼈던 것 같다.
1. useState 제대로 알아보기
import { useState } from 'react';
function Counter() {
// count: 현재 상태값
// setCount: 상태를 업데이트하는 함수
const [count, setCount] = useState(0);
return (
<div>
<p>현재 카운트: {count}</p>
<button onClick={() => setCount(count + 1)}>
증가
</button>
</div>
);
}
위의 간단한 코드 예시와 함께 살펴보겠다.
위 코드로 예시를 들자면 useState는 배열 구조분해할당을 통해 두 가지 값을 반환한다.
- 현재 상태값 (count)
- 상태를 업데이트하는 함수 (setCount)
코드에서 중요한 부분을 설명해보자면
- useState(0): 초기값을 0으로 설정한다고 생각하면 된다.
- count: 현재 상태값을 저장하는 변수
- setCount: 상태를 업데이트하는 함수
- onClick={() => setCount(count + 1)}: 버튼 클릭 시 카운트를 1 증가한다는 의미
위와 같이 상태관리를 useState를 통해 해줄 수 있다.
Toggle을 구현한 코드로도 예시를 들 수 있다.
function ToggleButton() {
const [isOn, setIsOn] = useState(false);
const handleToggle = () => {
setIsOn(prev => !prev); // 함수형 업데이트 사용
};
return (
<button
onClick={handleToggle}
style={{ background: isOn ? 'yellow' : 'gray' }}
>
{isOn ? 'ON' : 'OFF'}
</button>
);
}
위에서 보면
- useState(false): 는 초기값을 false로 설정해준다는 것을 이젠 알 수 있을것이다.
- prev => !prev: 이전 상태값을 기반으로 반전된 값으로 업데이트 (토글 on and off 개념) 이라고 생각하면 된다.
위 코드는 조건부 렌더링으로 버튼의 텍스트와 스타일 변경을 한 예시가 될 수 있다.
2. 더 복잡한 코드 함께 살펴보기!
function SignupForm() {
const [formData, setFormData] = useState({
username: '',
email: '',
password: ''
});
const [errors, setErrors] = useState({});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const validateForm = () => {
let newErrors = {};
if (formData.username.length < 3) {
newErrors.username = '사용자명은 3글자 이상이어야 합니다.';
}
if (!formData.email.includes('@')) {
newErrors.email = '올바른 이메일 형식이 아닙니다.';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = (e) => {
e.preventDefault();
if (validateForm()) {
console.log('폼 제출:', formData);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<input
name="username"
value={formData.username}
onChange={handleChange}
placeholder="사용자명"
/>
{errors.username && <p className="error">{errors.username}</p>}
</div>
<div>
<input
name="email"
value={formData.email}
onChange={handleChange}
placeholder="이메일"
/>
{errors.email && <p className="error">{errors.email}</p>}
</div>
<button type="submit">가입하기</button>
</form>
);
}
위와 같은 폼 입력 관리 컴포넌트가 있다고 생각해보자.
위 코드에서는 객체 형태의 폼 데이터 상태 관리를 사용하고 있는데 두 가지가 있다.
모든 입력값을 하나의 객체로 관리하는 formData와 유효성 검사 및 오류 메시지 관리를 하는 errors가 있는 것을 볼 수 있다!
(useState로 초기값 정의와 함께!)
위 코드에서는 두 가지의 함수가 중요한 역할을 하고 있는데 바로 handleChange 함수와 validateForm 함수가 있다.
handleChange 함수는 폼의 입력값이 변경될 때마다 호출되는 이벤트 핸들러이다.
- 이 함수에서는 먼저 e.target에서 name과 value를 구조분해할당으로 추출한다.
🚨여기서의 name은 input 요소의 name 속성값이고, value는 사용자가 입력한 실제 값이다!🚨
예를 들어, 사용자가 username 필드에 "hyunjoong"을 입력하면 name은 "username"이고 value는 "hyunjoong"이 된다. - 그 다음으로 setFormData를 호출할 때 함수형 업데이트를 사용한다.
prev => ({...prev, [name]: value}) 이 구문에서 prev는 이전 상태값을 의미하며, 스프레드 연산자(...)를 사용해 이전 상태의 모든 속성을 새 객체로 복사한다고 보면 된다.
그리고 [name]: value를 통해 변경된 필드만 새로운 값으로 업데이트 함으로써 다른 입력 필드의 값은 그대로 유지하면서 변경된 필드만 업데이트할 수 있다.
validateForm 함수는 폼이 제출되기 전에 모든 입력값의 유효성을 검사한다.
- 빈 객체 newErrors를 생성하고, 각 필드별로 조건을 검사하면서 문제가 있는 경우 해당 필드의 에러 메시지를 newErrors 객체에 추가한다.
- 예시로 username의 경우 길이가 3글자 미만인지, email의 경우엔 '@' 문자를 포함하고 있는지 검사한다고 보면된다.
- 검사가 끝나면 setErrors를 호출하여 에러 상태를 업데이트하고, Object.keys(newErrors).length === 0을 반환한다.
이는 newErrors 객체의 키 개수가 0인지 확인하는 것으로, 에러가 하나도 없으면 true를, 하나라도 있으면 false를 반환한다고 이해하면 될 것 같다.
이 반환값을 통해 handleSubmit 함수에서는 폼 제출을 계속할지 중단할지 결정할 수 있다!!!
앞으로 다른 상태 관리 방법에 대해서도 공부하고 포스팅할 예정이다!
'React' 카테고리의 다른 글
[React] 리액트 컴파일러 개념을 아시나요? 🤔 (2) | 2025.01.18 |
---|---|
[React] Error: can't resolve './reportWebVitals' 문제 해결 (0) | 2025.01.17 |
[React] Quill 에디터로 나만의 텍스트 에디터 만들기 🚀 (2) | 2025.01.15 |
[React] Firebase를 통한 전화번호 인증 시스템 구현하기 (0) | 2025.01.14 |
[React] React 19 출시 : 무엇이 해결되었을까? (0) | 2025.01.11 |