[프리온보딩] 리엑트 면접질문 정리

devAnderson·2022년 2월 28일
1

TIL

목록 보기
65/103

중복되어도, 복습의 개념 및 설명할 수 있는 정도로의 개념으로 재학습해서 정리한다

⌚️ 1. 개념과 장점, 컴포넌트란?

리엑트가 만들어진 이유를 알기 위해서는 우선 브라우저의 랜더링 과정과 이의 비효율성을 알아야 한다.
브라우저는
1. html을 파싱하여 DOM트리를 만든다
2. 비동기적으로 CSS 파일을 패칭받고, 분해하여 CSSOM을 만든다
3. 두 트리를 결합하여 랜더링을 위한 랜더트리를 형성한다.
4. 뷰포트에 픽셀단위로 반영되기 위한 레이아웃을 계산한다. (reflow)
5. GPU연산을 이용하는 css코드들(ex, trasform이나 abolute) 이 발견되면 컴포짓 레이어를 만든 후 gpu에서 연산을 한다.
6. 그 후, 두 레이아웃을 결합하여 최종적으로 뷰포트 반영한다 (paint)
7. 변경점이 존재하면, 레이아웃 계산부터 다시 진행하며 페인트를 다시 실행한다 (repaint)

gpu란, 픽셀단위 계산에 최적화된 일종의 서브 컴퓨터이다. gpu는 독립적으로 프로세스, 메모리, 데이터 처리 로직을 가지고 있다. 독립 레이어에서 해당 css 계산들을 하게 된다.

최신 모던 브라우저들은 변화된 내용만큼의 reflow를 계산할 능력이 되긴 하지만서도 여전히 js를 이용한 DOM 조작에 의해 발생되는 뷰포트의 딜레이는 부드럽지 않은 사용자 환경을 제공할 수 있다.

이것을 방지하기 위해 리엑트는 virtual dom을 이용한 가상 트리에서 계산을 마친 후 이 변경점만을 반영하는 방식으로 DOM을 업데이트한다.

또한 SPA을 위한 AJAX요청으로 필요한 정보를 자바스크립트로 받고 해당 내용만을 정확히 반영하기에 유리하다.

컴포넌트란, 리엑트의 Virtual DOM을 업데이트하기 위한 JSX 엘레멘트 객체를 리턴하는 함수로, props를 통해 받은 데이터를 이용해 내부적으로 로직을 연산한 뒤 일정한 결과를 리턴하는 순수 함수이다.

⌚️ 2. Reconciliation

리엑트에서 말하는 reconcilation은 render함수의 호출을 통해 만들어진 Virtual DOM을 특정 조건에 따라 다시 업데이트하여 해당 변경점을 실제 DOM에 반영하는 과정을 의미한다.

공식문서로는 UI의 개념을 객체화한 데이터를 메모리에 저장하고, 이것을 실제 DOM과 동기화하는 과정이라고 설명한다

리엑트를 사용하여 render함수를 호출할때 만들어지는 가상 돔 Virtual DOM은 실제 DOM의 노드 트리 구조를 흉내내어 만들어져 있는 객체로, 내부적으로는 Node API가 존재하지 않는다.

해당 JSX객체는 메모리상에 탑재되어 모든 연산이 메모리 안에서 이루어지고 실제 DOM 객체의 복잡한 트리 내에서 이루어지지 않으므로 빠른 연산이 가능하다.

JSX객체는 바벨에 의해 변환된 후 React.createElement를 통해 호출되어 메모리에 저장되기 위한 javascript 객체로 변환되게 된다.
그리고 이 객체를 React.render 함수의 인자로 전달하면서 Virtual DOM 객체를 형성한 뒤, 메모리에 저장된 후 실제 DOM과 비교 알고리즘을 통해 비교한 뒤에 업데이트를 진행한다.

만약 해당 자바스크립트 코드의 변화에 의해 JSX객체의 내부 상태가 달라지고, 이것을 render을 통해 호출할 경우, 리엑트 라이브러리는 diffing 알고리즘을 통해 기존에 메모리에 저장되어 있던 Virtual DOM에서 새롭게 만들어진 JSX객체를 통해 만들어진 Virtual DOM을 상호 비교하여 빠르게 바뀐점을 파악한 뒤, 이 파악점을 실제 DOM에게 변경하라고 명령한다.

⌚️ 3. React Lifcyle

