프론트엔드 기술 면접 질문을 대비해보자!

LUCAS·2023년 1월 18일
6

자바스크립트에서 동시성을 어떻게 지원하고 있나요?

자바스크립트는 싱글 스레드 언어이기 때문에, 자바스크립트 코드는 전체 코드가 실행될 때 까지 순차적으로 실행됩니다. 이는 웹 UX 관점에서 치명적일 수 있기 때문에 자바스크립트는 비동기 처리를 지원하기 위한 여러 기능을 제공합니다.

자바스크립트에서 주로 사용되는 비동기 처리 기능으로는 콜백 함수, Promise, async/await 등이 있습니다.

비동기 처리를 위해 사용할 수 있는 이러한 기능을 사용하면 자바스크립트에서도 동시성을 지원할 수 있습니다.

어떤 꼬리 질문이 있을 수 있을까?: 이벤트 루프

자바스크립트에서 자료형 Set과 Map을 사용해보신적이 있을까요?

Yeah! 주로 중복을 없애거나, 요소들을 저장할 때 사용했었습니다.

자바스크립트 자료형 Set은 순서보장이 될까요?

Set 자료형은 저장된 요소들의 순서를 보장하지 않습니다.

자바스크립트 자료형 Map은 Literal Object와 비교해 시간복잡도 및 차이가 어떻게 되나요?

Map 자료형은 저장된 요소들의 순서를 유지하나, Literal Object는 요소들의 순서를 유지하지 않습니다.
Map 자료형은 어떤 타입의 값이든 저장할 수 있으나 Literal Object는 객체의 프로퍼티 값으로만 저장할 수 있습니다.
Map 자료형은 값을 삭제할 수 있으나, Literal Object에서는 객체의 프로퍼티를 삭제할 수 없습니다.

Map 자료형은 값을 추가하거나 삭제할 때 시간 복잡도가 O(1)입니다. 그러나 Literal Object에서는 객체의 프로퍼티를 추가하거나 삭제할 때 시간 복잡도가 O(n)이 될 수 있습니다.

이유로는 Map 자료형이 구현된 방식이 다르기 때문입니다.

Literal Object는 객체의 프로퍼티들을 해시테이블로 구현하고 있기 때문에 O(n)이 될 수 있습니다.

다만 Map 자료형은 값을 저장할 때 요소들을 연결리스트로 연결하고 있습니다. 따라서 값을 추가하거나 삭제할 때 시간복잡도가 O(1)이 됩니다. 이는 요소의 수가 커질 수록 시간복잡도가 증가하지 않기 때문입니다.

그럼 대부분의 경우에서 자료형 Map이 Literal Object 보다 좋은 걸까요?

아니요. Literal Object가 Map 자료형보다 선호될 수 있는 점도 있습니다.
Literal Object가 Map 자료형보다 적은 용량을 차지하기 때문입니다.
또한 일반적인 객체 생성과 같은 구문을 사용할 수 있기 때문에 코드의 가독성이 높아질 수 있습니다.

CSS-IN-JS가 어떻게 컴포넌트를 스타일링해줄 수 있는지 아시나요?

일반적으로 CSS-IN-JS 라이브러리는 자바스크립트 코드 안에서 스타일을 정의할 때 고유한 클래스 이름을 생성하고 이 클래스 이름을 컴포넌트에 적용합니다.
이렇게 하면 컴포넌트 간의 스타일 충돌을 줄일 수 있고, 컴포넌트를 재사용할 때 전역 스타일이 적용되지 않도록 할 수 있습니다.

그러나 이러한 고유한 클래스 이름은 일반적으로 알기 어렵고 직관적이지 않기 때문에, CSS-IN-JS 라이브러리는 일반적으로 고유한 클래스 이름을 생성할 때 Hash 값을 사용합니다. 이는 컴포넌트 스타일을 정의할 때 같은 스타일 정의가 있는지 쉽게 확인할 수 있고, 컴포넌트 스타일이 업데이트될 때 캐시를 깨기 쉽게 할 수 있기 때문입니다.

