# React 면접에서 가장 자주 묻는 질문 30선: 합격을 위한 완벽 가이드
> 2026년 React 면접에서 가장 자주 출제되는 30가지 질문을 정리했습니다. 상세한 답변과 코드 예제로 React 개발자 합격을 준비할 수 있습니다.
- Published: 2026-01-31
- Updated: 2026-04-21
- Author: SharpSkill
- Tags: react interview, frontend interview, react questions, javascript, technical interview
- Reading time: 18 min
---
React 기술 면접에서는 핵심 개념, 고급 패턴, 그리고 모범 사례에 대한 이해도를 평가합니다. 이 가이드는 가장 자주 출제되는 30가지 질문을 정리하고, 효과적인 준비를 돕기 위해 상세한 답변과 코드 예제를 함께 제공합니다.
> **준비를 위한 조언**
>
> 질문은 난이도 순으로 정리되어 있습니다. 고급 개념을 다루기 전에 기초를 충분히 익히면 보다 체계적인 준비가 가능합니다.
## React 기초
### 1. Virtual DOM이란 무엇이며 React는 왜 사용하는가
Virtual DOM은 실제 DOM을 가볍게 표현한 JavaScript 객체입니다. React는 이 추상화를 통해 인터페이스 업데이트를 최적화합니다.
처리 과정은 세 단계로 이루어집니다. React는 먼저 DOM의 가상 복사본을 만들고, 변경이 발생하면 이전 버전과 비교하며(diffing 알고리즘), 마지막으로 필요한 변경 사항만 실제 DOM에 적용합니다(reconciliation).
```jsx
// Simplified example of the concept
// When state changes, React doesn't recreate the entire DOM
function Counter() {
const [count, setCount] = useState(0)
// Only the span containing count will be updated in the real DOM
// The rest of the component is untouched
return (
Counter
{count}
)
}
```
이러한 방식 덕분에 비용이 큰 DOM 작업을 피할 수 있고, 복잡한 인터페이스에서도 빠른 업데이트가 가능합니다.
### 2. 함수형 컴포넌트와 클래스 컴포넌트의 차이는 무엇인가
함수형 컴포넌트는 props를 받아 JSX를 반환하는 JavaScript 함수입니다. React 16.8부터 훅이 도입되면서 함수형 컴포넌트에서도 상태와 라이프사이클을 다룰 수 있게 되었습니다.
```jsx
// Functional component (recommended)
// More concise, easier to test, supports hooks
function Welcome({ name }) {
const [visits, setVisits] = useState(0)
useEffect(() => {
setVisits(v => v + 1)
}, [])
return
Hello {name}, visit #{visits}
}
// Class component (legacy)
// More verbose, requires this binding
class WelcomeClass extends React.Component {
state = { visits: 0 }
componentDidMount() {
this.setState(prev => ({ visits: prev.visits + 1 }))
}
render() {
return
}
}
```
오늘날에는 함수형 컴포넌트가 표준입니다. 클래스 컴포넌트는 여전히 지원되지만, 신규 프로젝트에서는 권장되지 않습니다.
### 3. JSX는 어떻게 동작하는가
JSX는 JavaScript의 구문 확장으로, 코드 안에 마크업을 작성할 수 있게 해줍니다. HTML이 아니라 JavaScript를 보기 좋게 표현한 것입니다.
```jsx
// What we write (JSX)
const element = (
Title
Paragraph
)
// What Babel compiles (pure JavaScript)
const element = React.createElement(
'div',
{ className: 'container' },
React.createElement('h1', null, 'Title'),
React.createElement('p', null, 'Paragraph')
)
```
HTML과의 차이점으로는 `class` 대신 `className`, `for` 대신 `htmlFor`, 속성에 카멜케이스 사용(`onClick`, `tabIndex`), 자체 닫힘 태그의 명시적 종료 등이 있습니다.
### 4. state와 props의 차이는 무엇인가
Props는 부모 컴포넌트에서 자식 컴포넌트로 전달되는 데이터로, 읽기 전용입니다. State는 컴포넌트 내부의 상태이며 setter를 통해 변경할 수 있습니다.
```jsx
// UserCard.jsx
// name and role are props (immutable)
function UserCard({ name, role }) {
// isExpanded is state (mutable)
const [isExpanded, setIsExpanded] = useState(false)
return (
{name}
{role}
{/* Modifying state triggers a re-render */}
{isExpanded && }
)
}
// Usage
```
기본 원칙은 props는 위에서 아래로(부모에서 자식으로) 흐르고, state는 각 컴포넌트에 국한된다는 점입니다.
### 5. 리스트에서 key가 중요한 이유는 무엇인가
Key는 리스트의 어떤 요소가 변경, 추가, 제거되었는지 React가 식별할 수 있도록 돕습니다. 고유하고 안정적인 key가 없다면 React는 예상치 못한 동작을 보일 수 있습니다.
```jsx
// ❌ Bad practice: index as key
// Problem: if order changes, React loses tracking
{items.map((item, index) => (
))}
// ✅ Good practice: unique and stable identifier
{items.map(item => (
))}
// Concrete example of the problem with indices
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Learn React' },
{ id: 2, text: 'Create a project' }
])
// When deleting the first element with key={index}
// React will think element 0's content changed
// instead of understanding an element was removed
return (
{todos.map(todo => (
{todo.text}
))}
)
}
```
## React Hooks
### 6. useState와 자주 발생하는 함정에 대해 설명한다
`useState`는 함수형 컴포넌트의 로컬 상태를 관리합니다. setter는 값 또는 업데이트 함수를 인자로 받을 수 있습니다.
```jsx
// Declaration with initial value
const [count, setCount] = useState(0)
// ❌ Pitfall: multiple updates in the same cycle
function increment() {
setCount(count + 1) // count = 0, sets 1
setCount(count + 1) // count = 0 still, sets 1
setCount(count + 1) // count = 0 still, sets 1
// Final result: 1 (not 3)
}
// ✅ Solution: use the update function
function incrementCorrect() {
setCount(prev => prev + 1) // 0 → 1
setCount(prev => prev + 1) // 1 → 2
setCount(prev => prev + 1) // 2 → 3
// Final result: 3
}
// ❌ Pitfall: object mutation
const [user, setUser] = useState({ name: 'Alice', age: 25 })
user.age = 26 // Direct mutation, no re-render
// ✅ Solution: create a new object
setUser({ ...user, age: 26 })
// or
setUser(prev => ({ ...prev, age: 26 }))
```
### 7. useEffect는 의존성 배열과 함께 어떻게 동작하는가
`useEffect`는 렌더링 이후에 사이드 이펙트를 실행합니다. 의존성 배열은 이펙트가 언제 다시 실행될지를 제어합니다.
```jsx
// Executed on every render (rare, usually avoid)
useEffect(() => {
console.log('Render completed')
})
// Executed only on mount (equivalent to componentDidMount)
useEffect(() => {
console.log('Component mounted')
// Cleanup on unmount (equivalent to componentWillUnmount)
return () => {
console.log('Component unmounted')
}
}, [])
// Executed when userId changes
useEffect(() => {
async function fetchUser() {
const response = await fetch(`/api/users/${userId}`)
const data = await response.json()
setUser(data)
}
fetchUser()
}, [userId])
// ❌ Missing dependency - subtle bug
useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1) // count is "captured" at its initial value
}, 1000)
return () => clearInterval(timer)
}, []) // count is missing from dependencies
// ✅ Fix with update function
useEffect(() => {
const timer = setInterval(() => {
setCount(prev => prev + 1) // No need for count in deps
}, 1000)
return () => clearInterval(timer)
}, [])
```
> **ESLint 규칙**
>
> 누락된 의존성을 감지하기 위해 `eslint-plugin-react-hooks`를 항상 활성화하는 것이 좋습니다. 이 규칙은 진단하기 어려운 많은 버그를 사전에 방지해 줍니다.
### 8. useMemo와 useCallback은 언제 사용해야 하는가
이 두 훅은 메모이제이션을 통해 불필요한 재계산이나 함수 재생성을 피하는 데 사용됩니다. 다만 과도하게 사용하지 않도록 주의해야 합니다.
```jsx
// useMemo: memoizes a computed value
function ProductList({ products, filter }) {
// Recalculated only if products or filter change
const filteredProducts = useMemo(() => {
console.log('Filtering...')
return products.filter(p => p.category === filter)
}, [products, filter])
return
{filteredProducts.map(p =>
{p.name}
)}
}
// useCallback: memoizes a function
function ParentComponent() {
const [count, setCount] = useState(0)
// Without useCallback, handleClick is recreated on every render
// Causing unnecessary re-renders of ExpensiveChild
const handleClick = useCallback((id) => {
console.log('Clicked:', id)
}, []) // Empty deps = stable function
return (
<>
{count}
{/* React.memo on ExpensiveChild for this to be effective */}
>
)
}
// ❌ Over-optimization: not needed here
const SimpleComponent = () => {
// This calculation is trivial, useMemo adds overhead
const doubled = useMemo(() => 2 * 2, [])
return {doubled}
}
```
이 훅들은 명확한 성능 문제가 확인되었거나, 메모이제이션된 컴포넌트로 전달되는 참조를 안정화해야 할 때만 사용해야 합니다.
### 9. useRef는 어떻게 동작하며 어떤 용도로 사용하는가
`useRef`는 렌더링 사이에서 유지되는 가변 참조를 만듭니다. 값이 바뀌어도 재렌더링은 발생하지 않습니다.
```jsx
// Case 1: Access a DOM element
function TextInput() {
const inputRef = useRef(null)
const focusInput = () => {
inputRef.current.focus()
}
return (
<>
>
)
}
// Case 2: Store a mutable value without re-render
function Timer() {
const [seconds, setSeconds] = useState(0)
const intervalRef = useRef(null)
const start = () => {
// Store the interval ID to be able to stop it
intervalRef.current = setInterval(() => {
setSeconds(s => s + 1)
}, 1000)
}
const stop = () => {
clearInterval(intervalRef.current)
}
return (
{seconds}s
)
}
// Case 3: Keep the previous value
function usePrevious(value) {
const ref = useRef()
useEffect(() => {
ref.current = value
}, [value])
return ref.current
}
```
### 10. useContext를 설명하고 언제 사용하는지 알아본다
`useContext`는 prop drilling 없이 React 컨텍스트에 접근할 수 있게 해줍니다. 테마나 로그인 사용자처럼 전역 데이터에 적합합니다.
```jsx
// 1. Create the context
const ThemeContext = createContext({
theme: 'light',
toggleTheme: () => {}
})
// 2. Create the provider
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light')
const toggleTheme = useCallback(() => {
setTheme(prev => prev === 'light' ? 'dark' : 'light')
}, [])
// Memoize the value to avoid unnecessary re-renders
const value = useMemo(() => ({ theme, toggleTheme }), [theme, toggleTheme])
return (
{children}
)
}
// 3. Use the context
function ThemedButton() {
const { theme, toggleTheme } = useContext(ThemeContext)
return (
)
}
// 4. Wrap the application
function App() {
return (
)
}
```
## 고급 패턴
### 11. Higher-Order Component(HOC)란 무엇인가
HOC는 컴포넌트를 인자로 받아 강화된 새 컴포넌트를 반환하는 함수입니다. 훅이 등장한 이후로 사용 빈도는 줄었지만, 일부 라이브러리에서는 여전히 사용됩니다.
```jsx
// HOC that adds logging
function withLogging(WrappedComponent) {
return function WithLogging(props) {
useEffect(() => {
console.log(`${WrappedComponent.name} mounted with props:`, props)
return () => {
console.log(`${WrappedComponent.name} unmounted`)
}
}, [])
return
}
}
// HOC that handles authentication
function withAuth(WrappedComponent) {
return function WithAuth(props) {
const { user, isLoading } = useAuth()
if (isLoading) return
if (!user) return
return
}
}
// Usage
const ProtectedDashboard = withAuth(Dashboard)
const LoggedButton = withLogging(Button)
```
### 12. Render Props 패턴을 설명한다
Render Props 패턴은 함수 형태의 prop을 통해 컴포넌트 간에 로직을 공유하는 방식입니다.
```jsx
// Component with render prop
function MouseTracker({ render }) {
const [position, setPosition] = useState({ x: 0, y: 0 })
useEffect(() => {
const handleMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY })
}
window.addEventListener('mousemove', handleMove)
return () => window.removeEventListener('mousemove', handleMove)
}, [])
// Call the render function with data
return render(position)
}
// Usage
function App() {
return (
(
Position: {x}, {y}
)}
/>
)
}
// Modern version with custom hook (preferred)
function useMousePosition() {
const [position, setPosition] = useState({ x: 0, y: 0 })
useEffect(() => {
const handleMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY })
}
window.addEventListener('mousemove', handleMove)
return () => window.removeEventListener('mousemove', handleMove)
}, [])
return position
}
function App() {
const { x, y } = useMousePosition()
return
Position: {x}, {y}
}
```
### 13. 커스텀 훅을 만드는 방법
커스텀 훅은 상태가 있는 로직을 컴포넌트 간에 추출하여 재사용할 수 있게 해줍니다.
```jsx
// useLocalStorage.js
function useLocalStorage(key, initialValue) {
// Initialize with localStorage value or default
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key)
return item ? JSON.parse(item) : initialValue
} catch (error) {
console.error(error)
return initialValue
}
})
// Setter wrapper that syncs with localStorage
const setValue = useCallback((value) => {
try {
// Support update functions
const valueToStore = value instanceof Function ? value(storedValue) : value
setStoredValue(valueToStore)
window.localStorage.setItem(key, JSON.stringify(valueToStore))
} catch (error) {
console.error(error)
}
}, [key, storedValue])
return [storedValue, setValue]
}
// Usage
function Settings() {
const [theme, setTheme] = useLocalStorage('theme', 'light')
const [fontSize, setFontSize] = useLocalStorage('fontSize', 16)
return (