지난주에는 클로저, Promise, async/await를 배웠습니다. React에서 가장 많이 쓰이는 JS 개념이기도 합니다. 코드 예시와 비유 중심으로 다시 짚어보겠습니다.
클로저를 비유하면, 함수가 태어날 때 주변 변수를 배낭에 담아가는 것 입니다. 함수가 어디서 실행되든, 태어났을 때 담아간 배낭(렉시컬 환경)은 그대로 들고 다닙니다.
function makeGreeting(greeting) {
// greeting이 배낭에 담김
return function (name) {
// 이 함수가 어디서 실행되든 greeting을 기억
return `${greeting}, ${name}!`;
};
}
React에서의 클로저: useState의 상태값, 이벤트 핸들러 안의
변수가 모두 클로저로 동작합니다. 나중에 React를 배우면 이 개념이 바로
연결됩니다.
// React에서 클로저가 쓰이는 패턴 (미리보기)
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
// handleClick은 count를 클로저로 기억
setCount(count + 1);
};
return <button onClick={handleClick}>{count}</button>;
}
비동기를 비유하면 카페에서 주문하는 것과 같습니다. 주문(요청)을 넣고 번호표(Promise)를 받습니다. 음료가 나올 때까지 다른 일을 하다가, 번호가 불리면(fulfilled) 음료를 받습니다. 만약 재료가 없으면(rejected) "죄송합니다"라는 답을 받습니다.
// Promise = 번호표
const order = fetch("/api/coffee"); // 주문 접수 → 번호표(Promise) 반환
// async/await = 번호표를 들고 기다리기
async function getCoffee() {
try {
const response = await fetch("/api/coffee"); // 기다림
const data = await response.json(); // 음료 수령
return data;
} catch (err) {
console.error("주문 실패:", err); // 재료 없음
}
}
아래 코드의 실행 순서를 맞춰보세요. 콜 스택 → 마이크로태스크(Promise) → 매크로태스크(setTimeout) 순서를 기억하면 됩니다.
console.log("1"); // 콜 스택 — 즉시 실행
setTimeout(() => console.log("2"), 0); // 매크로태스크 큐
Promise.resolve().then(() => console.log("3")); // 마이크로태스크 큐
console.log("4"); // 콜 스택 — 즉시 실행
// 출력 순서: 1, 4, 3, 2
Q1. React의 useState는 왜 객체가 아닌 배열을 반환할까요? 배열 구조 분해로
받으면 어떤 점이 편리한지 설명해보세요.
배열 구조 분해는 변수 이름을 자유롭게 지정할 수 있기
때문입니다. 객체 구조 분해는 프로퍼티 이름이 고정되어 있어 여러 useState를
사용할 때 이름 충돌이 발생합니다.
const [count, setCount] = useState(0)처럼 원하는 이름을 바로 붙일 수
있습니다. 만약 객체였다면 const {"{"} state: count, setState: setCount {"}"}{" "} = useState(0)처럼 매번 이름을 변경해야 합니다.
Q2. setState(prev ={">"} ({"{"} ...prev, count: prev.count + 1 {"}"}))
패턴에서 스프레드(...)를 사용하는 이유를 설명해보세요. 스프레드 없이 {"{"} count: prev.count + 1 {"}"}만 쓰면 어떤 문제가 생길까요?
스프레드를 사용하면
기존 상태의 모든 프로퍼티를 유지하면서 특정 프로퍼티만 덮어쓸 수 있습니다.
스프레드 없이 쓰면 나머지 프로퍼티가 모두 사라집니다.
예를 들어 상태가 {"{"} name: '철수', count: 0 {"}"}일 때 {"{"} count: 1{" "} {"}"}만 쓰면 name이 사라져 {"{"} count: 1 {"}"}만 남습니다. React에서는
상태를 직접 수정(mutation)하지 않고 새 객체를 만드는
불변 업데이트를 원칙으로 합니다.
Q3. user?.address?.city와 user && user.address && user.address.city의
동작이 완전히 같을까요? 차이가 있다면 어떤 경우에 다르게 동작하는지
설명하세요.
정답: user가 0이나 ''처럼 falsy 값일 때 다르게 동작합니다.
&&는 왼쪽이 falsy이면 그 값을 반환합니다. user가 0이면 0 && 0.address는 0을 반환합니다. 하지만 0?.address는 undefined를 반환합니다.
?.는 오직 null과 undefined만 체크하기 때문입니다.
Q4. React 프로젝트에서 named export와 default export를 각각 어떤 상황에 사용하는지, 실제 프로젝트 관례를 기준으로 설명해보세요.
named export는 import 시 자동완성이 잘 되고, 오타를 에디터가 잡아주는 장점도 있습니다.
이번 주는 React로 넘어가기 전 마지막 JS 면접 질문입니다. ES6+ 문법은 React에서 매일 사용하므로, 자신만의 말로 설명하는 연습을 해보세요.
Q1. map과 filter의 차이는 무엇이고, 언제 사용하나요?
둘 다 원본 배열을 변경하지 않고 새 배열을 반환하는 순수 함수입니다.
map: 각 요소를 변환하여 같은 길이의 새
배열을 반환. "모든 요소에 같은 작업 적용"filter: 조건에 맞는 요소만 걸러내서 더
짧거나 같은 길이의 새 배열을 반환. "조건에 맞는 것만 선택"const numbers = [1, 2, 3, 4, 5];
// map — 변환: 모든 요소를 2배로
numbers.map((n) => n * 2); // [2, 4, 6, 8, 10]
// filter — 필터링: 짝수만 선택
numbers.filter((n) => n % 2 === 0); // [2, 4]
// 체이닝: filter 먼저 → map
// 짝수만 골라서(2개) 2배 하기 vs 전체(5개) 2배 후 짝수 고르기
numbers.filter((n) => n % 2 === 0).map n
면접 팁: "React에서 map은 리스트 렌더링에, filter는 조건부 필터링에 매일
사용합니다. 체이닝할 때 filter를 먼저 하면 처리할 요소 수가 줄어들어 더
효율적입니다"라고 연결하면 좋습니다.
구조 분해 할당은 배열이나 객체의 값을 개별 변수로 한 번에 추출하는 문법입니다. React에서는 두 가지 핵심 패턴에 사용됩니다.
// 1. props 수신 — 객체 구조 분해
function UserCard({ name, age, role = "user" }) {
return (
<div>
{name} ({age}) - {role}
</div>
);
}
// 2. useState — 배열 구조 분해
const [count, setCount] = useState(0);
// useState가 [현재값, 변경함수] 배열을 반환하고,
// 구조 분해로 각각 변수에 할당
// 3. API 응답에서 필요한 값만 추출
const { data, error isLoading
면접 팁: "기본값 지정(role = 'user'), 이름 변경(data: userData), Rest
파라미터({ name, ...others })까지 알고 있으면 코드가 훨씬 간결해집니다"라고
답하면 좋습니다.
Q3. 옵셔널 체이닝(?.)과 null 병합 연산자(??)를 설명해주세요. ||와 ??의 차이는?
옵셔널 체이닝(?.): 앞의 값이 null이나 undefined이면
에러 없이 undefined를 반환합니다. 중첩 객체에 안전하게 접근할 때 사용합니다.
null 병합 연산자(??): 왼쪽이 null 또는 undefined일 때만
오른쪽 값을 반환합니다.
const user = null;
// 옵셔널 체이닝 — TypeError 방지
user?.address?.city; // undefined (에러 없음)
// ?. + ?? 조합 — 안전한 접근 + 기본값
user?.profile?.avatar ?? "/default.png"; // "/default.png"
// ?? vs || — 핵심 차이
const count = 0;
count || "기본값"; // "기본값" — 0은 falsy라서 오른쪽 반환
count ?? "기본값"; // 0 — 0은 null/undefined가 아니므로 왼쪽 유지
const name = "";
name || "익명"; // "익명" — 빈 문자열은 falsy
name ?? "익명"; // "" — 빈 문자열은 null/undefined가 아님
면접 팁: "||는 0, 빈 문자열, false도 falsy로 처리합니다. 수량이나 금액처럼
0이 유효한 값인 경우 ??를 써야 의도치 않게 기본값으로 바뀌지 않습니다"라고
설명하면 실무 감각이 있다는 인상을 줍니다.
다음 주부터 드디어 React를 시작합니다! 지금까지 배운 JS 개념들 — 클로저, 비동기, 구조 분해, map/filter, 옵셔널 체이닝 — 이 React에서 어떻게 활용되는지 바로 체감할 수 있을 것입니다.
React는 컴포넌트라는 작은 UI 조각을 조합해서 화면을 만드는 라이브러리입니다. HTML을 JS 안에 쓰는 JSX 문법, 부모에서 자식으로 데이터를 전달하는 props, 컴포넌트가 자신의 값을 기억하는 useState 같은 개념을 배우게 됩니다. JS를 이미 알고 있으니 생각보다 빠르게 적응할 수 있습니다.
아래 챕터를 순서대로 읽어오세요. 코드 예제는 직접 따라 작성해보는 것을 권장합니다.