재사용성 측면에서 CSS파일과 CSS-IN-JS가 가지는 차이가 무엇일까요?

CSS 파일은 전역 스타일을 적용할 수 있기 때문에 컴포넌트를 재사용할 때 의도하지 않은 스타일이 적용될 수 있습니다. CSS-IN-JS는 컴포넌트 안에서 스타일을 정의하기 때문에 컴포넌트를 재사용할 때 전역 스타일이 적용되지 않고 의도한 스타일만 적용됩니다.

React에서 최적화 훅(useMemo, useCallback)을 사용하시는데, 어떤 기준으로 사용하시는지 궁금합니다.

둘다 캐싱을 통해 리렌더링될 경우 새로운 값이 재생성되지 않기 위해 사용되는 최적화 훅입니다.
물론, 이 둘에 대해서 Dependancy Array를 통해 의존성에 대한 값에 대한 디핑을 시도하여 캐시를 사용할지에 대한 여부를 판단하게 되는데, 디핑 연산 또한 성능을 저하할 수 있는 요소가 될 수 있기 때문에 계산량이 많거나 시간이 오래 걸리는 작업을 최적화할 때 사용해야합니다.

따라서 저는 React-Dev-Tool을 통해 컴포넌트에 대해 리렌더링 횟수 와 사유 및 비용을 프로파일링 하고 컴포넌트가 가지는 실제 성능과 작업 내용을 고려해 최적화 훅의 도입 여부를 결정합니다.

Atomic Design Pattern의 단점이 무엇이라 생각하시나요?

요소의 수가 많아지면 요소들의 계층 구조를 관리하기가 어렵다는 부분이 있습니다.
요소들이 많아질 수록 원자들을 조합하여 새로운 요소를 만드는 작업이 점점 복잡해질 수 있기 때문입니다.
또한 요소들을 원자적인 단위로 구분하기 위해 요소들의 속성과 기능을 제한하게 되는데, 이 경우 요소들의 고유한 속성이나 기능을 가지기 어렵게 할 수도 있습니다.

오버 엔지니어링을 방지할 수 있는 최적의 방법이 무엇이라 생각하시나요?

개발 과정에서 주기적으로 코드 리뷰를 수행하는 것이라 생각합니다.
코드 리뷰를 통해 개발자들은 코드가 기능하기 위해 필요한 정도로만 코드가 작성되었고, 구조가 양호하며 스타일이 통일되어있는지 등을 검토할 수 있습니다.

또한 개발 과정에서 적절한 기술 스택과 아키텍처를 선택해 유지보수 쉽고 코듀의 구조가 양호해지도록 설계하는 것이 중요합니다.
또한 적절한 유지보수 정책을 수립해 이 일련된 과정이 반복될 수 있도록 하는 것이 무엇보다 중요할 것 같습니다.

프론트엔드에서 테스트 코드를 어떻게 작성하시는 편인지 생각이 궁금합니다.

주로 Jest와 RTL을 사용해 테스트 코드를 작성하고 있습니다.

Jest는 단위 테스트 프레임워크로, 컴포넌트의 기능 및 스냅샷을 검증하고, RTL을 통해 렌더링 결과를 검증하고 컴포넌트의 속성과 상태, 이벤트 핸들러 검증 등을 진행합니다.

기술 스택을 선정할 때에도 테스트 코드에 얼마만큼의 보일러 플레이트를 생성할지를 염두에 두는 것 같습니다.
예를 들어 테스트하려는 컴포넌트가 Redux Store에 접근하는 경우, 테스트 코드에서도 이를 제공해주어야 하기 때문입니다.

최근에는 SDD를 통해 StoryBook으로 테스트 코드를 작성하고 있습니다.
JSDOM을 통해 진행되는 테스트의 경우 개발자가 직접 테스트 되는 화면을 확인할 수 없어 테스트 코드 작성에 대한 동기 부여가 SDD에 비해 떨어질 수 있다고 생각하기 때문입니다.