클래스 컴포넌트를 기준으로 설명하자면

  • constructor을 통해 리엑트 엘레멘트 객체를 만들고, 바벨을 통해 createElement를 호출하여 JSX객체를 만듭니다.
  • render을 통해 Virtual DOM을 형성한 뒤, 이것을 실제 DOM에 반영합니다
  • componentDIdMount를 통해 DOM에 마운트가 된 이후 작업을 실행합니다.
  • 상태에 변경점이 생긴다면 다시 constructor을 통해 엘레멘트 객체를 만들고, createElement를 호출하여 JSX객체를 만든 뒤 render을 통해 DOM에 반영합니다.
  • 이후 componentDidUpdate를 통해 변경점이 반경된 이후의 작업을 실행합니다.
  • 컴포넌트가 마운트가 해제가 되면 (객체 상에서 빠지게 된다면) 그 순간 실행될 내부 함수 componentWillUnmount가 호출됩니다.

잘 사용되지 않는 함수 getDerivedStateFromProps = props가 바로 내부의 상태와 연동되야 할 때에 사용합니다.
componentShouldUpdate = 업데이트를 해야하는지에 대한 유무를 개발자가 직접 컨트롤하지만 추천되지 않습니다.
getSnapshopBeforeUpdate = 업데이트가 되기 전에 기존 상태값이 현재 UI에 필요할 경우 사용됩니다 (ex, 기존 스크롤 위치)

⌚️ 4. 클라이언트 사이드 라우팅

웹 페이지의 라우팅을 클라이언트 사이드에서 받은 js파일을 통해 실행되는 것을 의미합니다.
서버에는 초기 랜더링을 위한 js 파일을 받은 후, 이후 뷰포트의 내용이 변경되기 위해 해당 받아온 js 파일의 내부 로직을 사용하게 되므로 서버는 AJAX를 통한 요청의 응답만 날리게 되어 부담이 덜어집니다.

하지만, 초기에 받아오는 html 파일에는 SEO를 위한 초기 데이터가 존재하지 않으므로 검색노출이 불리하다는 점이 있습니다.
(고 기존까지는 그랬으나, 현재는 google의 search robot이 알아서 js파일도 분해하여 랜더링 한 후 탐색하기 때문에 해당 불리점은 현대에서는 적용하기 어렵다고 여겨집니다.)

⌚️ 5. state을 직접 바꾸지 않고 setState을 사용해야 하는 이유

리엑트는 상태에 있어 불변성을 유지해야 한다는 원칙을 가지고 있습니다.
불변성을 유지한다는 것은, 해당 상태 프로퍼티에 저장되는 값이 항상 기존과는 전혀 다른 상태를 가져야 한다는 것을 의미합니다.
만약 불변성을 유지하지 않고 내부 객체 자체를 변경하는 형태로 이루어졌을 경우, 리엑트 라이브러리는 해당 상태의 변경점을 확인하기 위해 모든 객체의 상태를 다 비교해야 하는 불편함을 가집니다.

따라서 비교 알고리즘 최적화를 위한 얕은 비교를 위해 리엑트는 해당 상태를 전부 다 하나의 새로운 상태로 교체하는 방식으로 상태를 업데이트합니다.
또한 리엑트 라이브러리는 setState 메서드를 통한 변경점만을 실시간으로 반영하여 리랜더링을 하기 때문에 사용해야 합니다.
기존 state의 내부를 그대로 변경할 경우, 리엑트 라이브러리는 해당 상태객체의 레퍼런스 주소가 변경되지 않았으므로 상태가 변경되지 않았다고 판단하여 리랜더링을 진행하지 않습니다.

⌚️ 6. hook과 hook의 사용 이유

공식문서에 따르면, 기존 class 컴포넌트의 경우, 상태가 기존 JSX객체의 프로퍼티에 귀속되기 때문에 재활용성이나 독립적인 상태관리 로직을 구현하기 어렵다고 한다. 또한, class컴포넌트 내부에서 계속해서 사용되는 this는 호출 대상에 따라 달라지는 변수이기때문에 오류를 일으킬 가능성이 높고 혼란을 야기할 가능성이 많다.

따라서 상태관련 로직을 완전하게 분리하여 독립적으로 관리하는 hook을 사용하면 상태관련 로직을 재사용하는 데 도움을 준다.

