기술 면접에서 받은 질문 (2) - React 및 기타
요 근래 몇몇 크고 작은 회사에서 기술 면접을 봤다. 경력에 관련된 질문은 제외하고, 순수하게 기술과 관련된 질문 중 기억에 남는 것들을 정리해 보았다.
이 글에는 React를 비롯한 여러 라이브러리와 기타 프론트엔드 관련된 내용들을 정리했다. Vanilla JavaScript 에 관련된 질문은 이전 글에서 정리했다.
인용구로 작성된 내용은 인용구가 나오기 전 링크가 연결된 문서에서 가져온 문장이거나, 해당 문서에 있는 영문장을 번역한 문장이다.
React
1. 함수 컴포넌트와 클래스 컴포넌트의 차이
함수 컴포넌트와 클래스 컴포넌트의 차이를 묻는 질문이었다. React Hooks API 기능이 나오면서 차이는 없어졌다고 봐도 무방하지만, 굳이 찾아보면 아래와 같은 차이가 있다.
- 문법
- 클래스 컴포넌트는 라이프 사이클을 지원한다.
- 함수 컴포넌트도
useEffect
등을 사용하면 대부분의 라이프 사이클을 사용할 수 있다. componentDidCatch
,getSnapshotBeforeUpdate
,getDerivedStateFromError
는 여전히 사용할 수 없다.
- 함수 컴포넌트도
- 클래스 컴포넌트는
state
를 지원한다.- 함수 컴포넌트도
useState
를 사용하면state
를 사용할 수 있다.
- 함수 컴포넌트도
- 클래스 컴포넌트는 ref 인자를 받거나 자신의 자식 컴포넌트에게 ref 를 줄 수 있다.
- 함수 컴포넌트도
forwardRef
와useRef
를 통해 ref 를 사용할 수 있다.
- 함수 컴포넌트도
2. useLayoutEffect
useLayoutEffect
를 사용한 적이 있는지, useEffect
와 어떻게 다른지에 관한 질문이었다. 해당 내용은 Hooks API Reference 문서의 useLayoutEffect 섹션에서 확인할 수 있다.
주요 특징은
useEffect
와 동일하다. 하지만useLayoutEffect
는 모든 DOM 변경이 끝난 이후에 동기적으로 실행된다. 이것은 DOM으로부터 레이아웃을 읽어와 동기적으로 재렌더링할 때 사용된다.useLayoutEffect
내에서 예정된 업데이트는 브라우저가 실제로 렌더링을 업데이트하기 전에 동기적으로 실행된다.
일단
useEffect
를 먼저 사용하기를 권한다. 만약useEffect
가 문제를 일으키면 그 때useLayoutEffect
를 사용하라. (원문: we recommend starting with useEffect first and only trying useLayoutEffect if that causes a problem.)
컴포넌트가 업데이트된 직후 useEffect
에서 다시 컴포넌트 업데이트를 시도하면, 바로 화면 업데이트를 재시도하면서 화면이 깜빡이는 것처럼 보일 수 있다 (flikering). 이럴 때 useLayoutEffect
를 사용하면, 화면이 갱신되기 직전에 동기적으로 로직이 실행되므로 깜빡이는 현상을 막을 수 있다.
When to useLayoutEffect Instead of useEffect (example) 문서에서 예제 코드를 실행해 볼 수 있다.
3. Context API
React Context API 가 무엇인지, 사용해 봤는지에 관한 질문이었다. reactjs.org 의 Context 문서에 잘 설명되어 있다.
Context 는 React 컴포넌트 트리 내에서 데이터를 "글로벌"하게 공유하게 위해 설계되었다.
React로 개발을 한 경험이 있다면 직접 사용해 보지는 않았더라도 외부 라이브러리를 통해 간접적으로 사용해 보았을 것이다. React Redux의 Provider
, styled components의 ThemeProvider
, React DnD의 DndProvider
등 많은 라이브러리에서 내부적으로 Context를 사용하고 있다.
만약 특정 prop을 멀리 있는 컴포넌트에게 전달하기만을 원한다면, Context 보다는 component composition(prop을 여러 레벨에 거쳐 계속 전달하기보다는 해당 prop을 사용하는 컴포넌트 자체를 전달하는 방식)이 종종 더 간단한 해결책이 되곤 한다.
4. useReducer
어떤 상황을 주고 해당 상황에 대해 useReducer
를 사용해서 해결할 수 있겠느냐는 질문이었다. useReducer
에 관한 내용은 Hooks API Reference 문서의 해당 섹션에서 확인할 수 있다.
const [state, dispatch] = useReducer(reducer, initialArg, init);
useState
의 대안이 될 수 있다.(state, action) => newState
타입의reducer
를 인자로 받고, 현재state
와dispatch
메서드를 반환해준다. (만약 Redux에 익숙하다면, 당신은 이미 이게 어떻게 동작하는지 알고 있을 것이다.)
5. 부모자식 컴포넌트 간 렌더링/라이프 사이클 순서
부모자식 컴포넌트 간 렌더링 순서를 묻는 질문이었다. 직관적으로 알 수 있듯이, 부모를 렌더링하다가 자식 컴포넌트를 그려야 하는 시점이 오면 자식을 먼저 렌더링하고 부모의 남은 부분을 마저 렌더링하는 식으로 진행된다.
imkev.dev의 React Rendering Order 글을 보면 예시까지 있어 더 이해하기 쉬울 것이다.
라이프 사이클 순서도 렌더링과 별반 다를 것 없다.
6. Hooks API로 라이프사이클 구현
함수 컴포넌트에서 Hooks API로 클래스 컴포넌트의 라이프사이클 메서드를 구현할 수 있는지 묻는 질문이었다.
useEffect
로 componentDidMount
, componentDidUpdate
, componentDidUnmount
를 대체할 수 있다. 자세한 설명은 Using the Effect Hook 문서에서 볼 수 있다.
만약 당신이 React 클래스의 라이프사이클 메서드에 익숙하다면,
useEffect
hook이componentDidMount
,componentDidUpdate
,componentDidUnmount
의 조합이라고 생각할 수 있겠다.
useMemo
, useCallback
등을 활용해서 shouldComponentUpdate
도 대체할 수 있다. 관련 내용은 Hooks API Reference 문서의 useCallback 항목에서 볼 수 있다.
useCallback
은 콜백 함수를 전달 받는 자식 컴포넌트의 reference 비교에 의존한 불필요한 렌더 링을 막는데 유용하다. (예를 들면shouldComponentUpdate
같이)
그 외 라이브러리
1. TypeScript 장단점
현재 재직 중인 회사의 프로젝트에서 타입스크립트를 쓰고 있어서 나온 질문이다. 아마 타입스크립트를 사용한 적이 없는 사람들에게는 질문하지 않을 것 같다. 관련 내용은 The Good and the Bad of TypeScript 문서나 자바스크립트 개발자를 위한 타입스크립트의 1.2 왜 타입스크립트인가 문서 등 많은 문서에 나와 있다.
간단히 정리해보자면 장점은 아래와 같다.
- 코드의 가독성을 올려준다.
- 유지보수, 리팩토링 등이 용이해진다.
- 자바스크립트 사용자가 습득하기 쉽다.
- 타입과 관련된 에러를 컴파일 단계에서 잡아준다.
- OOP를 지원한다.
단점은 아래와 같다.
- 진짜 정적 타이핑이 아니다. 컴파일 후에는 자바스크립트로 변환된다. 따라서 런타임에서는 여전히 타입과 관련된 에러 가능성이 있다.
- 타입 선언 및 사용을 위해 코드 작성을 더 해야 한다. 작업 시간이 늘어나며 소스코드의 크기도 커진다.
2. Redux
Redux가 어떤 것인지, 장단점이 무엇인지, 비슷한 다른 것들은 써보았는지, 어떤 상황에서 쓰면 좋고 어떤 상황은 피하는 게 좋은지. Redux를 써봤다고 하면 들을 수 있는 전형적인 질문들이었다.
Redux는 JavaScript를 위한 예측 가능한 상태 컨테이너이다.
이것은 일관적으로 동작하고 다른 여러 환경에서 동작하며 테스트하기 쉬운 앱을 만들 수 있도록 도와준다.
Redux 공식 문서에는 위처럼 나와있다. Redux가 무엇인지에 대한 간략한 답은 "React에서는 전역적인 상태 관리를 위한 라이브러리"라고 하면 충분할 것이고, 더 깊은 답을 원한다면 One-way(single-direction) data flow 등으로 대표되는 Flux architecture와 연관지어 설명해주면 된다.
Redux를 대체할 수 있는 것으로는 MobX, Recoil 등이 있고 React Context API 로도 대체할 수 있다.
- MobX에 관한 좋은 글: React에서 Mobx 경험기 (Redux와 비교기)
- Recoil에 관한 좋은 글: Recoil - 또 다른 React 상태 관리 라이브러리?
Redux가 전역적인 상태 관리에 좋긴 하지만 간단한 앱에서는 도리어 코드의 복잡도만 올릴 뿐 생산성에 도움이 되지 않는 경우도 있다. 따라서 만드려는 앱의 규모와 관리하려는 상태의 구조를 잘 생각해 사용을 선택해야 한다.
3. webpack 최적화
webpack 최적화 경험에 관한 질문이었 다. 관련 내용은 An in-depth guide to performance optimization with webpack 글에 잘 나와있다.
일단 가장 단순한 최적화는 mode: 'production'
설정을 해주는 것이다. 이렇게 설정해주면 webpack에서 많은 설정을 자동으로 해주는데, 공식 문서에 따르면 아래와 같은 설정들을 적용해준다.
DefinePlugin
을 사용해process.env.NODE_ENV
값을production
으로 설정해준다. module과 chunk를 위한 deterministic mangled names,FlagDependencyUsagePlugin
,FlagIncludedChunksPlugin
,ModuleConcatenationPlugin
,NoEmitOnErrorsPlugin
,TerserPlugin
을 활성화해준다.
Webpack Bundle Analyzer를 사용해 번들링된 결과물의 Treemap을 시각화해서 볼 수 있다. 이 Treemap을 보며 각 모듈이 어떻게 구성되고 크기가 얼마나 하는지 알 수 있고, 불필요하거나 비효율적인 모듈을 찾을 수도 있다.
이외에도 이 글에서는 아래와 같은 방법들을 제시한다.
- 가능하다면 앱의 구조 및 설계에 따라 앱을 SPA(Single-Page Application)가 아닌 MPA(Multi-Page Appliation)로 구성하자.
- 캐시 효율을 높이기 위해 외부 라이브러리와 직접 작성한 코드를 나눠서 번들링하자.
- Lazy-load 기능을 활용하자.
- reactjs.org의 Code-Splitting 문서도 참고
- 외부 라이브러리를 번들링에서 제외하고, CDN 등을 통해 따로 제공하자.
- Tree Shaking (사용하지 않는 코드를 번들링에서 제외하는 것) 기능을 활용하자.
- 번들링 결과물의 크기를 제한하자.
performance.maxAssetSize
,performance.maxEntrypointSize
옵션 등. webpack의 Performance 옵션 문서 참고
- 목적에 따른 적절한 Devtool 옵션을 사용하자.
일반
1. 브라우저 렌더링 방식
브라우저가 웹페이지를 렌더링할 때 어떤 일이 일어나는 가를 설명해달라는 질문이었다. 관련 내용은 브라우저 렌더링 과정 - Reflow Repaint, 그리고 성능 최적화 문서에 자세히 나와있다.
렌더링 엔진의 기본 동작 방식은 아래와 같다.
- 먼저 DOM(Document Object Model) Tree와 CSSOM (CSS Object Model) Tree를 생성한다.
- 위 두 항목을 기반으로 Render Tree를 생성한다.
- Layout 단계: Render Tree를 사용해 뷰포트Viewport에서의 엘리먼트의 크기와 위치를 계산한다.
- Paint 단계: Layout 단계에서 계산한 값을 가지고 실제로 화면에 그린다.
Reflow
Layout 단계부터 다시 렌더링 작업을 수행하는 것을 Reflow 라고 한다. 아래와 같은 경우에 Reflow가 발생한다.
- 페이지 최초 렌더링 시
- 윈도우 리사이징 시
- 노드 추가 또는 제거 시
- 요소 위치 또는 크기 변경 시
- 폰트, 텍스트 내용, 이미지 크기 변경 시
Repaint
Paint 단계를 다시 렌더링 작업을 수행하는 것을 Repaint 라고 한다. 아래와 같은 경우에 Repaint가 발생한다.
- Reflow 발생 시
- 레이아웃에 영향을 주지 않는 스타일 속성 변경 시 (예,
background-color
,visibility
)
Reflow, Repaint 줄이려면
- Reflow, Redraw 가 발생하는 CSS 속성들의 사용을 피하자.
- 레이아웃 변경 시 영향을 주는 노트/엘리먼트 수를 줄이자.
- 애니메이션 사용 시 애니메이션 프레임을 줄이자.
추가로 볼 문서
위에서 언급한 브라우저 렌더링 과정 - Reflow Repaint, 그리고 성능 최적화 문서도 충분히 좋은 내용이다.
MDN의 Populating the page: how browsers work 문서나 네이버 개발자 블로그의 브라우저는 어떻게 동작하는가? 문서도 좋다. 두 문서는 내용이 꽤 길긴 하지만 충분히 읽어볼 만하다.