프론트엔드에서는 디자인이나 유저 플로우에 대한 변경이 잦을 수 있는데, 그렇다면 사용자의 버튼 인터렉션이나 색상에 대한 테스트가 결국 수정이 잦아 유지보수 비용이 높은 테스트 코드를 만드는 악습관이 되는건 아닐까요?

따라서 테스트 코드를 작성할 때 적절한 경계를 지정하는 것이 중요하다고 생각합니다.

예를 들어, 컴포넌트가 전달받은 속성을 제대로 출력하는지 검증하는 테스트 코드는 사용자의 버튼 인터렉션과 색상에 상관없이 작동할 것입니다. 그러므로 컴포넌트의 기능과 스냅샷을 검증하는 테스트 코드는 일반적으로 유지보수 비용이 낮습니다.

따라서 테스트 코드를 작성할 때에는 어떤 요소들이 고정되어있고 어떤 요소들이 변경될 수 있는지 생각해보고, 적절한 경계를 설정하는 것이 중요합니다.

Left+Top으로 움직이는 고양이 사진과, Transform을 통해 움직이는 강아지 사진이 있다고 가정해보았을 때, 고양이 사진이 오래된 디바이스 기기에서 끊김 현상이 발생하는 이유가 무엇인지 아시나요?

전자의 경우 이미지의 위치가 움직이기 위해서 브라우저가 새로운 위치를 계산해야 하기 때문에 상대적으로 느릴 수 있습니다.

즉 position을 사용할 경우 Layout, paiting, composite 까지 매 프레임마다 과정을 거치겠지만, Transform은 레이어의 합성만 다시 발생하기 때문에 Composite만 거치게 된다.

Suspense는 어떨 때 사용을 해야하고, 어떤 식으로 동작하는지 설명해주세요.

Suspense는 컴포넌트의 렌더링을 어떤 작업이 끝날 때 까지 잠시 중단시키고, 다른 컴포넌트를 먼저 렌더링할 수 있도록 도와주는 기능입니다.
이를 사용하면, Component Lazy Loading 이나, Data Fetching 등의 비동기 처리를 할 때 응답을 기다리는 동안 fallback UI를 보여주고 그 사이에 우선 순위가 높은 다른 UI 들을 동시적으로 먼저 렌더링할 수 있습니다.

Suspense는 자신에게 주어진 자식 컴포넌트에 대해 아래와 같은 전략으로 fallback을 처리합니다.

  1. Suspense가 ChildComponent를 렌더할 때 데이터를 캐시로부터 읽으려고 시도합니다.
  2. 데이터가 없으므로 ChildComponent의 캐시는 Promise를 Throw 합니다.
  3. Suspense는 이 Promise를 받아 fallback을 처리하고 정상적으로 Promise가 Resolve 되었다면 다시 ChildComponent를 보여줍니다.

프론트엔드 측면에서 성능 최적화를 진행하셨던 경험이 있다면 들려주세요.

이미지 최적화를 위해 레스터 이미지를 벡터 이미지로 변환하고, 이미지 크기를 줄였습니다.
자주 사용되는 자원은 캐시에 저장하여 재사용할 수 있도록 하고, 코드 스플리팅을 통해 필요한 코드만 사용할 수 있게끔 경량화하였습니다.

또한 웹 성능 최적화를 위해 네트워크 최적화도 진행하였는데, 중요한 요청을 우선적으로 처리하도록 요청 순서를 재정렬하고, 요청 수를 줄이기 위해 콘텐츠 재사용 기술을 적용하고, 압축과 캐싱을 적용하여 전송되는 데이터를 줄였습니다.

리액트에서 최적화에 대한 필요성을 느꼈을 때는 어떤 때입니까?

저는 리액트가 컴포넌트를 Composite 하며 확장해나간다는 관점에서 컨텍스트 관리에 신경을 많이 쓰고 있습니다.

