250x250
Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
Tags
- 과제 진행하기
- mock date
- 연결 요소 제거하기
- 통신망분석
- 리액트네이티브
- 테스트 Date
- JavaScript
- 중첩 점
- 테이블 해시 함수
- 최솟갑 구하기
- 리액트네이티브 엔진
- Jest uuid syntax
- 귤 고르기
- ResizeObserver
- Hermes Engine
- 구름톤
- create-next-app
- 날짜 테스트
- 구름톤챌린지
- nextjs-performance
- 자바스크립트
- jest
- nextjs
- 프로그래머스
- mutationobserver
- 호텔 대실
- 구름톤 챌린지
- 헤르메스 엔진
- Google 애널리틱스
- Leetcode #javascript #알고리즘 #Algorithms #js
Archives
- Today
- Total
나만보는개발공부블로그
useState의 과다사용방지를 위한 정리글 본문
리액트 애플리케이션의 성능을 최적화하려면 상태 관리에 대한 깊은 이해가 필요하다. 이번 글에서 useState를 효과적으로 사용하기 위한 다양한 방법과 정리한 내용들을 기록해볼려한다.
1. useState와 객체 식별성: Set() 객체 다루기
React의 useState는 객체의 내용이 아닌 객체 식별성(reference identity)을 기반으로 리렌더링을 트리거합니다. 이로 인해 Set, Map과 같은 컬렉션 객체를 사용할 때 문제가 발생할 수 있다.
const [set, setSet] = useState(new Set());
return (
<button onClick={() => setSet(set.add('set'))}>
아이템 추가 (작동 안 함)
</button>
)
위 코드에서 set.add('set')은 동일한 Set 객체를 반환하기 때문에 리액트는 상태 변화를 감지하지 못한다.
해결책: 새 객체 참조 생성하기
// 방법 1: 배열로 래핑하기
const [[set], setSet] = useState([new Set()]);
return (
<button onClick={() => setSet([set.add('set')])}>
아이템 추가 (작동함)
</button>
)
// 방법 2: 새 Set 객체 생성하기
const [set, setSet] = useState(new Set());
return (
<button onClick={() => {
const newSet = new Set(set);
newSet.add('set');
setSet(newSet);
}}>
아이템 추가 (작동함)
</button>
)
2. useState 대신 useRef의 올바른 사용법
컴포넌트 내에서 변경은 필요하지만 렌더링을 트리거하지 않아야 하는 값에는 useRef를 사용하는 것이 적합하다.
const count = useRef(0);
const onClick = () => {
count.current += 1;
console.log(`클릭 횟수: ${count.current}`);
}
return (
<div onClick={onClick}>클릭해보세요</div>
)
- useState: UI에 직접 표시되어야 하는 상태나 렌더링에 영향을 주는 데이터
- useRef:
- 내부 계산용 값
- DOM 요소 참조
- 타이머 ID
- 이전 렌더링의 값 저장
- 컴포넌트 리렌더링이 필요 없는 값
3. URL 매개변수를 통한 상태 관리
정렬이나 필터링과 같은 UI 상태를 URL에 저장할 경우 State값으로 리렌더링이 일어나지않고 URL을 통해 데이터를 관리할 수 있다.
1. 페이지 새로고침 후에도 상태 유지
2. 북마크 및 공유 가능한 URL
3.뒤로가기 버튼과 연동
위의 이점들을 가져갈 수 있다.
일반적인 useState를 사용한 방식
// 기존 방식: 컴포넌트 내부 상태
const [sort, setSort] = useState<string | null>(null);
return (
<>
<button onClick={() => setSort('asc')}>오름차순</button>
<button onClick={() => setSort('desc')}>내림차순</button>
</>
)
URL 매개변수를 사용한 개선된 방식
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const sort = searchParams.get('sort');
const createQueryString = useCallback((name: string, value: string) => {
const params = new URLSearchParams(searchParams.toString());
params.set(name, value);
return params.toString();
}, [searchParams]);
return (
<>
<button onClick={() =>
router.push(`${pathname}?${createQueryString('sort', 'asc')}`)
}>
오름차순
</button>
<button onClick={() =>
router.push(`${pathname}?${createQueryString('sort', 'desc')}`)
}>
내림차순
</button>
</>
)
nuqs 라이브러리 활용
복잡한 쿼리 파라미터 관리를 위해 nuqs 라이브러리를 사용할 수 있다.
import { useQueryState } from 'nuqs';
function SortComponent() {
const [sort, setSort] = useQueryState('sort');
return (
<>
<button onClick={() => setSort('asc')}>오름차순</button>
<button onClick={() => setSort('desc')}>내림차순</button>
</>
);
}
4. 데이터 페칭 최적화
데이터 페칭을 useState로 관리할 때 발생하는 일반적인 문제점:
- 로딩/에러 상태 관리를 위한 중복 코드
- 비동기 로직 처리 복잡성
- 캐싱 및 재시도 기능 부재
해결책 1: 서버 컴포넌트 (RSC) 활용
// 서버 컴포넌트
async function ProductDetailsServer({productId, searchParams}) {
const product = await fetchProduct(productId);
const activeTab = searchParams.tab ?? 'description';
return (
<div>
<h1>{product.name}</h1>
<Tabs activeTab={activeTab} product={product} />
</div>
);
}
해결책 2: useTransition 활용
const [isPending, startTransition] = useTransition();
return (
<button
disabled={isPending}
onClick={() => {
startTransition(async () => {
await processCafeData();
});
}}
>
{isPending ? '처리 중...' : '카페 데이터 처리'}
</button>
);
해결책 3: 쿼리 라이브러리 활용 (TanStack Query)
import { useQuery } from '@tanstack/react-query';
function Products() {
const { data, isLoading, error } = useQuery({
queryKey: ['products'],
queryFn: fetchProducts,
});
if (isLoading) return <div>로딩 중...</div>;
if (error) return <div>에러 발생: {error.message}</div>;
return (
<ul>
{data.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
5. 폼 상태 관리 개선
폼 필드마다 useState를 사용하는 방식으로 폼을 구현하면 아래와 같이 많은 state가 발생하는 경우가 발생한다.
const [firstName, setFirstName] = useState('');
const [username, setUsername] = useState('');
const [bio, setBio] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
// 폼 제출 로직
}
return (
<form onSubmit={handleSubmit}>
<input value={firstName} onChange={e => setFirstName(e.target.value)} />
{/* 다른 필드들 */}
</form>
);
해결책 1: FormData API 활용
import { z } from 'zod';
const UserSchema = z.object({
firstName: z.string().min(1, "이름을 입력하세요"),
username: z.string().min(3, "사용자명은 3글자 이상이어야 합니다"),
bio: z.string().optional(),
});
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
try {
const data = UserSchema.parse(Object.fromEntries(formData));
// 유효성 검사 통과, 데이터 처리
console.log(data);
} catch (error) {
// 유효성 검사 실패
console.error(error);
}
}
return (
<form onSubmit={handleSubmit}>
<input name="firstName" />
<input name="username" />
<textarea name="bio" />
<button type="submit">제출</button>
</form>
);
해결책 2: useActionState 활용 (Next.js)
'use client';
import { useActionState } from 'next/client';
import { createUser } from '@/actions/user';
export default function RegisterForm() {
const initialState = { success: false, errors: {} };
const [state, formAction, pending] = useActionState(createUser, initialState);
return (
<form action={formAction}>
<input name="firstName" />
{state.errors?.firstName && <p>{state.errors.firstName}</p>}
{/* 다른 필드들 */}
<button type="submit" disabled={pending}>
{pending ? '제출 중...' : '회원가입'}
</button>
</form>
);
}
해결책 3: 폼 라이브러리 활용
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
export default function RegisterForm() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting }
} = useForm({
resolver: zodResolver(UserSchema)
});
const onSubmit = async (data) => {
// 폼 제출 로직
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("firstName")} />
{errors.firstName && <p>{errors.firstName.message}</p>}
{/* 다른 필드들 */}
<button type="submit" disabled={isSubmitting}>
회원가입
</button>
</form>
);
}
6. 상태 과다 사용 방지하기
많은 관련 상태를 개별적으로 관리하면 코드가 복잡해지고 동기화 문제가 발생할 수 있다.
해결책 1: 객체로 상태 그룹화
// 개별 상태
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [email, setEmail] = useState('');
const [phone, setPhone] = useState('');
// 객체로 그룹화
const [userProfile, setUserProfile] = useState({
firstName: '',
lastName: '',
email: '',
phone: ''
});
// 업데이트 예시
const handleFirstNameChange = (e) => {
setUserProfile(prev => ({
...prev,
firstName: e.target.value
}));
};
해결책 2: Immer 활용
중첩된 객체 상태 업데이트를 간소화하기 위해 Immer를 사용할 수 있다.
import { produce } from 'immer';
const [userProfile, setUserProfile] = useState({
personal: {
firstName: '',
lastName: ''
},
contact: {
email: '',
phone: ''
},
preferences: {
theme: 'light',
notifications: true
}
});
// Immer를 사용한 깔끔한 업데이트
const updateFirstName = (newName) => {
setUserProfile(produce(draft => {
draft.personal.firstName = newName;
}));
};
// 여러 필드 업데이트도 간단하게
const updateContactInfo = (email, phone) => {
setUserProfile(produce(draft => {
draft.contact.email = email;
draft.contact.phone = phone;
}));
};
'Web Development > Front' 카테고리의 다른 글
serverless-image-handler 적용하기 (0) | 2024.02.28 |
---|---|
[RN] react-native-nmap Component 'RCTView' re-registered direct event 'topClick' as a bubbling event 에러 (0) | 2024.01.21 |
[RN] 코드푸시 DevInternalSettings is not public in com.facebook.react.devsupport 에러 해결방법 (0) | 2024.01.17 |
[RN] 리액트네이티브 카카오 소셜 로그인 (0) | 2024.01.14 |
[RN] 리액트 네이티브에서 환경 변수 처리 (1) | 2024.01.09 |