또한 기존 class 함수는 복잡한 라이프사이클 함수들이 늘어남에 따라 내부 코드가 복잡하게 얽히고 서로간의 관계성을 파학하기 어려운 단점이 존재했다.

이를 hook에서 제공하는 useEffect를 활용하면 두번째 인자로 제공하는 dependency 배열을 통해 손쉽게 라이프사이클을 추상화하는것이 가능하다.

⌚️ 7. JSX란

JSX는 javascript XML의 줄임말로 자바스크립트 내부에서 XML 문법을 사용한 코드를 리턴하는 함수로 구성되어 있습니다. 이 함수는 바벨을 통해 컴파일이 될 때 React.createElement로 변환되어 호출되며, 이 함수가 리턴하는 객체에는 Virtual DOM을 만들기 위한 다양한 프로퍼티들이 존재합니다.
이렇게 만들어진 객체를 render 함수의 인자로 전달하면 Virtual DOM을 생성하여 메모리에 저장합니다.

⌚️ 8. useMemo와 useCallback의 차이점

두 함수 모두 리엑트 최적화를 위해 만들어졌습니다. 리엑트는 기본적으로 내부 상태가 변경되면 createElement를 통해 새 객체를 형성하여 새로운 Virtual DOM을 만듭니다. 그런데 이 때, 함수 내부의 코드들은 역시 호출 전 재평가를 통해 함수객체를 형성해야 하고, 내부적으로 값이나 함수가 여러개 존재하지만 변동이 없을 경우 불필요한 재평가 과정을 진행하게 됩니다. 또한 특정 함수를 호출한 결과가 몹시 연산작업이 클 경우, prop 내용이 변동되지 않았음에도 불구하고 해당 함수를 계속해서 호출하는 것은 비효율적이라고 할 수 있습니다. 따라서 이 평가값들을 캐시형태로 저장한 후, dependency의 변동이 있을 때만 재평가와 호출을 하게 만드는 방식이 useMemo 그리고 useCallback입니다.

useMemo는 첫번째 인자로 들어오는 함수가 호출되어 리턴하는 값을 기억합니다.
useCallback은 첫째 인자로 들어오는 함수가 평가가 완료된 함수객체값 그 자체를 기억합니다.
결국 두 함수 모두 첫째 인자에 주어지는 값을 기억한다는 것은 동일하기 때문에, 만약 useMemo에 리턴되는 값이 함수객체 그 자체라면 useCallback과 큰 차이가 없지만 가독성이 좋지 않고 네이밍적으로도 추론하기 어려우므로 지양하는 편이 좋습니다.

⌚️ 9. 리엑트의 상태변화 감지방법

리엑트의 상태에 저장되는 것은 값의 메모리 주소로, 일반적인 할당과 동일합니다. 다만 리엑트는 항상 상태의 불변성을 유지합니다. 즉, 기존의 상태와 현재의 상태가 저장된 메모리 값의 주소는 항상 다르기 때문에 비교 알고리즘을 통해 해당 주소가 다르다면 상태가 달라졌다고 인식하고 리랜더링과 관련된 함수를 호출합니다.

⌚️ 10. redux / mobX/ apollo 의 비교

  • redux : flux 패턴을 이용한 단방향 상태변화 관리 툴입니다. 하나의 source of truth를 가지며, 불변성을 유지하는 스토어가 존재하고 해당 스토어를 변경시키기 위해서는 오로지 액션객체를 리턴하는 순수함수를 이용하여 디스패쳐에 전송해 이 액션의 타입에 따라 스토어의 내부 구조를 변경하는 형태로 이루어져 있습니다.
  • mobX : OOP 패턴을 이용한 상태변화 관리 툴입니다. 제공되는 메서드인 observable을 이용해 계속 변화를 감지할 객체를 전달하고, reaction 메서드를 통해 해당 객체의 변경점이 감지된다면 두번째 인자인 콜백함수를 실행하도록 되어있다.
  • apollo : graphQL 기반의 상태관리툴로, 기존 RestAPI의 단점인 overFetching(필요하지 않은 데이터도 같이 옴) 이나 underFetching( 필요한 데이터를 다 전달받지 못해서 추가엔드포인트가 필요해짐) 과 같은 문제를 해결하고 클라이언트측에서 필요한 데이터를 요청해 받아오는 데이터를 저장하는 형태로 되어있다.
profile
자라나라 프론트엔드 개발새싹!

0개의 댓글