Context API와 Redux, 그리고 Recoil 같은 상태관리 도구의 차이점은 무엇인가요?
언제 Context만으로 충분하고, 언제 외부 상태관리 라이브러리를 써야 할까요?
Context API는 React에서 기본으로 제공하는 전역 상태 관리 도구로, 설정값이나 다크모드, 언어 설정처럼 빈번하게 변경되지 않는 전역 상태를 전달할 때 적합합니다.
Redux는 중앙 저장소(store)를 기반으로 컴포넌트 간 복잡한 상태 공유, 액션 기반의 상태 전이 로직, 미들웨어 연동이 필요한 경우에 더 유리합니다. 특히 디버깅 도구(Redux DevTools)를 활용한 추적성도 장점이에요.
Recoil은 React 기반의 비교적 가벼운 상태 관리 도구로, 비동기 상태(atom), 파생 상태(selector)를 선언적으로 관리할 수 있고, 컴포넌트 간의 느슨한 의존성을 유지하면서도 전역 상태를 간편하게 다룰 수 있다는 점에서 장점이 있습니다.
useEffect에서의 클린업 함수(clean-up function)는 언제 필요하며, 어떤 상황에서 사용하나요?
예시로 setTimeout, 이벤트 리스너, fetch 취소 같은 걸 들어서 설명해보시겠어요?
useEffect에서 클린업 함수는 컴포넌트가 언마운트될 때 실행되는 정리 함수(clean-up)입니다.
주로 setTimeout, setInterval, addEventListener 등 외부 리소스를 사용하는 작업에서는 컴포넌트가 사라질 때 이를 명시적으로 제거하지 않으면 메모리 누수나 원치 않은 동작이 발생할 수 있어요.
useEffect(() => {
// 1. Timer 클린업
const timer = setTimeout(() => {
console.log('실행!');
}, 1000);
return () => clearTimeout(timer); // 클린업!
}, []);
useEffect(() => {
// 2. 이벤트 리스너 클린업
const handleScroll = () => console.log('scroll');
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
useEffect(() => {
// 3. fetch 요청 취소
const controller = new AbortController();
fetch('/api/data', { signal: controller.signal })
.then(response => response.json())
.catch(err => console.log(err));
return () => controller.abort(); // 요청 취소!
}, []);
예를 들어 타이머를 걸어놓고 컴포넌트를 빠르게 이동하면 이전 타이머가 실행돼 버리는 문제를 막기 위해 clearTimeout을 클린업에서 해주는 거죠!
React에서 리스트를 렌더링할 때 key의 중요성은 무엇인가요?
key가 없거나 index를 key로 썼을 때 생길 수 있는 문제를 설명해보세요.
React에서 key는 리스트 렌더링 시 각 항목을 고유하게 식별하기 위한 값입니다.
내부적으로 React는 Virtual DOM을 기반으로 diff 알고리즘을 수행할 때 key를 기준으로 변경된 항목을 찾고, 해당 항목만 리렌더링합니다.
// ❌ 문제가 될 수 있는 경우
{items.map((item, index) => (
<div key={index}>
<input value={item.name} />
</div>
))}
// ✅ 올바른 사용
{items.map(item => (
<div key={item.id}>
<input value={item.name} />
</div>
))}
만약 key가 없거나 index를 key로 사용하게 되면, 아이템의 위치가 바뀌는 경우 불필요한 리렌더링 또는 입력 폼 초기화 같은 예기치 않은 동작이 발생할 수 있습니다.
그래서 가능하면 id 등 고유한 값을 key로 사용하는 것이 바람직합니다! 🎯
서버사이드 렌더링(SSR), 정적 사이트 생성(SSG), 클라이언트 사이드 렌더링(CSR)의 차이를 설명해주세요.
각각 언제 쓰는 게 적절한지, Next.js에서 어떻게 구현되는지도 포함해서!
서버사이드에서 HTML을 미리 렌더링해서 클라이언트에 전달하는 방식으로, 초기 로딩 속도가 빠르고 SEO에 유리합니다.
Next.js 구현: getServerSideProps 활용
export async function getServerSideProps() {
const data = await fetchData();
return { props: { data } };
}
빌드 시점에 정적으로 HTML을 생성해두는 방식으로, 변경이 적은 페이지(예: 블로그, 회사 소개)에 적합합니다.
Next.js 구현: getStaticProps, getStaticPaths 사용
export async function getStaticProps() {
const posts = await getPosts();
return { props: { posts } };
}
브라우저에서 JS가 실행되고 나서야 콘텐츠가 렌더링되는 방식으로, SEO에는 불리하지만 사용자 경험이 유연해서 로그인 페이지나 대시보드처럼 사용자 중심 인터랙션이 많은 페이지에 적합합니다.
| 방식 | 적합한 사용처 | 장점 | 단점 |
|---|---|---|---|
| SSR | 뉴스, 검색 결과 | 빠른 초기 로딩, 좋은 SEO | 서버 부하 |
| SSG | 블로그, 회사 소개 | 매우 빠름, CDN 활용 | 동적 데이터 제한 |
| CSR | 대시보드, 관리자 페이지 | 유연한 UX | 느린 초기 로딩, SEO 불리 |
실제 프로젝트에서는 페이지의 특성에 따라 혼합 렌더링 전략을 사용하는 것이 좋습니다! 🎨
컴포넌트의 렌더링 최적화를 위해 불변성(immutability)이 중요한 이유는 무엇인가요?
왜 객체를 새로 만들어주는 것이 더 좋은 방법인지, 그리고 shallow compare와도 연결해서 말씀해보세요
React는 상태가 바뀌었는지 확인할 때 얕은 비교(shallow compare)를 사용합니다. 이때 참조값이 바뀌지 않으면 React는 "같은 객체네?"라고 판단해서 렌더링을 하지 않게 됩니다.
불변성(immutability)은 "값을 직접 수정하지 않고, 새로운 객체를 만들어서 상태를 바꾼다"는 개념입니다.
// ✅ 불변성 유지
const obj = { a: 1 };
const newObj = { ...obj, a: 2 };
// ❌ 불변성 깨짐
const arr = [1, 2];
arr.push(3); // 직접 수정
setState(arr); // 렌더링 안 될 수 있음!
// ✅ 올바른 방법
setState([...arr, 3]);
const arr1 = [1, 2];
const arr2 = [1, 2];
arr1 === arr2 // false → 새 배열이니까 다르게 인식!
React는 이렇게 ===로 비교해서 "다르면 다시 렌더링하자!" 결정해요.
| 개념 | 설명 |
|---|---|
| 불변성 | 기존 값을 직접 바꾸지 않고 새로운 객체를 만들어 상태 변경 |
| shallow compare | 객체의 참조값만 비교해서 바뀐지 감지 |
| 중요한 이유 | 리렌더링 최적화와 예측 가능한 상태 관리, 버그 예방! |
그래서 기존 객체를 직접 수정하는 방식이 아니라, 새로운 객체를 만들어 상태를 갱신하는 불변성을 지켜야 React가 정확히 변경을 감지하고 필요한 컴포넌트만 리렌더링할 수 있습니다! ⚡