함수형 컴포넌트 내부에서 상태를 정의하고, 해당 상태를 관리할 수 있게 해주는 훅
import { useState } from 'react’
const [state, setState] = useState(initialState);
undefinedstate 값 자체를 사용setState 함수를 사용해 해당 state의 값 변경 가능setState가 호출된 이후에도 지역변수인 state 참조 가능setState를 사용할 때는 함수형 업데이트 방식을 사용하는 것이 좋음상태를 업데이트할 때 이전 상태값에 기반하여 새로운 값을 계산하는 방식
const [count, setCount] = useState(0);
// 일반적인 업데이트 방식
setCount(1);
// 함수형 업데이트 방식
setCount((prevCount) => prevCount + 1);
setState는 비동기적으로 실행되기 때문에, 이전 상태를 참조해서 업데이트할 필요가 있을 때 함수형 업데이트 방식이 유용setState에 함수를 전달하면 자동으로 이전 상태값을 인자로 넘겨줌const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
setCount(count + 1);
};
// 기대값: 클릭 시 count 2씩 증가
// 실제값: count는 1씩 증가함
react는 성능 최적화를 위해 여러 개의 상태 업데이트를 한 번에 처리
⇒ 동일한 이벤트 루프 안에서 발생하는 상태 업데이트는 하나로 묶어서 처리
setState 호출 직후에 state 값이 곧바로 업데이트되지 않기 때문에 바로 state 값을 참조하면 예기치 않은 동작이 생길 수 있음
특히 상태가 빠르게 연속해서 변경되거나, 이벤트 핸들러 안에서 여러 번 상태 변경이 일어나는 경우에는 이전 상태값이 꼬일 수 있음
const [items, setItems] = useState([]);
const addItem = (newItem) => {
setItems((prevItems) => [...prevItems, newItem]);
};
const [user, setUser] = useState({ name: 'Lee', age: 25 });
const updateAge = () => {
setUser((prevUser) => ({
...prevUser,
age: prevUser.age + 1,
}));
};
const [form, setForm] = useState({
name: '',
email: '',
password: '',
});
const handleChange = (e) => {
const { name, value } = e.target;
setForm((prevForm) => ({
...prevForm,
[name]: value,
}));
};
useState에 변수 대신 함수를 넘기는 것
// 일반적인 useState사용
// 바로 값을 집어넣음
const [count, setCount] = useState(
Number.parselnt(window.localStorage.getltem(cacheKey)),
)
// 게으른 초기화
// 위 코드와의 차이접은 함수를 실행해 값을 반환
const [count , setCount] = useState ( () =>
Number.parselnt(window.localStorage.getltem(cacheKey)),
)
useState를 대체할 수 있는 함수로 복잡한 상태관리가 필요한 경우 사용
import React, { useReducer } from "react";
const [state, dispatch] = useReducer(reducer, initialState, init);
| 용어 | 설명 |
|---|---|
| state | 컴포넌트에서 사용할 상태 |
| dispatch 함수 | reducer 함수를 실행하는 함수컴포넌트 내에서 state를 업데이트할 때 사용 |
| reducer 함수 | 현재 state와 action을 받아 새로운 state를 반환하는 함수컴포넌트 외부에서 상태 업데이트 로직을 정의 |
| initialState | 초기 상태값 |
| init | 초기 상태를 지연 생성할 때 사용하는 함수 (lazy initialization에 사용됨) |
복잡한 상태 로직
ex) 대규모 폼에서 각 필드의 상태나 유효성 검사 처리
상태 변화가 여러 단계로 이루어지는 경우
ex) 로딩 상태, 성공 상태, 실패 상태 등을 다룰 때
상태 변화를 추적하고 싶을 때:
ex) 액션 타입에 따라 다르게 처리되는 복잡한 로직이 있을 때
const initialState = { count: 0, isLoading: false, error: null };
const reducer = (state, action) => {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + 1 };
case 'decrement':
return { ...state, count: state.count - 1 };
case 'loading':
return { ...state, isLoading: true };
case 'error':
return { ...state, isLoading: false, error: action.payload };
default:
return state;
}
};
const [state, dispatch] = useReducer(reducer, initialState);
const handleIncrement = () => {
dispatch({ type: 'increment' });
};
const handleDecrement = () => {
dispatch({ type: 'decrement' });
};
const handleLoading = () => {
dispatch({ type: 'loading' });
};
const handleError = (error) => {
dispatch({ type: 'error', payload: error });
};
return (
<div>
<button onClick={handleIncrement}>Increment</button>
<button onClick={handleDecrement}>Decrement</button>
<button onClick={handleLoading}>Start Loading</button>
<button onClick={() => handleError('Something went wrong!')}>Simulate Error</button>
<p>Count: {state.count}</p>
<p>{state.isLoading ? 'Loading...' : ''}</p>
<p>{state.error ? `Error: ${state.error}` : ''}</p>
</div>
);
count, isLoading, error)를 하나의 useReducer에서 처리클로저를 활용하여 state를 관리하는 것은 같음
- useState: 상태 업데이트가 직관적이고 간단하지만, 상태 관리가 복잡해지면 코드가 길어지거나 불편해질 수 있음
- useReducer: 상태가 여러 개일 때나 복잡한 로직이 필요한 경우, 액션을 통해 상태를 변경할 수 있어 구조적이고 관리가 쉬워짐
| 항목 | useState | useReducer |
|---|---|---|
| 목적 | 간단한 상태 관리 | 복잡한 상태 관리 (여러 상태 변화를 다룰 때 유용) |
| 상태 업데이트 | 직접적인 값 변경 (setState) | 액션을 통해 상태를 업데이트 (dispatch) |
| 초기화 방법 | 초기값을 useState(initialValue)로 직접 설정 | useReducer(reducer, initialState, init)으로 설정 |
| 상태 관리 방식 | useState는 각 상태가 독립적이고 간단함 | useReducer는 복잡한 상태를 하나의 상태 객체로 관리 |
| 상태 업데이트 흐름 | 상태 업데이트가 즉시 적용 | reducer 함수에서 상태를 반환하는 방식으로 관리 |
| 주요 사용 예시 | 단순한 상태 (카운터, 텍스트 필드 등) | 여러 상태 변화가 있는 복잡한 애플리케이션 (폼 상태, 리스트 관리 등) |
useState 예시
const [count, setCount] = useState(0);
const increment = () => {
setCount((prev)=> prev + 1);
};
return <button onClick={increment}>Count: {count}</button>;
useReducer 예시
const initialState = { count: 0 };
const reducer = (state, action) => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
default:
return state;
}
};
const [state, dispatch] = useReducer(reducer, initialState);
return <button onClick={() => dispatch({ type: 'increment' })}>Count: {state.count}</button>;