컨텍스트에 따라 컴포넌트가 리렌더링될 수 있고, 초기에 컨텍스트 관리가 힘들어지면, 해당 컴포넌트를 확장한 컴포넌트도 성능이 떨어질 수 밖에 없었습니다.

따라서 저는 React-Dev-Tool을 자주 사용합니다.
프로파일링을 통해서 컴포넌트에 대한 렌더링 지표를 모니터링하고 프레임이 밀리거나 사용자 인터렉션 관점에서 UX 상 치명적으로 판단될 경우에 최적화를 시도합니다.

다만, Atomic Design Pattern을 자주 사용하기 때문에 Atomic 컴포넌트에 대해서는 더욱 최적화에 신경을 쓰고 있습니다.

서버사이드 렌더링 타임에서 요청을 하는 부분에 있어 최적화를 하신 경험이 있을까요?

SSR에서 네트워크에 요청을 하는 관점에서 수많은 최적화를 진행하였습니다.

React-Query를 통해 동일한 요청에 대해서 캐싱된 데이터를 사용할 수 있게끔 처리하였습니다.
이 경우에도 쿼리 클라이언트에 대해 매번 만들도록 하여 캐시 데이터가 SSR 요청마다 갱신되도록 처리했고, SSR 라이프사이클이 끝나면 인스턴스를 destory하도록 하여 가비지컬렉터가 인스턴스 자원에 대한 메모리 반환이 이루어지도록 유도하였습니다.

Application Layer 외의 단에서 최적화를 시도해보신 경험이 있을까요?

사실 웹 어플리케이션에 있어 가장 좋은 최적화 방법은 요청을 하지 않는 네트워크 요청이라 생각합니다.

조금 이상한 말일 수도 있지만, ETag를 통해 네트워크에 캐시 데이터 해시에 대한 유효 토큰 검증을 질의하는 것보다 cache-control을 통한 캐시 데이터에 대한 Stale 판단 근거를 프론트엔드에 정의 하는 방식으로 캐시 전략을 사용했던 부분이 있고, 이를 CDN에서도 활용한 경험이 있습니다.

또한 네트워크 요청에 의해 반환되는 데이터에 대해 압축을 요청하거나 이미지 에셋과 관련해 레스터와 벡터를 상황에 맞게 디자이너 팀에 요청하는 등 많은 시도를 하였습니다.

브라우저에서 렌더링하는 과정에 대해 설명해주세요

브라우저가 웹서버로 부터 정적인 HTML 파일을 받아오게 되면 변환 작업을 거쳐 HTML의 원시 바이트를 파싱하고 HTML에 정의된 인코딩에 따라 개별 문자로 변환하는 작업을 거칩니다.
이후 토크나이징을 통해 변환된 문자열을 통해 HTML5 표준에 지정된 고유 토큰으로 변환하며, 렉싱 과정을 거쳐 토큰을 해당 속성 및 규칙을 정의한 노드로 변환합니다.
마지막으로 노드 간의 연관성을 지닐 수 있도록 트리를 만드는데 이를 DOM Tree라 합니다.
DOM Tree를 만들었던 것 처럼 CSSOM Tree를 만들어 visible한 노드를 대상으로 render Tree를 만듭니다.

이후 Layout 작업을 거쳐 노드 요소들이 배치될 영역 등을 계산하고 paint 과정을 거쳐 레이어에 그리는 작업을 합니다.

최종적으로는 composite을 거쳐 레이어를 합성하는 작업을 통해 사용자에게 노출됩니다.

reflow, repaint에 대한 설명을 추가적으로 드리자면, reflow는 요소의 크기나 위치가 변경되어 변경된 attribute가 포함된 render Tree를 다시 만들고 요소에 대한 위치를 재계산해 브라우저에 재배치 하는 과정을 뜻합니다.
repaint는 요소의 색상이나 배경 이미지가 변경될 때 실제 화면에 그려지는 부분을 다시 그리는 과정을 말합니다.

