Reference: 지금까지 받았던 신입 프론트엔드 면접 질문들 by arthur
Thanks to Dante
React는 UI를 만들기 위한 JavaScript 라이브러리입니다.
React는 스스로 상태를 관리하는 캡슐화된 컴포넌트를 조합해 복잡한 UI를 만들 수 있도록 지원하며, 데이터가 변경됨에 따라 적절한 컴포넌트만 효율적으로 갱신하고 렌더링합니다.
React는 상태 변화에 따른 UI 변경점을 결정하기 위해 재조정(Reconciliation)이라는 알고리즘을 사용하며, 이를 구현하기 위해 Virtual DOM이라는 패턴을 사용하였습니다.
또한 React 16부터 React Fiber엔진을 사용하여, 리액트의 Reconciler를 정비하였습니다.
React의 큰 특징으로는 Virtual DOM 개념과, 단방향 데이터 바인딩이 있습니다.
Virtual DOM은 Real DOM의 in-memory 표현으로, UI 표현은 메모리에 저장되며, Real DOM과 동기화됩니다.
단방향 데이터 바인딩은 간단히 설명하면, HTML에 바인딩한 데이터를 JS에서 수정할 경우 화면에 반영되지만, 화면에서 직접 해당 엘리먼트의 값을 바꿨을 때, JS의 데이터가 수정되도록 바인딩하는 방법은 제공하지 않는 방법입니다.
Virtual DOM의 뒤에는 재조정(Reconciliation) 알고리즘이 있습니다.
React는 재조정과 렌더링이 별개의 단계가 되도록 설계되었습니다.
Reconciler는 트리의 어떤 부분이 변경되었는지 계산하고, 이후 Renderer는 계산된 정보를 앱을 실제로 업데이트하는 데 사용합니다.
그리고, Fiber를 이용하여, 리액트 컴포넌트에 특화되도록 stack을 재구성하였습니다.
이를 통하여 stack frame을 메모리에 보관하고, 원하는 경우 언제든지 실행할 수 있도록 만들었으며, 이를 통해 스케줄링 목표를 달성하고자 하였습니다.
Fiber를 도입하며 모든 업데이트를 즉시 적용하는 것이 아닌, 더 우선순위가 높은 업데이트를 먼저 완료할 수 있게 되었습니다.
JSX는 JavaScript를 확장한 문법으로, 공식문서에서 React와 함께 사용할 것을 권장하고 있는 문법입니다.
JSX는 JavaScript의 모든 기능이 포함되어 있으며, React Element를 생성하기 위해 사용됩니다.
엘리먼트는 자바스크립트 객체고, 리액트로 화면을 그려내는 데에 가장 기본적인 요소입니다. 엘리먼트는 한 번 생성되면 다시는 변형되지 않습니다.
반면 컴포넌트는 엘리먼트를 반환하는 함수 혹은 클래스를 의미합니다.
재사용성을 강조하여, 엘리먼트를 좀 더 자유롭게 다룰 수 있으며, 컴포넌트의 이름을 사용하여 하나의 태그처럼 사용할 수 있습니다.
컴포넌트를 생성하는 방법으로는 함수형 컴포넌트와 클래스형 컴포넌트가 있습니다.
함수형 컴포넌트는 JavaScript 함수와 같은 방법으로 정의하며, 인자를 받아, React element를 반환하도록 만들 수 있습니다.
클래스형 컴포넌트는 ES6의 class
를 사용하여 정의합니다. class
안에서 render()
함수를 정의하고, 여기에서 React element를 반환하도록 만들 수 있습니다.
주의해야할 점으로, 컴포넌트의 이름은 항상 대문자로 시작해야 합니다. React는 소문자로 시작하는 컴포넌트를 DOM 태그로 처리합니다. 이는 babel 컴파일을 진행할 때, 원시태그와 컴포넌트를 구분하기 위한 규칙으로, 지키지 않으면 에러가 발생합니다.
네 ㅋㅋ
클래스형 컴포넌트는 LifeCycle API를 제공하나, 함수형 컴포넌트는 기본적으로는 제공되지 않습니다. (Hook을 사용하면 사용할 수 있습니다.)
반면, 함수형 컴포넌트는 메모리 자원을 클래스형 컴포넌트보다 덜 사용하며, 빌드한 결과물의 크기 역시 클래스형 컴포넌트보다 적습니다.
React 공식문서에서 클래스형 컴포넌트로 선언하는 방법을 아예 Legacy API로 분류해 놓을 정도로 함수형 컴포넌트는 강력합니다. 더 이상 클래스형 컴포넌트와 함수형 컴포넌트의 비교는 무의미해졌기 때문에 이후 나오는 클래스형 컴포넌트에 대한 질문들을 삭제하였습니다.
모든 리액트 컴포넌트에는 라이프 사이클이 있으며, 컴포넌트는 마운트 -> 업데이트 -> 언마운트의 라이프사이클을 갖습니다.
마운트는 컴포넌트가 생성되는 시점를 의미하며, constructor
-> getDerivedStateFromProps
-> render
-> componentDidMount
의 순서로 호출됩니다.
업데이트는 컴포넌트가 업데이트되는 시점을 의미하며, getDerivedStateFromProps
-> shouldComponentUpdate
-> render
-> getSnapshotBeforeUpdate
-> componentDidUpdate
순서로 호출됩니다.
언마운트는 컴포넌트가 화면에서 사라지는 시점을 의미하며, 컴포넌트가 화면에서 사라지기 직전에 componentWillUnmount
가 호출됩니다.
getDerivedStateFromProps
: props
로 받아온 것을 state
로 넣어주고 싶을 때 사용합니다.
componentDidMount
: 컴포넌트의 첫번째 렌더링이 마치고 나면 호출되는 메서드로, 이 메서드가 호출되는 시점에는 우리가 만든 컴포넌트가 화면에 나타난 상태입니다.
shouldComponentUpdate
: 컴포넌트가 리렌더링 할지 말지를 결정하는 메서드로 주로 최적화를 할 때 사용하는 메서드입니다.
getSnapshotBeforeUpdate
: 컴포넌트에 변화가 일어나기 직전의 DOM 상태를 가져와서 특정 값을 반환하면, 그 다음 발생하게 되는 componentDidUpdate
에서 받아와 사용할 수 있습니다.
componentDidUpdate
: 리렌더링을 마치고, 화면에 우리가 원하는 변화가 모두 반영되고 난 뒤 호출되는 메서드입니다. 3번째 파라미터로 getSnapshotBeforeUpdate
에서 반환한 값을 조회할 수 있습니다.
componentWillUnmount
: 컴포넌트가 화면에서 사라지기 직전에 호출되는 메서드입니다.
훅은 16.8버전 이전까지 클래스 기반의 리액트 로직을 함수 기반으로 대체하기 위해 만들어 졌습니다.
또한 바닐라 자바스크립트 함수와 동일한 모양이므로, 여러 훅을 이용해 커스텀 훅으로 조립하여 사용할 수 있습니다. 각각의 커스텀 훅은 독립적인 상태를 관리하며 다른 컴포넌트에 주입되어 캡슐화를 제공합니다.
즉, 훅은 코드 재사용성을 위해 만들어졌습니다.
리액트에서 훅은 호출되는 순서에 의존합니다.
그 이유는 state
가 자바스크립트의 클로저를 이용하여 구현되었기 때문입니다.
클로저 내에서는 해당 state
의 인덱스를 기록하고, 이 인덱스 값을 추적할 수 있도록 배열 내에서 상태값들을 관리합니다.
따라서 훅을 조건문 내에서 사용하면 이 순서에 문제가 생길 수 있으므로, 조건문 내에서 훅을 사용하면 안됩니다.
제일 큰 차이점은 useEffect
는 비동기적으로 동작하고, useLayoutEffect
는 동기적으로 동작한다는 것입니다. 리액트에서 useEffect
는 렌더링이 끝나고 특정 행동을 수행하고, useLayoutEffect
는 렌더링 전에 특정 행동을 수행합니다.
따라서 성능 모니터링이나 애니메이션 구현 등 즉시 반응이 필요한 경우에 useLayoutEffect
를 사용하고, 네트워크 요청, DOM 접근, 비동기 작업을 하는 경우에는 useEffect
를 사용하는 것이 좋습니다.
State
는 컴포넌트가 정보를 기억할 수 있도록 하는 기능입니다.
컴포넌트에 state
를 추가하기 위해서는 useState
나 useReducer
훅을 사용할 수 있습니다.
배열을 사용하여 관리하고, 해당 배열의 인덱스와 state
값을 클로저 내에 관리하여 useState()
함수가 반환되고 나서도, 별도의 메모리 공간에 값들을 저장합니다.
props
는 컴포넌트에 전달해주는 값으로, 매개변수와 같은 역할을 합니다.
props
는 부모 컴포넌트와 자식 컴포넌트를 독립적으로 생각할 수 있도록 도와줍니다.
props
를 UI 트리 깊숙이 전달해야 하거나 여러 컴포넌트에 동일한 props
가 필요한 경우, props
전달이 굉장히 불편해 집니다.
이렇게 props
를 계속 이어서 전달하는 상황을 prop drilling
이라고 부릅니다.
prop drilling
을 해결하는 대표적인 방법은 Context API를 사용하는 것입니다.
Context
는 컴포넌트가 해당 컴포넌트 하위의 트리 전체에 정보를 제공할 수 있도록 하는 기능입니다.
부모가 자식으로 setter
함수를 props를 통해 보내면 됩니다.
React legacy 공식문서에 의하면, props
는 함수 매개변수처럼 컴포넌트에 전달되는 반면 state
는 함수 내에 선언된 변수처럼 컴포넌트 안에서 관리됩니다.
state
는 일반적인 변수와 다르게 값이 변하면 리렌더링이 발생합니다. 즉, 값이 변하게 되면, 연관되어 있는 컴포넌트들이 다시 렌더링이 되어 화면이 바뀌게 됩니다.
그런데, React
는 setState
호출에 의한 state
의 주소 변경에만 반응하여 리렌더링이 발생하게 됩니다. 즉, 이런 방식으로 변경하지 않으면 React
가 감지하지 못합니다.
React
는 state
를 immutable하게 관리합니다. 그렇기 때문에 state
가 변경되면, state
의 주소값이 변경되게 되고, 이 변화를 알아채게 됩니다.
이것이, state
가 배열의 형태로 존재할 때, 배열에 원소를 .push()
를 이용해 추가하더라도, state
변화를 알아채지 못하는 이유입니다.
상태를 직접 수정하면 React가 상태 변경을 감지하지 못할 수 있습니다. 대신에 setState
메서드를 사용하여 상태를 변경합니다
상태를 업데이트할 때 이전 상태를 변경하지 말고 새로운 객체나 배열을 생성하여 업데이트합니다. 만약 객체나 배열의 깊이가 1이라면 spread 연산자인 ...
을 이용하여 간단히 생성할 수 있습니다.
만약 객체나 배열의 내부가 중첩되어 있다면, immer
라이브러리를 사용하여, 불변성을 유지할 수 있습니다.
setState
는 비동기적으로 동작합니다. 하지만 비동기 함수는 아닙니다.
그 이유는 리액트의 리렌더링 원리가 비동기적으로 작동하기 때문입니다.
그리고 그이유는 리액트가 가상돔을 사용하도록 설계되어 있기 때문입니다.
이는 리액트의 Fiber
와 밀접한 관련이 있습니다.
fiber architecture
는 재조정 알고리즘을 구현할 때, 변경된 부분을 찾고, 실제 돔에 변경사항하는 작업을 나누어 진행합니다.
그런데, 이 과정을 동기적으로 진행한다면, 메인스레드가 차단되고, 이는 프레임 드롭이나 응답지연으로 이어지기 때문에 UX를 저해하게 됩니다.
먼저 문법의 차이가 있습니다.
HTML은 전달할 함수를 문자열의 형태로 전달하지만, React는 함수 그 자체로 핸들러를 전달합니다.
또한 HTML은 이벤트를 소문자로 갖지만, React는 camelCase를 사용합니다. (ex. onClick)
다른 차이점으로는, HTML에서 false
를 반환하는 식으로 이벤트의 기본동작을 방지하지만, React에서는 preventDefault
를 명시적으로 호출하여 이벤트의 기본동작을 막아야 합니다.
key
는 React가 어떤 항목을 변경, 추가 또는 삭제할지 식별하는 것을 돕습니다.
key
는 엘리먼트에 안정적인 고유성을 부여하기 위해 배열 내부의 엘리먼트에 지정해야 합니다.
컴포넌트가 특정 정보를 기억하도록 하고 싶지만, 해당 정보가 새 렌더링을 촉발하지 않도록 하려는 경우 ref를 사용할 수 있습니다.
외부 시스템이나 브라우저 API를 사용해야 할 경우 유용합니다.
가장 일반적인 사용 사례는 DOM 요소에 액세스하는 것입니다. 예를 들어 input
에 focus
를 맞추거나, 스크롤을 하는 등의 DOM 조작을 하는 데 굉장히 유용합니다.
제어 컴포넌트는 사용자의 입력을 기반으로 자신의 state를 관리하고 업데이트 합니다. 일반적으로 state
속성을 통해 상태를 관리하고, setState
를 통해 업데이트합니다.
반면, 비제어 컴포넌트는 기존의 Vanilla JavaScript와 크게 다르지 않은 방식입니다.
이 방식을 사용할 때에는 setState
를 사용하지 않고, ref
를 사용하여 값을 얻습니다.
따라서 값이 실시간으로 동기화되지 않습니다.
고차 컴포넌트는 컴포넌트를 인자로 받아 새로운 컴포넌트를 다시 return해주는 함수입니다.
Context API는 React의 의존성 주입 도구입니다.
Context는 React 컴포넌트 트리 안에서 전역적이라고 볼 수 있는 데이터를 공유할 수 있도록 고안된 방법으로, 예를 들어, 로그인 유저, 테마, 언어 등이 있습니다.
리액트 컴포넌트는 1개의 엘리먼트를 리턴해야 하며, 여러개의 엘리먼트를 리턴할 경우, 에러가 발생합니다.
이러한 상황처럼, 여러개의 엘리먼트를 단일 엘리먼트가 들어갈 수 있는 위치에 배치하고자 할때, 사용할 수 있는 문법으로, Fragment
는 엘리먼트들을 다른 컨테이너로 감싸지 않기 때문에 레이아웃이나 스타일에 영향을 주지 않습니다.
Portal
을 사용하면, 컴포넌트가 일부 자식 컴포넌트를 DOM의 다른 위치로 렌더링할 수 있습니다.
Portal
은 오로지 DOM 노드의 물리적 배치만 변경하며, 다른 모든 면에서는 이를 렌더링하는 React
컴포넌트의 자식 노드 역할을 합니다.
예를 들어, 자식은 부모 컴포넌트의 컨텍스트에 접근할 수 있으며, 이벤트는 자식에서 부모로 버블링됩니다.
에러 바운더리는 하위 컴포넌트에서 발생하는 에러를 잡아서 선언적으로 처리할 수 있는 컴포넌트입니다.
이는 try-catch와 비슷한 형식으로, getDerivedStateFromError
를 사용하면 폴백 UI를 렌더링할 수 있고, componentDidCatch
를 사용하면 에러 정보를 기록할 수 있습니다.
메모이제이션이란 계산된 값을 자료구조에 저장하고 이 후 같은 계산을 반복하지 않고 자료구조에서 꺼내 재사용하는 것을 말합니다.
메모이제이션의 대표적인 예로는 동적계획법의 탑다운 방식이 있습니다.
리액트는 값을 메모이제이션하기 위한 useMemo
와 함수를 메모이제이션하기 위한 useCallback
을 사용하여 메모이제이션을 사용할 수 있습니다.
이 둘을 활용하면 퍼포먼스 최적화를 할 수 있습니다.
useMemo
는 deps 배열의 요소가 변경되지 않는 이상 함수의 반환값을 새로 계산하지 않기 위하여 사용합니다.
useCallback
은 deps 배열의 요소가 변경되지 않는 이상, 함수를 새로 생성하지 않기 위하여 사용합니다. 대표적으로, 자식 컴포넌트에 함수를 넘겨줄 때 사용합니다.
React.memo는 HOC이고, useMemo는 훅입니다.
React.memo는 컴포넌트 자체를 메모이제이션하는 용도로 사용하고, useMemo는 복잡한 계산의 결과 값을 메모이제이션하는 용도로 사용합니다. 물론 컴포넌트도 넣을 수는 있습니다.
컴포넌트를 map을 통해 매핑할 때, key값으로 index를 사용하지 않는 등의 방법이 있습니다.
React.Memo
, useMemo
, useCallback
등을 사용합니다.
Automatic batching
React 18부터 상태 업데이트를 하나로 통합해서 배치처리한 뒤, 리렌더링을 진행합니다.
웃프게도,
배치처리한ㅎㅜ
라고 썼다가 스팸처리가 되었었다.
hydration error
텍스트 내용 누락, 불일치 등을 이제 경고가 아닌 오류로 처리합니다.
리액트는 서버 마크업을 일치시키기 위해 클라이언트 노드에 삽입이나 삭제를 함으로서 개별 노드를 수정해주지 않고, 트리에서 가장 가까운 <Suspense>
바운더리까지 클라이언트 렌더링으로 돌아갑니다.
Suspense
트리에 완전히 추가되기 전에 컴포넌트가 suspend된경우 리액트는 새 트리를 완전히 버리고, 비동기 작업이 완료될 때까지 기다린 다음, 다시 처음부터 렌더링을 시도합니다.
새로운 js 환경
이제 모던 브라우저 기능인 Promise
, Symbol
, Object.assign
에 의존합니다. 따라서 인터넷 익스플로러 등 오래된 브라우저를 지원해야 하는 경우 글로벌 폴리필을 추가하는 것을 고려해야 합니다.
CSR은 렌더링이 클라이언트 단에서 일어나는 방식으로, 서버가 클라이언트에 HTML과 JS를 보내고, 클라이언트는 이를 받아 렌더링을 시작합니다.
일반적으로 다음과 같은 순서로 진행됩니다.
Redux는 자바스크립트 상태관리 라이브러리입니다.
Redux는 모든 상태의 업데이트를 액션으로 정의하고, 액션 정보에 기반하여 리듀서에서 상태를 업데이트하기 때문에, 상태를 더욱 쉽게 예측가능하게 하여 유지보수 측면에 긍정적인 효과가 있습니다.
또한 Redux-saga, Redux-thunk와 같은 미들웨어를 통해 비동기작업에 대해 더 디테일하고 편한 컨트롤을 할 수 있게 됩니다.
단점
가장 큰 단점은 어렵습니다. 리덕스의 기본 개념 자체를 이해하기 어렵습니다.
그리고, 리덕스를 유용하게 사용하려면 많은 패키지를 추가해야 합니다. 또한 리덕스 스토어 환경설정도 복잡하며, 보일러플레이트 코드를 너무 많이 요구합니다.
이는 Redux-toolkit을 사용하면, 어느정도 해결되는 문제입니다.
장점
리덕스를 잘 쓸 수 있게 되는 것의 가장 큰 장점은 상태관리가 쉽다는 것입니다.
프로젝트의 크기가 커지면, 상태가 굉장히 많고 다양해집니다.
그러나 Redux를 사용하면, 웹사이트의 상태를 어디서 관리해야 할지 고민하지 않아도 됩니다.
그리고, 어떤 액션을 통해 어떤 데이터가 변경되었는지 쉽게 알 수 있기 때문에, 상태를 좀 더 예측하기 쉽습니다.
Flux 패턴은 사용자 입력을 기반으로 Action을 만들고, Action을 Dispatcher에 전달하여, Store의 데이터를 변경한 뒤, View에 반영하는 단방향의 흐름으로 애플리케이션을 만드는 아키텍처입니다.
Context API는 상태관리 도구가 아닙니다.
Context API는 prop drilling을 해결하기 위해 나온 도구이고, Redux는 상태를 좀더 쉽게 관리하고자 나온 도구입니다.
즉, Context API와 Redux를 비교하는 것보다는 Context API와 useState
, useReducer
를 이용한 상태관리와 Redux를 비교하는 것이 옳습니다.
이 둘의 차이점은 다음과 같습니다.
Redux는 컴포넌트에 상태값을 종속시키지 않고 바깥에서 관리할 수 있습니다.
또한 Redux는 리렌더링을 최적화합니다. useReducer와 달리, Redux를 사용하면 특정 값이 변경될 때에만 re-render할 수 있습니다. 이를 통해 성능 향상을 꾀할 수 있습니다.
그리고, useReducer
는 미들웨어가 없습니다.
저도 면접 준비하면서 많이 시달렸는데 이력서에 한 줄이라도 더 추가하려고 대기업들이 개최하는 프로그램 다 참여했어요 !!
이번엔 넥슨에서 개최하는 게 있는데 참가신청만 해도 상품을 준다네요 아래 링크 첨부해드려요!
같이 화이팅입니다 :)
http://m.site.naver.com/1boix
다시 FE로 복귀하시나요..?