React 요약 16 - 면접 질문 모음
Updated:
React 기초 개념
-
React란 무엇이며, 다른 프레임워크와 비교했을 때 장점은 무엇인가요?
React는 Facebook에서 개발한 UI 라이브러리로, 컴포넌트 기반 아키텍처를 사용하여 재사용 가능한 UI 요소를 만들 수 있게 해줍니다. 주요 장점으로는 가상 DOM을 통한 효율적인 렌더링, 단방향 데이터 흐름, 선언적 프로그래밍 방식, 큰 생태계와 커뮤니티 등이 있습니다. Angular와 달리 전체 프레임워크가 아닌 UI 라이브러리이며, Vue보다 더 큰 생태계와 기업 지원을 가지고 있습니다.
-
가상 DOM이란 무엇이며, 어떻게 작동하나요?
가상 DOM은 실제 DOM의 가벼운 복사본으로, React가 메모리에 유지하는 객체입니다. React는 상태가 변경될 때 새로운 가상 DOM 트리를 생성하고, 이전 가상 DOM과 비교(diffing)하여 실제로 변경된 부분만 실제 DOM에 적용합니다. 이 과정을 재조정(Reconciliation)이라고 하며, 불필요한 DOM 조작을 최소화하여 성능을 최적화합니다.
-
JSX란 무엇인가요?
JSX는 JavaScript XML의 약자로, JavaScript 내에서 HTML과 유사한 마크업을 작성할 수 있게 해주는 문법적 확장입니다. React 컴포넌트의 구조를 직관적으로 표현할 수 있게 해주며, 실제로는
React.createElement()
함수 호출로 변환됩니다. JSX는 선택사항이지만, 대부분의 React 개발자가 사용하는 표준 방식입니다. -
컴포넌트란 무엇이며, 어떤 종류가 있나요?
컴포넌트는 React 애플리케이션의 기본 구성 요소로, UI의 독립적이고 재사용 가능한 부분입니다. 함수형 컴포넌트와 클래스 컴포넌트 두 가지 유형이 있습니다. 함수형 컴포넌트는 props를 입력으로 받아 JSX를 반환하는 JavaScript 함수이며, Hooks의 도입 이후 더 많이 사용됩니다. 클래스 컴포넌트는 React.Component를 상속받는 ES6 클래스로, 더 많은 기능(생명주기 메서드 등)을 제공하지만 코드가 더 복잡합니다.
-
Props와 State의 차이점은 무엇인가요?
Props(속성)는 부모 컴포넌트에서 자식 컴포넌트로 전달되는 읽기 전용 데이터입니다. 컴포넌트는 자신의 props를 수정할 수 없습니다(불변성). 반면, State(상태)는 컴포넌트 내부에서 관리되는 데이터로, 컴포넌트의 생명주기 동안 변경될 수 있습니다. State가 변경되면 컴포넌트가 다시 렌더링됩니다. 간단히 말해, props는 “외부에서 전달받는 데이터”, state는 “내부에서 관리하는 데이터”입니다.
-
React의 단방향 데이터 흐름이란 무엇인가요?
React의 단방향 데이터 흐름(one-way data flow)은 데이터가 부모 컴포넌트에서 자식 컴포넌트로만 전달된다는 개념입니다. 자식 컴포넌트는 props를 통해 데이터를 받기만 하고, 직접 수정할 수 없습니다. 자식 컴포넌트가 데이터를 변경하려면 부모 컴포넌트에서 전달받은 콜백 함수를 호출해야 합니다. 이 패턴은 애플리케이션의 데이터 흐름을 예측 가능하게 만들고, 디버깅을 용이하게 합니다.
-
제어 컴포넌트와 비제어 컴포넌트의 차이점은 무엇인가요?
제어 컴포넌트(Controlled Component)는 React 상태에 의해 값이 제어되는 폼 요소입니다. 입력값이 변경될 때마다 상태가 업데이트되고, 렌더링에 반영됩니다. 비제어 컴포넌트(Uncontrolled Component)는 DOM 자체에 의해 값이 관리되는 폼 요소로, React에서는 ref를 사용하여 필요할 때만 DOM에서 값을 가져옵니다. 제어 컴포넌트는 더 많은 제어와 유효성 검사를 제공하지만, 비제어 컴포넌트는 더 간단하고 특정 상황(파일 입력 등)에 유용할 수 있습니다.
Hooks와 상태 관리
-
React Hooks란 무엇이며, 왜 도입되었나요?
React Hooks는 함수형 컴포넌트에서 상태와 생명주기 기능을 사용할 수 있게 해주는 함수들입니다. React 16.8에서 도입되었으며, 클래스 컴포넌트 없이도 React의 모든 기능을 사용할 수 있게 해줍니다. Hooks가 도입된 주요 이유는 컴포넌트 간 상태 로직 재사용의 어려움, 복잡한 컴포넌트의 이해 어려움, 클래스의 혼란스러운 개념(this 바인딩 등) 등의 문제를 해결하기 위함입니다.
-
자주 사용되는 Hooks와 그 용도를 설명해주세요.
useState
: 함수형 컴포넌트에서 상태 관리useEffect
: 부수 효과 처리 (데이터 페칭, 구독, DOM 조작 등)useContext
: Context API를 통한 전역 상태 접근useReducer
: 복잡한 상태 로직 관리useCallback
: 함수 메모이제이션useMemo
: 계산 결과 메모이제이션useRef
: DOM 요소 참조 또는 렌더링 사이에 값 유지useLayoutEffect
: DOM 변경 후, 브라우저 페인팅 전에 실행되는 효과useImperativeHandle
: ref로 노출되는 인스턴스 값 사용자 정의useDebugValue
: 개발자 도구에서 커스텀 Hook 레이블 표시
-
useEffect의 작동 방식과 의존성 배열의 역할은 무엇인가요?
useEffect
는 컴포넌트 렌더링 후에 부수 효과를 실행하는 Hook입니다. 첫 번째 인자로 효과 함수를, 두 번째 인자로 의존성 배열을 받습니다. 의존성 배열은 효과가 다시 실행되어야 하는 조건을 지정합니다: - 빈 배열([]
)을 전달하면 컴포넌트 마운트 시에만 효과 실행 - 특정 값을 포함하면 해당 값이 변경될 때마다 효과 실행 - 배열을 생략하면 모든 렌더링 후에 효과 실행효과 함수에서 정리(cleanup) 함수를 반환하면, 컴포넌트 언마운트 시 또는 다음 효과 실행 전에 호출됩니다.
-
커스텀 Hook이란 무엇이며, 어떤 경우에 사용하나요?
커스텀 Hook은 로직을 재사용 가능한 함수로 추출한 것으로, 이름이 “use”로 시작하는 JavaScript 함수입니다. 내부에서 다른 React Hook을 사용할 수 있으며, 여러 컴포넌트에서 동일한 상태 로직을 공유할 때 유용합니다. 예를 들어, 데이터 페칭, 폼 처리, 애니메이션 등의 로직을 커스텀 Hook으로 추출하여 재사용할 수 있습니다.
-
Context API란 무엇이며, 언제 사용해야 하나요?
Context API는 컴포넌트 트리 전체에 데이터를 전달하는 방법으로, props drilling(여러 단계의 컴포넌트를 통해 props를 전달하는 것) 없이 데이터를 공유할 수 있게 해줍니다.
createContext
,Provider
,useContext
로 구성되며, 테마, 로그인 상태, 언어 설정 등 여러 컴포넌트에서 필요한 전역 데이터에 적합합니다. 그러나 자주 변경되는 상태나 복잡한 상태 로직에는 Redux나 다른 상태 관리 라이브러리가 더 적합할 수 있습니다. -
Redux와 같은 상태 관리 라이브러리가 필요한 이유는 무엇인가요?
Redux와 같은 상태 관리 라이브러리는 다음과 같은 경우에 유용합니다: - 애플리케이션 전체에서 공유되는 복잡한 상태 관리 - 깊은 컴포넌트 트리에서의 상태 전달 간소화 - 예측 가능한 상태 업데이트 (순수 함수인 리듀서 사용) - 시간 여행 디버깅, 상태 지속성, 미들웨어 등의 고급 기능 - 상태 변경 로직과 UI 로직의 분리
작은 애플리케이션에서는 Context API와 useReducer로 충분할 수 있지만, 규모가 커지고 상태 관리가 복잡해질수록 전용 라이브러리의 이점이 커집니다.
성능 최적화
-
React에서 성능을 최적화하는 방법에는 어떤 것들이 있나요?
React 애플리케이션의 성능 최적화 방법: -
React.memo
로 불필요한 리렌더링 방지 -useMemo
와useCallback
으로 계산 비용이 많이 드는 연산이나 함수 메모이제이션 - 상태 구조 최적화 (정규화된 상태, 불변성 유지) - 가상화(virtualization)로 대량의 데이터 효율적 렌더링 - 코드 분할(Code Splitting)과 지연 로딩(Lazy Loading) - 이미지와 자산 최적화 - 서버 사이드 렌더링(SSR) 또는 정적 사이트 생성(SSG) 활용 - 웹 워커를 사용한 무거운 계산 오프로딩 - 효율적인 이벤트 핸들러 관리 (이벤트 위임 등) -
React.memo, useMemo, useCallback의 차이점은 무엇인가요?
React.memo
: 고차 컴포넌트(HOC)로, props가 변경되지 않으면 컴포넌트의 리렌더링을 방지합니다.useMemo
: Hook으로, 의존성 배열의 값이 변경될 때만 계산 비용이 많이 드는 값을 재계산합니다.-
useCallback
: Hook으로, 의존성 배열의 값이 변경될 때만 함수를 재생성합니다.React.memo
는 컴포넌트 자체를 메모이제이션하는 반면,useMemo
는 값을,useCallback
은 함수를 메모이제이션합니다.useCallback(fn, deps)
는useMemo(() => fn, deps)
와 동일합니다.
-
불필요한 리렌더링을 방지하는 방법은 무엇인가요?
불필요한 리렌더링 방지 방법: - 컴포넌트 분할: 상태 변경의 영향을 받는 부분만 분리 -
React.memo
로 함수형 컴포넌트 메모이제이션 -shouldComponentUpdate
또는PureComponent
사용 (클래스 컴포넌트) -useMemo
와useCallback
으로 props로 전달되는 객체와 함수 메모이제이션 - 상태 업데이트 최적화 (불필요한 상태 업데이트 방지) - Context 분할: 자주 변경되는 값과 그렇지 않은 값 분리 - 키(key) 속성 올바르게 사용: 리스트 렌더링 시 안정적인 고유 키 제공 -
React에서 대량의 데이터를 효율적으로 렌더링하는 방법은 무엇인가요?
대량 데이터 효율적 렌더링 방법: - 가상화(Virtualization): 화면에 보이는 항목만 렌더링 (react-window, react-virtualized 등 사용) - 페이지네이션: 한 번에 일부 데이터만 로드 및 표시 - 무한 스크롤: 사용자가 스크롤할 때 추가 데이터 로드 - 데이터 정규화: 중복 없이 효율적인 상태 구조 유지 - 메모이제이션: 계산 비용이 많이 드는 변환이나 필터링 최적화 - 지연 로딩: 필요할 때만 컴포넌트나 데이터 로드 - 웹 워커: 무거운 데이터 처리를 별도 스레드로 오프로딩
아키텍처와 패턴
-
컴포넌트 구성(Composition)이란 무엇이며, 상속보다 선호되는 이유는 무엇인가요?
컴포넌트 구성은 여러 컴포넌트를 조합하여 새로운 컴포넌트를 만드는 방식입니다. React에서는
children
props나 특정 props를 통해 구성을 구현합니다. 상속보다 구성이 선호되는 이유는: - 더 유연하고 명시적인 코드 작성 가능 - 컴포넌트 간 결합도 감소 - 기능 재사용성 향상 - 테스트와 디버깅이 더 쉬움 - React의 단방향 데이터 흐름 모델과 더 잘 맞음React 팀은 수천 개의 컴포넌트를 사용하면서도 컴포넌트 상속을 권장할 사례를 찾지 못했다고 언급했습니다.
-
고차 컴포넌트(HOC)란 무엇이며, 언제 사용하나요?
고차 컴포넌트(Higher-Order Component, HOC)는 컴포넌트를 인자로 받아 새 컴포넌트를 반환하는 함수입니다. 이는 컴포넌트 로직을 재사용하기 위한 React의 고급 패턴입니다. HOC는 다음과 같은 경우에 유용합니다: - 여러 컴포넌트에서 공통 기능 공유 (인증, 데이터 구독 등) - 크로스커팅 관심사(cross-cutting concerns) 분리 - 기존 컴포넌트 기능 확장 - 조건부 렌더링 로직 추상화
예:
withRouter
,connect
(Redux),withStyles
등이 HOC 패턴을 사용합니다. -
Render Props 패턴이란 무엇인가요?
Render Props는 컴포넌트 간에 값을 공유하기 위해 함수 props를 사용하는 패턴입니다. 이 함수는 컴포넌트가 무엇을 렌더링할지 결정하는 데 사용됩니다. 일반적으로
render
또는children
prop으로 함수를 전달합니다. 이 패턴은 다음과 같은 경우에 유용합니다: - 상태나 동작을 캡슐화하면서 렌더링 로직을 유연하게 제어 - 컴포넌트 간 코드 재사용 - HOC의 대안으로 사용 (중첩 구조 없이 기능 공유)예:
<Route render={(props) => <MyComponent {...props} />} />
(React Router) -
Flux 아키텍처와 Redux의 관계는 무엇인가요?
Flux는 Facebook이 제안한 애플리케이션 아키텍처로, 단방향 데이터 흐름을 강조합니다. Redux는 Flux의 개념을 기반으로 하지만, 몇 가지 차이점이 있습니다:
공통점: - 단방향 데이터 흐름 - 액션을 통한 상태 변경 - 상태의 중앙 집중화
차이점: - Flux는 여러 스토어를 가질 수 있지만, Redux는 단일 스토어 사용 - Redux는 리듀서(순수 함수)를 사용하여 상태 업데이트, Flux는 각 스토어가 자체 로직 포함 - Redux는 불변성 강조 - Redux는 미들웨어 시스템 제공
Redux는 Flux의 구현체라기보다 Flux 패턴에서 영감을 받은 라이브러리입니다.
-
Container와 Presentational 컴포넌트 패턴이란 무엇인가요?
Container/Presentational 패턴은 관심사 분리를 위한 컴포넌트 설계 방식입니다:
- Container 컴포넌트:
- 데이터 페칭, 상태 관리, 비즈니스 로직 처리
- Redux 등의 상태 관리 라이브러리와 연결
- 일반적으로 스타일링 없음
- “어떻게 동작하는지”에 집중
- Presentational 컴포넌트:
- UI 렌더링에만 집중
- props를 통해 데이터와 콜백 함수 받음
- 자체 상태는 UI 상태만 관리 (있는 경우)
- 재사용 가능하고 스타일링 포함
- “어떻게 보이는지”에 집중
Hooks의 등장으로 이 패턴의 필요성이 줄었지만, 여전히 큰 애플리케이션에서 유용한 구조화 방법입니다.
라우팅과 서버 통신
-
클라이언트 사이드 라우팅이란 무엇이며, React에서 어떻게 구현하나요?
클라이언트 사이드 라우팅은 페이지 전체를 다시 로드하지 않고 JavaScript를 사용하여 URL 변경에 따라 화면을 업데이트하는 방식입니다. 이는 더 나은 사용자 경험과 성능을 제공합니다. React에서는 주로 React Router 라이브러리를 사용하여 구현합니다:
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom'; function App() { return ( <BrowserRouter> <nav> <Link to="/">Home</Link> <Link to="/about">About</Link> </nav> <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> <Route path="/users/:userId" element={<UserDetail />} /> </Routes> </BrowserRouter> ); }
React Router는
BrowserRouter
,Routes
,Route
,Link
등의 컴포넌트와useParams
,useNavigate
,useLocation
같은 Hooks를 제공합니다. -
React에서 API 호출을 처리하는 가장 좋은 방법은 무엇인가요?
React에서 API 호출을 처리하는 방법:
useEffect
Hook 내에서 API 호출:useEffect(() => { const fetchData = async () => { try { setLoading(true); const response = await fetch(url); const data = await response.json(); setData(data); } catch (error) { setError(error); } finally { setLoading(false); } }; fetchData(); }, [url]);
- 커스텀 Hook으로 API 로직 추출:
function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { // 페칭 로직 }, [url]); return { data, loading, error }; }
- React Query, SWR 같은 데이터 페칭 라이브러리 사용:
const { data, isLoading, error } = useQuery(['users', userId], () => fetchUser(userId) );
-
Redux와 미들웨어(Redux Thunk, Redux Saga) 사용
- React 18의 Suspense for Data Fetching 활용
최근에는 React Query나 SWR 같은 라이브러리가 캐싱, 재시도, 백그라운드 업데이트 등의 기능을 제공하여 널리 사용됩니다.
-
서버 사이드 렌더링(SSR)이란 무엇이며, 어떤 이점이 있나요?
서버 사이드 렌더링(SSR)은 서버에서 React 컴포넌트를 HTML로 렌더링하여 클라이언트에 전송하는 기술입니다. 이후 클라이언트에서 JavaScript가 로드되면 이 HTML에 이벤트 리스너를 연결하는 “하이드레이션(hydration)” 과정이 진행됩니다.
SSR의 이점: - 더 빠른 초기 로딩 및 First Contentful Paint (FCP) - 검색 엔진 최적화(SEO) 개선 - 소셜 미디어 공유 시 메타데이터 제공 - 느린 기기나 네트워크에서 더 나은 사용자 경험 - 접근성 향상
Next.js, Remix 같은 프레임워크는 React 애플리케이션의 SSR을 쉽게 구현할 수 있게 해줍니다.
-
React Query나 SWR 같은 데이터 페칭 라이브러리의 장점은 무엇인가요?
데이터 페칭 라이브러리의 장점: - 캐싱: 동일한 데이터에 대한 중복 요청 방지 - 자동 재검증: 포커스, 네트워크 재연결 등의 이벤트 시 데이터 갱신 - 낙관적 업데이트: UI를 즉시 업데이트하고 서버 응답 후 확인 - 페이지네이션 및 무한 스크롤 지원 - 요청 중복 제거 및 재시도 로직 - 백그라운드 업데이트: 오래된 데이터 표시 후 새 데이터로 업데이트 - 서버 상태와 클라이언트 상태의 명확한 구분 - 로딩 및 오류 상태 관리 간소화 - 의존적 쿼리: 다른 쿼리 결과에 따라 쿼리 실행
이러한 라이브러리는 복잡한 데이터 페칭 로직을 추상화하여 개발자가 비즈니스 로직에 집중할 수 있게 해줍니다.
테스팅과 디버깅
-
React 컴포넌트를 테스트하는 방법에는 어떤 것들이 있나요?
React 컴포넌트 테스트 방법:
- 단위 테스트:
- Jest + React Testing Library 또는 Enzyme 사용
- 컴포넌트의 렌더링, 이벤트 처리, 상태 변경 등 테스트
test('increments counter when button is clicked', () => { render(<Counter />); const button = screen.getByText('Increment'); fireEvent.click(button); expect(screen.getByText('Count: 1')).toBeInTheDocument(); });
-
통합 테스트: - 여러 컴포넌트의 상호작용 테스트 - 실제 API 대신 모의(mock) 서비스 사용
- 스냅샷 테스트:
- 컴포넌트 출력의 스냅샷을 저장하고 변경 사항 감지
test('renders correctly', () => { const tree = renderer.create(<Button>Click me</Button>).toJSON(); expect(tree).toMatchSnapshot(); });
-
E2E(End-to-End) 테스트: - Cypress, Playwright, Selenium 등 사용 - 실제 브라우저에서 사용자 흐름 테스트
-
시각적 회귀 테스트: - Storybook + Chromatic 등 사용 - UI 변경 사항 시각적으로 감지
-
React 애플리케이션에서 일반적인 성능 문제를 디버깅하는 방법은 무엇인가요?
React 성능 문제 디버깅 방법:
-
React DevTools Profiler 사용: - 컴포넌트 렌더링 시간 측정 - 불필요한 리렌더링 식별 - 커밋 간 변경 사항 확인
-
Chrome DevTools Performance 탭: - JavaScript 실행 시간 분석 - 메모리 사용량 모니터링 - 병목 현상 식별
-
why-did-you-render
라이브러리: - 불필요한 리렌더링 자동 감지 및 로깅 -
코드 분할 분석: - 번들 크기 분석 (webpack-bundle-analyzer) - 큰 번들 식별 및 분할
- 메모리 누수 확인:
- Chrome DevTools Memory 탭
- 컴포넌트 언마운트 후 이벤트 리스너나 타이머가 정리되지 않는지 확인
- 네트워크 요청 최적화:
- Network 탭에서 불필요하거나 중복된 요청 식별
- 큰 페이로드 확인
- 로깅 및 에러 추적:
- 콘솔 로그 및 에러 모니터링
- Sentry 같은 도구로 프로덕션 에러 추적
-
React에서 발생하는 메모리 누수의 일반적인 원인과 해결 방법은 무엇인가요?
React 메모리 누수 원인과 해결 방법:
- 정리되지 않은 이벤트 리스너:
useEffect(() => { window.addEventListener('resize', handleResize); // 정리 함수 반환 return () => { window.removeEventListener('resize', handleResize); }; }, []);
- 취소되지 않은 API 요청:
useEffect(() => { let isMounted = true; const fetchData = async () => { const response = await fetch(url); const data = await response.json(); if (isMounted) setData(data); }; fetchData(); return () => { isMounted = false; }; }, [url]);
- 정리되지 않은 타이머:
useEffect(() => { const timerId = setInterval(() => { // 작업 수행 }, 1000); return () => { clearInterval(timerId); }; }, []);
-
클로저로 인한 오래된 참조: - 최신 값을 참조하기 위해 useRef 또는 함수형 업데이트 사용
-
순환 참조 구조: - 객체 간 순환 참조 피하기 - WeakMap/WeakSet 사용 고려
-
React 애플리케이션에서 접근성(a11y)을 개선하는 방법은 무엇인가요?
React 접근성 개선 방법:
-
시맨틱 HTML 사용: -
<div>
대신<button>
,<a>
,<nav>
등 적절한 요소 사용 - ARIA 속성 올바르게 적용 - 키보드 접근성:
- 모든 상호작용 요소가 키보드로 접근 가능하게 함
- 포커스 관리 및 포커스 트랩 구현
const modalRef = useRef(); useEffect(() => { modalRef.current?.focus(); }, [isOpen]);
-
색상 대비 및 텍스트 크기: - WCAG 지침 준수 - 충분한 색상 대비 제공
- 스크린 리더 지원:
- 이미지에 대체 텍스트 제공
- 상태 변경 시 적절한 알림
<img src="logo.png" alt="Company Logo" /> <div aria-live="polite">{statusMessage}</div>
-
도구 활용: - ESLint의 jsx-a11y 플러그인 - Lighthouse, axe 등의 접근성 감사 도구 - React Testing Library의 접근성 중심 쿼리
- 폼 레이블 및 오류 메시지:
- 모든 입력 필드에 레이블 연결
- 오류 메시지를 프로그래밍 방식으로 연결
<label htmlFor="name">Name:</label> <input id="name" aria-describedby="name-error" /> <div id="name-error">{errors.name}</div>
최신 트렌드와 고급 개념
-
React 18의 주요 새 기능은 무엇인가요?
React 18의 주요 기능:
-
자동 배치 처리 개선: - 이벤트 핸들러 외부에서도 상태 업데이트 배치 처리
-
동시성(Concurrency) 기능: -
useTransition
: 우선순위가 낮은 상태 업데이트 표시 -useDeferredValue
: 값의 업데이트를 지연시켜 UI 응답성 유지 -
서버 컴포넌트: - 서버에서만 실행되는 컴포넌트로 번들 크기 감소 - 서버 리소스에 직접 접근 가능
-
Suspense 개선: - 데이터 페칭을 위한 Suspense 지원 - SSR에서 Suspense 지원
-
새로운 Hooks: -
useId
: 접근성을 위한 고유 ID 생성 -useSyncExternalStore
: 외부 스토어와 동기화 -useInsertionEffect
: CSS-in-JS 라이브러리용 -
새로운 Root API: -
createRoot
를 통한 새로운 렌더링 방식 -
Strict Mode 개선: - 개발 모드에서 컴포넌트 이중 마운트로 부작용 감지
-
서버 컴포넌트란 무엇이며, 어떤 이점이 있나요?
서버 컴포넌트는 서버에서만 실행되고 클라이언트로 HTML 결과만 전송되는 새로운 유형의 React 컴포넌트입니다. 주요 이점:
-
번들 크기 감소: - 서버 컴포넌트의 코드는 클라이언트로 전송되지 않음 - 큰 의존성을 서버에만 유지 가능
-
서버 리소스 직접 접근: - 데이터베이스, 파일 시스템 등에 직접 접근 - API 호출 감소
-
자동 코드 분할: - 클라이언트 컴포넌트만 자동으로 코드 분할
-
향상된 보안: - 민감한 로직이나 API 키를 클라이언트에 노출하지 않음
-
초기 로딩 성능 향상: - 데이터를 미리 가져와 HTML에 포함
서버 컴포넌트는 Next.js 같은 프레임워크에서 구현되어 있으며, 클라이언트 컴포넌트와 함께 사용하여 하이브리드 애플리케이션을 구축할 수 있습니다.
-
React의 동시성(Concurrency) 모드란 무엇인가요?
동시성 모드는 React 18에서 도입된 새로운 렌더링 모델로, React가 렌더링 작업을 중단, 재개, 우선순위 지정할 수 있게 해줍니다. 주요 특징:
-
렌더링 작업 우선순위 지정: - 중요한 업데이트(타이핑, 클릭)를 우선 처리 - 덜 중요한 업데이트(데이터 로딩, 목록 필터링)는 지연 가능
-
주요 API: -
useTransition
: 우선순위가 낮은 상태 업데이트 표시 -startTransition
: 함수를 트랜지션으로 표시 -useDeferredValue
: 값의 업데이트를 지연 -
이점: - UI 응답성 향상 - 사용자 상호작용 차단 방지 - 불필요한 로딩 상태 감소
- 사용 예:
const [isPending, startTransition] = useTransition(); const handleChange = (e) => { // 즉시 업데이트 (높은 우선순위) setInputValue(e.target.value); // 트랜지션으로 표시 (낮은 우선순위) startTransition(() => { setSearchResults(filterItems(e.target.value)); }); };
-
React 애플리케이션에서 상태 관리의 최신 트렌드는 무엇인가요?
React 상태 관리 최신 트렌드:
-
상태 관리 간소화: - 거대한 전역 스토어 대신 작은 상태 조각 사용 - Context API + useReducer 조합
-
서버 상태와 클라이언트 상태 분리: - React Query, SWR 등으로 서버 상태 관리 - 로컬 UI 상태는 useState, useReducer로 관리
-
원자적 상태 관리: - Recoil, Jotai 등의 원자 기반 라이브러리 - 작은 상태 단위로 분할하여 재렌더링 최적화
-
Redux 간소화: - Redux Toolkit 사용으로 보일러플레이트 감소 - RTK Query로 데이터 페칭 통합
-
불변성 관리 간소화: - Immer 통합으로 불변성 유지 코드 간소화
-
상태 관리 라이브러리 경량화: - Zustand, Valtio 같은 경량 라이브러리 인기
-
상태 지속성 및 동기화: - 로컬 스토리지, IndexedDB와 상태 동기화 - 실시간 협업을 위한 CRDT 기반 상태 관리
-
TypeScript와 React를 함께 사용할 때의 이점과 모범 사례는 무엇인가요?
TypeScript와 React 사용 이점:
-
타입 안전성: - 컴파일 시점에 오류 발견 - props, 상태, 이벤트 핸들러 등의 타입 검증
-
개발자 경험 향상: - 자동 완성 및 인텔리센스 - 리팩토링 용이성
- 문서화 효과: - 컴포넌트 인터페이스 명확화 - 팀 협업 개선
모범 사례:
- Props 타입 정의:
interface ButtonProps { text: string; onClick: () => void; variant?: 'primary' | 'secondary'; } const Button: React.FC<ButtonProps> = ({ text, onClick, variant = 'primary' }) => { // ... };
- 이벤트 핸들러 타입:
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { // ... };
- 제네릭 컴포넌트:
interface ListProps<T> { items: T[]; renderItem: (item: T) => React.ReactNode; } function List<T>({ items, renderItem }: ListProps<T>) { // ... }
-
타입 추론 활용: - 불필요한 타입 어노테이션 피하기 - useState와 같은 제네릭 Hook 활용
-
유틸리티 타입 사용: - Partial, Pick, Omit 등으로 타입 조작 - 중복 타입 정의 최소화
-
마이크로 프론트엔드 아키텍처에서 React를 어떻게 활용할 수 있나요?
마이크로 프론트엔드와 React:
-
마이크로 프론트엔드 접근 방식: - 웹 애플리케이션을 독립적으로 개발, 테스트, 배포할 수 있는 작은 앱으로 분할 - 각 팀이 자체 기술 스택, 릴리스 주기 가능
- React 기반 구현 방법: (계속)
-
Web Components 래핑: ```jsx class ReactWrapper extends HTMLElement { connectedCallback() { const mountPoint = document.createElement(‘div’); this.attachShadow({ mode: ‘open’ }).appendChild(mountPoint);
ReactDOM.render(
, mountPoint); }
disconnectedCallback() { ReactDOM.unmountComponentAtNode(this.shadowRoot); } }
customElements.define(‘react-component’, ReactWrapper); ```
-
- iframes 사용
- 서버 사이드 조합 (SSI, ESI 등)
- 런타임 통합 라이브러리 (single-spa, qiankun 등)
- 통합 과제 해결:
- 스타일 격리: CSS Modules, CSS-in-JS, Shadow DOM
- 상태 공유: 이벤트 버스, 공유 라이브러리, 쿠키/localStorage
- 라우팅 조정: 중앙 라우터 또는 라우터 통합
- 성능 최적화: 공통 의존성 공유, 코드 분할
- 모노레포 관리:
- Nx, Lerna, Turborepo 등으로 코드베이스 관리
- 공유 컴포넌트 라이브러리 구축
실무 및 프로젝트 관련
-
대규모 React 애플리케이션을 구조화하는 방법은 무엇인가요?
대규모 React 애플리케이션 구조화:
-
폴더 구조 전략: - 기능별 구조 (Feature-based):
/src /features /auth /components /hooks /services /store index.js /products /components /hooks /services /store index.js /shared /components /hooks /utils
- 계층별 구조 (Layer-based):
```
/src
/components
/common
/layout
/features
/hooks
/services
/store
/utils
```
- 코드 분할 및 지연 로딩:
const ProductDetails = React.lazy(() => import('./features/products/ProductDetails')); function App() { return ( <Suspense fallback={<Spinner />}> <Routes> <Route path="/products/:id" element={<ProductDetails />} /> </Routes> </Suspense> ); }
-
상태 관리 전략: - 전역 상태와 지역 상태 구분 - 도메인별 상태 분리 - 서버 상태와 클라이언트 상태 분리
-
컴포넌트 설계 원칙: - 단일 책임 원칙 적용 - 컴포넌트 크기 제한 - 재사용 가능한 컴포넌트 라이브러리 구축
-
모듈화 및 경계 설정: - 명확한 API 경계 정의 - 내부 구현 세부 사항 캡슐화
-
문서화 및 스타일 가이드: - Storybook으로 컴포넌트 문서화 - 일관된 코딩 스타일 적용 (ESLint, Prettier)
-
React 프로젝트에서 성능 최적화를 위한 실용적인 팁은 무엇인가요?
React 성능 최적화 팁:
-
렌더링 최적화: - 컴포넌트 분할로 리렌더링 범위 제한 - React.memo, useMemo, useCallback 적절히 사용 - 상태 업데이트 일괄 처리
-
번들 크기 최적화: - 코드 분할 및 지연 로딩 - 트리 쉐이킹이 가능한 import 사용 - 번들 분석기로 큰 의존성 식별 - 불필요한 라이브러리 제거
-
이미지 및 미디어 최적화: - 적절한 이미지 포맷 사용 (WebP 등) - 이미지 크기 조정 및 압축 - 지연 로딩 적용
-
목록 렌더링 최적화: - 가상화 기술 사용 (react-window, react-virtualized) - 안정적인 키 사용 - 페이지네이션 또는 무한 스크롤 구현
-
네트워크 요청 최적화: - 데이터 프리페칭 - 캐싱 전략 구현 - GraphQL 사용 고려 (필요한 데이터만 요청)
-
메모리 관리: - 이벤트 리스너 정리 - 큰 객체 참조 해제 - 메모리 누수 모니터링
-
측정 및 모니터링: - Lighthouse, WebPageTest 등으로 정기적 성능 측정 - 사용자 중심 성능 지표 모니터링 (Core Web Vitals) - 실제 사용자 모니터링(RUM) 구현
-
React 프로젝트에서 상태 관리 라이브러리를 선택하는 기준은 무엇인가요?
상태 관리 라이브러리 선택 기준:
-
프로젝트 규모와 복잡성: - 작은 프로젝트: useState, useReducer, Context API - 중간 규모: Zustand, Jotai, Recoil - 대규모/복잡한 프로젝트: Redux Toolkit, MobX
-
팀 경험과 학습 곡선: - 팀의 기존 지식과 경험 - 문서화 품질과 커뮤니티 지원 - 학습 비용 대비 이점
-
성능 요구 사항: - 상태 업데이트 빈도 - 리렌더링 최적화 기능 - 메모리 사용량
-
기능 요구 사항: - 시간 여행 디버깅 필요성 - 미들웨어 지원 (비동기 작업, 로깅 등) - 상태 지속성 및 재수화 - 타입스크립트 지원
-
생태계와 지속 가능성: - 라이브러리 유지 관리 상태 - 업데이트 빈도 - 커뮤니티 크기와 활동성
-
통합 용이성: - 기존 코드베이스와의 통합 - 다른 라이브러리와의 호환성 - 점진적 도입 가능성
-
React 프로젝트에서 효과적인 에러 처리 전략은 무엇인가요?
React 에러 처리 전략:
- 에러 경계(Error Boundaries) 사용:
class ErrorBoundary extends React.Component { state = { hasError: false, error: null }; static getDerivedStateFromError(error) { return { hasError: true, error }; } componentDidCatch(error, info) { console.error('Error caught:', error, info); // 에러 로깅 서비스에 보고 } render() { if (this.state.hasError) { return <ErrorFallback error={this.state.error} />; } return this.props.children; } } // 사용 <ErrorBoundary> <MyComponent /> </ErrorBoundary>
- 비동기 에러 처리:
const fetchData = async () => { try { setLoading(true); setError(null); const data = await api.fetchSomething(); setData(data); } catch (error) { setError(error); // 에러 로깅 } finally { setLoading(false); } };
- 전역 에러 핸들러:
window.addEventListener('error', (event) => { // 전역 에러 처리 console.error('Global error:', event.error); // 에러 로깅 서비스에 보고 }); window.addEventListener('unhandledrejection', (event) => { // 처리되지 않은 Promise 거부 처리 console.error('Unhandled promise rejection:', event.reason); // 에러 로깅 서비스에 보고 });
-
에러 로깅 및 모니터링: - Sentry, LogRocket 등의 서비스 활용 - 사용자 컨텍스트 정보 포함 - 에러 그룹화 및 우선순위 지정
-
사용자 친화적인 에러 UI: - 명확한 에러 메시지 제공 - 복구 옵션 제시 (재시도, 새로고침 등) - 기술적 세부 사항 숨기기
-
유효성 검사: - 입력 데이터 사전 검증 - 타입스크립트로 타입 안전성 확보 - 불변 조건 확인
-
React 프로젝트에서 코드 품질을 유지하는 방법은 무엇인가요?
React 코드 품질 유지 방법:
-
코드 표준화 도구: - ESLint: 코드 품질 및 스타일 규칙 적용 - Prettier: 일관된 코드 포맷팅 - TypeScript: 타입 안전성 확보
-
테스트 전략: - 단위 테스트: 개별 컴포넌트 및 함수 - 통합 테스트: 컴포넌트 간 상호작용 - E2E 테스트: 사용자 흐름 - 테스트 커버리지 모니터링
-
코드 리뷰 프로세스: - 명확한 리뷰 체크리스트 - 자동화된 코드 품질 검사 - 지식 공유 및 멘토링 기회로 활용
-
문서화: - Storybook으로 컴포넌트 문서화 - JSDoc 주석 활용 - README 및 개발 가이드 유지
-
지속적 통합/배포(CI/CD): - 자동화된 테스트 실행 - 린트 및 타입 검사 자동화 - 배포 전 품질 게이트 설정
-
코드 구조 및 설계: - 단일 책임 원칙 적용 - 컴포넌트 크기 제한 - 일관된 네이밍 컨벤션 - 중복 코드 최소화
-
성능 모니터링: - 정기적인 성능 감사 - 번들 크기 모니터링 - 메모리 누수 검사
-
React Native와 React의 주요 차이점은 무엇인가요?
React Native와 React 차이점:
-
렌더링 대상: - React: 웹 브라우저의 DOM에 렌더링 - React Native: 네이티브 모바일 컴포넌트로 렌더링
-
컴포넌트: - React: div, span, p 등 HTML 요소 사용 - React Native: View, Text, Image 등 플랫폼 중립적 컴포넌트 사용
-
스타일링: - React: CSS, CSS-in-JS, CSS Modules 등 사용 - React Native: JavaScript 객체 기반 스타일링, Flexbox 레이아웃
-
이벤트 처리: - React: onClick, onChange 등 DOM 이벤트 - React Native: onPress, onChangeText 등 플랫폼 특화 이벤트
-
애니메이션: - React: CSS 트랜지션, Web Animations API 등 - React Native: Animated API, LayoutAnimation 등
-
네이티브 기능 접근: - React: 웹 API를 통해 제한된 기능 접근 - React Native: 네이티브 모듈을 통해 기기 기능 직접 접근
-
개발 환경: - React: 웹 브라우저에서 개발 및 디버깅 - React Native: 시뮬레이터/에뮬레이터 또는 실제 기기에서 개발
- 배포:
- React: 웹 서버에 배포
- React Native: 앱 스토어를 통해 배포, OTA 업데이트 가능
- 성능:
- React: 브라우저 성능에 의존
- React Native: 네이티브 스레드와 JS 스레드 간 통신으로 인한 오버헤드 존재
- 학습 곡선:
- React: 웹 개발 지식만 필요
- React Native: 모바일 개발 개념과 플랫폼별 특성 이해 필요
-
React 프로젝트에서 국제화(i18n)를 구현하는 방법은 무엇인가요?
React 국제화(i18n) 구현 방법:
-
라이브러리 선택: - react-intl: 포맷팅 기능이 강력 - i18next: 유연하고 확장성 높음 - react-i18next: i18next의 React 바인딩
- 기본 설정 (react-i18next 예시):
// i18n.js import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; i18n .use(initReactI18next) .init({ resources: { en: { translation: { welcome: 'Welcome to our app', greeting: 'Hello, !' } }, ko: { translation: { welcome: '앱에 오신 것을 환영합니다', greeting: '안녕하세요, 님!' } } }, lng: 'en', fallbackLng: 'en', interpolation: { escapeValue: false } }); export default i18n;
- 번역 사용:
import { useTranslation } from 'react-i18next'; function Welcome({ name }) { const { t } = useTranslation(); return ( <div> <h1>{t('welcome')}</h1> <p>{t('greeting', { name })}</p> </div> ); }
- 언어 전환:
function LanguageSwitcher() { const { i18n } = useTranslation(); return ( <div> <button onClick={() => i18n.changeLanguage('en')}>English</button> <button onClick={() => i18n.changeLanguage('ko')}>한국어</button> </div> ); }
- 복수형, 날짜, 숫자 포맷팅:
// 복수형 t('items', { count: 5 }); // "5 items" 또는 "5개 항목" // 날짜 포맷팅 t('date', { date: new Date(), formatParams: { date: { month: 'long' } } }); // 숫자 포맷팅 t('price', { price: 1000.5, formatParams: { price: { style: 'currency', currency: 'USD' } } });
-
번역 파일 관리: - 언어별 JSON 파일 분리 - 네임스페이스로 도메인별 번역 구성 - 동적 로딩으로 필요한 번역만 로드
-
React 프로젝트에서 접근성(a11y)을 구현하는 실용적인 방법은 무엇인가요?
React 접근성 구현 방법:
-
기본 접근성 원칙 적용: - 시맨틱 HTML 요소 사용 - 적절한 ARIA 속성 추가 - 키보드 탐색 지원 - 충분한 색상 대비 제공
- 접근성 도구 활용:
- ESLint의 jsx-a11y 플러그인:
npm install eslint-plugin-jsx-a11y --save-dev
- Lighthouse, axe 등의 접근성 감사 도구 - 스크린 리더 테스트 (VoiceOver, NVDA, JAWS)
- 폼 접근성:
<div> <label htmlFor="name">Name:</label> <input id="name" type="text" aria-describedby="name-error" aria-invalid={!!errors.name} /> {errors.name && ( <div id="name-error" className="error"> {errors.name} </div> )} </div>
- 동적 콘텐츠 알림:
function Notifications() { const [messages, setMessages] = useState([]); return ( <div> <ul> {messages.map(msg => ( <li key={msg.id}>{msg.text}</li> ))} </ul> {/* 새 메시지가 추가될 때 스크린 리더에 알림 */} <div aria-live="polite" className="sr-only"> {messages.length > 0 && `New message: ${messages[0].text}`} </div> </div> ); }
- 포커스 관리:
function Modal({ isOpen, onClose, children }) { const modalRef = useRef(); useEffect(() => { if (isOpen) { // 모달 열릴 때 포커스 이동 modalRef.current?.focus(); // 포커스 트랩 구현 const handleKeyDown = (e) => { if (e.key === 'Escape') { onClose(); } // 탭 키 처리로 포커스가 모달 내에 머물도록 함 }; document.addEventListener('keydown', handleKeyDown); return () => { document.removeEventListener('keydown', handleKeyDown); }; } }, [isOpen, onClose]); if (!isOpen) return null; return ( <div ref={modalRef} role="dialog" aria-modal="true" aria-labelledby="modal-title" tabIndex={-1} > <h2 id="modal-title">Modal Title</h2> {children} <button onClick={onClose}>Close</button> </div> ); }
-
접근성 테스트 자동화: - Jest와 Testing Library로 접근성 테스트 - CI/CD 파이프라인에 접근성 검사 통합
-
React 프로젝트에서 보안 모범 사례는 무엇인가요?
React 보안 모범 사례:
- XSS(Cross-Site Scripting) 방지:
- React의 자동 이스케이프 활용 (기본적으로 제공)
- 위험한 props 사용 주의:
// 위험: 사용자 입력을 직접 HTML로 삽입 <div dangerouslySetInnerHTML= /> // 대안: 텍스트로 렌더링 <div>{userInput}</div>
- 신뢰할 수 없는 URL 검증: ```jsx // URL 검증 함수 const isSafeURL = (url) => { try { const parsed = new URL(url); return ['https:', 'http:'].includes(parsed.protocol); } catch { return false; } };
// 사용 <a href={isSafeURL(userProvidedURL) ? userProvidedURL : ‘#’}>Link</a> ```
-
민감한 데이터 관리: - 환경 변수로 API 키 관리 (.env 파일) - 클라이언트 측 코드에 민감한 정보 포함 금지 - localStorage/sessionStorage에 민감한 데이터 저장 주의
-
의존성 보안: - 정기적인 의존성 업데이트 - npm audit 또는 Snyk 등으로 취약점 검사 - package-lock.json 또는 yarn.lock 파일 버전 관리
-
API 요청 보안: - CSRF 토큰 사용 - 적절한 인증 헤더 설정 - 요청 및 응답 데이터 검증
-
인증 및 권한 관리: - JWT 토큰 안전하게 저장 (httpOnly 쿠키 선호) - 토큰 만료 처리 - 권한 기반 UI 렌더링
-
CSP(Content Security Policy) 설정: - 인라인 스크립트 제한 - 신뢰할 수 있는 소스만 허용
-
사용자 입력 검증: - 클라이언트 측과 서버 측 모두에서 검증 - 입력 데이터 정규화 및 필터링
-
React 프로젝트에서 CI/CD 파이프라인을 구성하는 방법은 무엇인가요?
React CI/CD 파이프라인 구성:
-
CI/CD 도구 선택: - GitHub Actions, GitLab CI, CircleCI, Jenkins 등
- 기본 CI 파이프라인 구성 (GitHub Actions 예시):
# .github/workflows/ci.yml name: CI on: push: branches: [ main, develop ] pull_request: branches: [ main, develop ] jobs: build-and-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Node.js uses: actions/setup-node@v3 with: node-version: '16' cache: 'npm' - name: Install dependencies run: npm ci - name: Lint run: npm run lint - name: Type check run: npm run typecheck - name: Run tests run: npm test -- --coverage - name: Build run: npm run build - name: Upload build artifacts uses: actions/upload-artifact@v3 with: name: build path: build/
- CD 파이프라인 추가:
# .github/workflows/cd.yml name: CD on: push: branches: [ main ] jobs: deploy: runs-on: ubuntu-latest needs: build-and-test steps: - name: Download build artifacts uses: actions/download-artifact@v3 with: name: build path: build - name: Deploy to production uses: some-deploy-action@v1 with: api-key: $ app-name: my-react-app deploy-path: build/
-
환경별 배포 구성: - 개발(dev), 스테이징(staging), 프로덕션(prod) 환경 설정 - 환경별 환경 변수 및 구성 관리 - 브랜치 기반 배포 전략 (GitFlow 등)
-
품질 게이트 추가: - 테스트 커버리지 임계값 설정 - 성능 및 접근성 검사 - 보안 취약점 스캔
-
배포 자동화: - AWS S3 + CloudFront - Netlify, Vercel 등의 정적 호스팅 서비스 - Docker 컨테이너화 및 Kubernetes 배포
-
React 프로젝트에서 기술 부채를 관리하는 방법은 무엇인가요?
React 기술 부채 관리 방법:
-
기술 부채 식별: - 정기적인 코드 품질 검토 - 정적 분석 도구 활용 (SonarQube 등) - 성능 및 유지보수성 지표 모니터링
-
점진적 리팩토링: - 보이 스카우트 규칙 적용 (코드를 발견했을 때보다 더 깨끗하게 남기기) - 새 기능 개발과 함께 관련 코드 개선 - 리팩토링을 위한 전용 시간 할당
-
레거시 코드 현대화: - 클래스 컴포넌트를 함수형 컴포넌트로 마이그레이션 - 오래된 패턴 및 라이브러리 업데이트 - 타입스크립트 점진적 도입
-
문서화 개선: - 코드 주석 및 JSDoc 추가 - 아키텍처 결정 기록(ADR) 유지 - 기술 부채 항목 문서화 및 우선순위 지정
-
테스트 커버리지 향상: - 레거시 코드에 대한 테스트 추가 - 회귀 테스트 자동화 - 테스트 가능한 코드 설계 촉진
- 의존성 관리:
- 정기적인 의존성 업데이트
- 사용하지 않는 의존성 제거
- 의존성 버전 고정 및 호환성 확인
- 코드 복잡성 감소:
- 큰 컴포넌트 분할
- 복잡한 로직 추상화
- 중복 코드 제거
- 성능 최적화:
- 렌더링 병목 현상 식별 및 해결
- 번들 크기 최적화
- 메모리 누수 수정
-
React 프로젝트에서 디자인 시스템을 구축하고 유지하는 방법은 무엇인가요?
React 디자인 시스템 구축 및 유지:
- 디자인 토큰 정의:
// theme.js export const theme = { colors: { primary: '#0070f3', secondary: '#ff4081', text: { primary: '#333333', secondary: '#666666', }, background: { default: '#ffffff', paper: '#f5f5f5', } }, spacing: { xs: '4px', sm: '8px', md: '16px', lg: '24px', xl: '32px', }, typography: { fontFamily: "'Roboto', sans-serif", fontSize: { xs: '12px', sm: '14px', md: '16px', lg: '20px', xl: '24px', }, fontWeight: { regular: 400, medium: 500, bold: 700, } }, breakpoints: { xs: '0px', sm: '600px', md: '960px', lg: '1280px', xl: '1920px', } };
- 기본 컴포넌트 구축:
// Button.jsx import styled from 'styled-components'; import { theme } from '../theme'; const StyledButton = styled.button` background-color: ${props => props.variant === 'primary' ? theme.colors.primary : 'transparent'}; color: ${props => props.variant === 'primary' ? '#fff' : theme.colors.primary}; border: ${props => props.variant === 'primary' ? 'none' : `1px solid ${theme.colors.primary}`}; padding: ${theme.spacing.sm} ${theme.spacing.md}; border-radius: 4px; font-family: ${theme.typography.fontFamily}; font-size: ${theme.typography.fontSize.md}; font-weight: ${theme.typography.fontWeight.medium}; cursor: pointer; &:hover { opacity: 0.9; } &:disabled { opacity: 0.5; cursor: not-allowed; } `; export const Button = ({ children, variant = 'primary', ...props }) => { return ( <StyledButton variant={variant} {...props}> {children} </StyledButton> ); };
- 컴포넌트 문서화 (Storybook):
// Button.stories.jsx import { Button } from './Button'; export default { title: 'Components/Button', component: Button, argTypes: { variant: { control: { type: 'select', options: ['primary', 'secondary', 'outline'] }, defaultValue: 'primary', }, disabled: { control: 'boolean', defaultValue: false, }, }, }; const Template = (args) => <Button {...args} />; export const Primary = Template.bind({}); Primary.args = { children: 'Primary Button', variant: 'primary', }; export const Secondary = Template.bind({}); Secondary.args = { children: 'Secondary Button', variant: 'secondary', }; export const Disabled = Template.bind({}); Disabled.args = { children: 'Disabled Button', disabled: true, };
-
컴포넌트 라이브러리 구성: - 모노레포 설정 (Nx, Lerna, Turborepo 등) - 컴포넌트 패키지화 및 버전 관리 - npm/yarn에 배포 또는 비공개 레지스트리 사용
-
디자인 시스템 가이드라인: - 사용 패턴 및 모범 사례 문서화 - 접근성 지침 포함 - 컴포넌트 조합 예시 제공
-
테마 및 스타일링 전략: - CSS-in-JS (styled-components, emotion) - 테마 제공자 구현 - 다크 모드 지원
-
디자인-개발 협업: - Figma와 같은 디자인 도구와 통합 - 디자인 토큰 동기화 - 디자이너와 개발자 간 공통 언어 구축
-
React 프로젝트에서 확장 가능한 상태 관리 아키텍처를 설계하는 방법은 무엇인가요?
확장 가능한 상태 관리 아키텍처:
-
상태 계층화: - 로컬 상태: 단일 컴포넌트 내에서만 사용되는 UI 상태 - 공유 상태: 여러 컴포넌트에서 사용되는 상태 - 서버 상태: API에서 가져온 데이터 - 전역 상태: 앱 전체에서 사용되는 상태
- 도메인 중심 상태 분리:
// Redux 예시 // store/auth/slice.js import { createSlice } from '@reduxjs/toolkit'; const authSlice = createSlice({ name: 'auth', initialState: { user: null, isAuthenticated: false, loading: false, error: null }, reducers: { // 리듀서 정의 } }); // store/products/slice.js const productsSlice = createSlice({ name: 'products', initialState: { items: [], loading: false, error: null }, reducers: { // 리듀서 정의 } });
- 상태 접근 추상화:
// hooks/useAuth.js export function useAuth() { const user = useSelector(state => state.auth.user); const isAuthenticated = useSelector(state => state.auth.isAuthenticated); const loading = useSelector(state => state.auth.loading); const error = useSelector(state => state.auth.error); const dispatch = useDispatch(); const login = useCallback((credentials) => { dispatch(authActions.loginRequest(credentials)); }, [dispatch]); const logout = useCallback(() => { dispatch(authActions.logout()); }, [dispatch]); return { user, isAuthenticated, loading, error, login, logout }; }
- 서버 상태와 클라이언트 상태 분리:
// 서버 상태 관리 (React Query) function ProductList() { const { data: products, isLoading, error } = useQuery( 'products', fetchProducts ); // UI 렌더링 } // 클라이언트 상태 관리 (useState/Redux) function ShoppingCart() { const { items, addItem, removeItem } = useCart(); // UI 렌더링 }
- 비동기 작업 처리:
// Redux Toolkit 예시 const fetchUserProfile = createAsyncThunk( 'users/fetchUserProfile', async (userId, { rejectWithValue }) => { try { const response = await api.get(`/users/${userId}`); return response.data; } catch (error) { return rejectWithValue(error.response.data); } } ); // React Query 예시 const { data, isLoading, error } = useQuery( ['user', userId], () => fetchUserProfile(userId), { staleTime: 5 * 60 * 1000, // 5분 retry: 3, onError: (error) => { // 에러 처리 } } );
- 상태 지속성 및 동기화:
// Redux Persist 예시 import { persistStore, persistReducer } from 'redux-persist'; import storage from 'redux-persist/lib/storage'; const persistConfig = { key: 'root', storage, whitelist: ['auth', 'cart'] // 유지할 상태 지정 }; const persistedReducer = persistReducer(persistConfig, rootReducer); export const store = configureStore({ reducer: persistedReducer, // 기타 설정 }); export const persistor = persistStore(store);
-
성능 최적화: - 선택적 상태 구독 - 정규화된 상태 구조 - 메모이제이션 활용
-
React 프로젝트에서 지속적인 학습과 개선을 촉진하는 방법은 무엇인가요?
지속적인 학습과 개선 방법:
-
코드 리뷰 문화 구축: - 건설적인 피드백 장려 - 지식 공유 기회로 활용 - 코드 품질 및 일관성 향상
-
기술 공유 세션: - 정기적인 팀 내 기술 발표 - 새로운 라이브러리나 패턴 소개 - 프로젝트 문제 해결 사례 공유
-
실험 및 프로토타이핑: - 새로운 기술 탐색을 위한 시간 할당 - 프로토타입 개발 및 평가 - 성공적인 실험 결과 통합
-
회고 및 개선 사이클: - 정기적인 팀 회고 진행 - 개선 항목 식별 및 우선순위 지정 - 점진적 개선 계획 수립
-
학습 리소스 공유: - 유용한 블로그, 문서, 강의 공유 - 내부 지식 베이스 구축 - 학습 커뮤니티 참여 장려
-
성능 및 사용자 경험 측정: - 핵심 성능 지표 모니터링 - 사용자 피드백 수집 및 분석 - 데이터 기반 개선 결정
- 외부 커뮤니티 참여: - 오픈 소스 기여 - 기술 컨퍼런스 참석 - 블로그 작성 및 지식 공유
이 면접 질문 모음은 React의 기초부터 고급 개념까지 다양한 주제를 다루고 있습니다. 각 질문에 대한 답변은 실무 경험과 최신 React 동향을 반영하고 있으며, 면접 준비뿐만 아니라 React 개발자로서의 지식을 넓히는 데도 도움이 될 것입니다.
Leave a comment