따라서 transform 과 left-top을 통한 요소 재배치 애니메이션에 관련해 transform이 우위를 가지는 이유는 left-top은 레이아웃에 대한 재배치가 이루어져 브라우저에서 매번 계산 오버헤드가 발생하지만, transform은 레이어에 대한 composite만 이루어지기 때문에 성능상 우위를 가집니다.

전자의 경우 reflow에서부터 repaint -> composite 까지 세 과정이 연속되어 진행되기 때문입니다.

리액트에서 말하는 렌더링이란 무엇일까요?

리액트에서 렌더링이란 컴포넌트에 State를 주입하고 변경된 상태를 UI에 반영하는 작업을 리액트의 렌더링이라 생각합니다.

리액트에서 렌더링을 최적화하는 방법은 무엇이 있을까요?

리액트에서 렌더링을 최적화 할 수 있는 방법은 여러가지가 있습니다.
렌더링 시 DOM에 Commit되는 수를 줄이는 것이 렌더링의 성능에 있어 가장 중요한 키 포인트가 될 수 있는데 이 것을 위해 발생할 수 있는 리렌더링을 최소화해야한다고 생각합니다.
단편적으로는 한번의 리렌더링에서 발생하는 계산 요소를 메모이제이션하거나 API에 대한 캐싱 전략이 있을 수 있지만 리액트에서의 리렌더링은 상태를 업데이트 하기 위한 전략이기 때문에 불필요한 리렌더링이 일어나지 않게 하는 것이 중요하다고 생각합니다.

리액트는 reconciliation을 통해 휴리스틱 알고리즘으로 디핑 작업을 수행하고 있는데, 이 재조정이란 과정을 제대로 이해하고 개발해야 리액트에게 재계산을 필요로 하지않는 컴포넌트라 알릴 수 있습니다.

기본적으로 컴포넌트는 자신에게 주어진 key에 따라 리렌더링 시 디핑 알고리즘을 수행할 컴포넌트인지 비교합니다.
이 경우 때문에 render Collection에서 배열 인덱스를 요소의 key로 사용하지 말아야합니다.

두번째로 태그의 이름과 attribute가 변경되는 것을 주의해야합니다.
태그나 attribute가 변경되면 리액트에서는 새롭게 혹은 변경된 컴포넌트라 인식하고 디핑 알고리즘을 수행하기 때문입니다.

리액트 특성 상 부모 컴포넌트가 리렌더링되면 Top-bottom 리렌더 전략에 의해 자식 컴포넌트 까지 리렌더링되기 때문에 이 메카니즘을 이해하고 최적화하는 것이 중요합니다.

이 과정 때문에 UI 변경이 잦은 액션 노드를 분리하고 최소한의 렌더 트리만 리렌더링 및 계산이 이루어지도록 하고, 참조 타입의 얕은 비교 이슈를 피하기 위해 레퍼런스 재 작성을 방지하기 위한 useMemo 및 useCallback과 같은 최적화 훅의 사용을 검토해야합니다.

선호하는 State 관리 툴이 있나요?

저는 상태 관리 라이브러리로 Flux 아키텍처를 지향합니다.
데이터 흐름 자체가 단방향이기 때문에 유지보수나 코드를 관리할때 비교적으로 쉬웠었다고 생각합니다.

사내 프로젝트에서는 Redux를 많이 사용했었기 때문에 최근에는 Redux를 모토로 단방향 데이터 흐름을 채용한 zustand 라이브러리를 사용하고 있습니다.

이 라이브러리는 zero-config으로 별도의 provider 선언이 필요없고 기능 단위 자체를 중앙화된 store에서 처리할 수 있기 때문에 redux와 비슷합니다.

또한 zotai나 recoil에 비해 api가 적은 편이고 dev-tool 및 persist에 대해 기본적으로 지원하고 있기 때문에 별도의 라이브러리를 설치하지 않고 제한적으로 사용할 수 있어 보다 커뮤니티 비용이 감소할 수 있다는 장점이 있다고 생각합니다.

profile
안녕하세요! FE개발자 최근원입니다.

0개의 댓글