# 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

Hello {this.props.name}, visit #{this.state.visits}

} } ``` 오늘날에는 함수형 컴포넌트가 표준입니다. 클래스 컴포넌트는 여전히 지원되지만, 신규 프로젝트에서는 권장되지 않습니다. ### 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 (