프론트엔드 인터뷰 예상 질문 (2) - React

서동경·2023년 11월 11일
0

인터뷰 준비

목록 보기
2/3
post-thumbnail

💬 ⭐ React란

  • 효율적으로 사용자 인터페이스를 만들기 위한 자바스크립트 라이브러리이다. 컴포넌트 기반 개발이 가능하고, 단방향 데이터 흐름을 가지며, 가상 돔을 이용한 효율적인 업데이트가 가능하다는 특징이 있다. 또한 Client Side Rendering을 통한 Single Page Application 개발이 가능하다.

◽ 라이브러리나 프레임워크의 차이

  • 라이브러리는 개발을 위한 기능을 제공하는 도구이고, 프레임워크는 개발을 위한 구조를 제공하는 도구이다.

◽ 리액트를 사용한 이유

  • 컴포넌트 기반으로 재사용성이 뛰어나고, 단방향 데이터 흐름을 가져 데이터를 추적하여 유지보수하는 것이 용이하며, 가상 돔을 이용한 효율적인 업데이트가 가능하기 때문이다. 또한 Next.js, React Native와 같은 리액트 기반 기술의 러닝 커브를 줄일 수 있다고 생각했다.

    제이쿼리

    • 리액트처럼 자바스크립트의 사용성을 개선하는 라이브러리이다. 여러 브라우저에서 호환성이 뛰어나다는 장점이 있지만, 가상 돔을 사용하는 리액트보다 낮은 성능을 보여줬고, 이와 동시에 크롬의 점유율이 높아짐에 따라서 인기가 낮아지고 있다.

💬 JSX(JavaScript XML)

  • 리액트에서 사용하는 자바스크립트 확장 문법으로, 자바스크립트 파일 내부에서 XML과 유사한 형태의 마크업 코드를 작성할 수 있게 해준다. 이로 인해 마크업 파일을 분리할 필요가 없어져 가독성이 개선되고 유지보수가 용이해진다.

💬 ⭐ 가상 돔(Virtual DOM)

  • 상태 변화를 추적하여 효율적인 업데이트를 수행하는 실제 DOM의 복사본이다. / 가상 돔을 통한 업데이트는 변경 사항이 적용되지 않은 가상 돔과 변경 사항이 적용된 가상 돔을 비교함으로써, 일정 시간동안 발생한 상태 변경을 일괄 적용(=Batch Update, Batching)할 수 있고 최소한의 변경만을 실제 DOM에 반영하여 리플로우와 리페인트를 최소화할 수 있다.

    Auto Batching

    기존에는 React event handler에서만 Batch Update가 일어났다. 그러나 React 18 이후 Auto Batching 기술이 발표되면서, Promise, 타이머 함수, Native event handler에 모두 Batch Update가 적용되도록 개선되었다.

💬 Key

  • 동적으로 렌더링되는 여러 요소에 고유값을 부여하기 위한 프로퍼티이다.
  • 리액트의 JSX 문법에서 map을 통해 여러 요소를 동적으로 렌더링할 때, 요소마다 고유한 key를 부여하여 요소를 구별해야 한다. 그렇지 않으면 요소를 구별할 수 없어서 모든 요소를 다시 렌더링하므로 Warning이 발생한다.

💬 ⭐ CSR vs SSR

  • CSR은 첫 단계에서 서버로부터 빈 HTML을 전달받는다. 이후 모든 페이지에 대한 자바스크립트를 다운로드받아 실행하여 'Viewable한 상태'를 완성시키고 사용자와의 상호 작용에 따라 'interactive한 상태'까지 완성한다. CSR은 (완전한 콘텐츠가 보이는)첫 로딩은 느리지만 그 이후 페이지를 이동할 때는 빠른 속도를 가지며, 검색 엔진 최적화에 불리하다는 특징이 있다. / 반면 SSR은 첫 단계에서 서버로부터 해당 페이지에 대한 'Viewable한 상태'의 완전한 HTML을 전달받는다. 이후 자바스크립트 파일을 다운로드받아 실행하여 'interactive한 상태'까지 완성한다. SSR은 첫 로딩은 빠르지만 페이지 이동 시에는 비교적 느린 속도를 가지며, 검색 엔진 최적화에 유리하다는 특징이 있다.

    SPA(Single Page Application)

    처음에 서버에서 빈 HTML를 받아온 이후 사용자와의 상호 작용에 따라 동적으로 페이지를 렌더링하는 애플리케이션이다. / 클라이언트단으로부터 필요한 데이터만을 비동기적으로 로드하기 때문에 첫 로딩을 제외한 속도가 빠르고 새로고침이 발생하지 않는다는 장점이 있다.

💬 ⭐ 검색 엔진 최적화(SEO)

  • 구글이나 네이버 같은 사이트의 검색 엔진이 HTML 문서를 잘 분석하도록 하는 작업을 검색 엔진 최적화라고 한다. / CSR 방식을 사용하는 사이트들은 서버로부터 빈 HTML을 전달받기 때문에 검색 엔진에 빈 HTML이 수집되어 검색 엔진 최적화가 잘 이루어지지 않는다.

◽ 검색 엔진 최적화를 할 수 있는 방법

  • Next.js와 같은 프레임워크를 이용한 SSR이나 SSG 방식을 사용하는 것이 방법이 가장 효율적이다. / CSR 방식을 사용해야 하는 경우, react-helmet, react-snap 등의 라이브러리를 통해 검색 엔진 최적화 성능을 높일 수 있다.
  • react-helmet은 페이지 별로 메타 데이터를 설정할 수 있도록 해주는 라이브러리이고(여전히 HTML 파일은 하나), react-snap은 페이지 별로 각각의 HTML 파일을 가지도록 해주는 라이브러리이다.

💬 컴포넌트

  • 리액트에서 사용하는 재사용 가능한 독립적인 단위로, 자체적으로 상태를 가지고 이 상태에 따라 업데이트를 수행한다.

◽ 클래스 컴포넌트와 함수 컴포넌트

  • 클래스 컴포넌트는 class 키워드와 extends 키워드를 통해 React.Component 객체를 상속받아 만들어지는 컴포넌트이고, 함수 컴포넌트는 함수로 작성된 컴포넌트이다.

  • 클래스 컴포넌트는 constructor를 통해 상태를 관리하고 componentDidMount, componentDidUpdate, componentWillUnmount를 사용하여 생명 주기에 대한 동작을 정의한다. 반면 함수 컴포넌트는 useState Hook을 통해 상태를 관리하고 useEffect Hook을 통해 생명 주기에 대한 동작을 정의한다.

  • 함수 컴포넌트는 Hook의 도입과 this를 사용하지 않는다는 점에서 간결한 코드를 작성하기 유리하다. 또한 메모리를 덜 사용한다.

    super(클래스 컴포넌트)

    부모 클래스의 생성자(ex. React.Component 객체의 constructor)를 호출하고 실행하는 메서드.
    super를 호출하기 전에는 this를 사용할 수 없다.

    render(클래스 컴포넌트)

    JSX를 정의하고 반환하는 메서드.

    forceUpdate(클래스 컴포넌트)

    강제 업데이트 메서드.

◽ 컴포넌트 생명 주기(라이프 사이클)

  • 컴포넌트는 마운트, 업데이트, 언마운트라는 생명주기를 가진다. 마운트는 컴포넌트가 실행되어 DOM에 등록되는 시점이고, 업데이트는 컴포넌트에 변경이 생겨 DOM에 변경 사항을 적용하는 시점이고, 언마운트는 컴포넌트가 종료되어 DOM에서 제거되는 시점이다.

    컴포넌트에서 업데이트가 일어나는 경우

    • state나 props가 변경될 때
    • 부모 컴포넌트가 리렌더링될 때
    • forceUpdate()로 강제 업데이트될 때 (클래스 컴포넌트에서만)

◽ 고차 컴포넌트(HOC, Higher-Order Component)

  • 컴포넌트를 인자로 받아 새로운 컴포넌트를 반환하는 컴포넌트이다. 여러 컴포넌트에서 재사용되는 로직을 추상화할 수 있다.

💬 ⭐ props와 state

  • props는 부모 컴포넌트에서 자식 컴포넌트로 전달하는 데이터이고, state는 컴포넌트 내부에서 관리하는 동적인 데이터이다. / 자식 컴포넌트에서 props는 Read-Only, 즉 수정할 수 없다. 변경되는 것은 state이다.
  • (props는 리액트가 단방향 데이터 플로우를 가진다는 것을 보여준다.)

◽ Props Drilling

  • 상위 컴포넌트에서 타겟 컴포넌트로 props를 전달할 때, 모든 중간 컴포넌트에서 props를 내려주는 비효율적인 작업을 의미한다. / 이러한 문제는 전역 상태 관리 라이브러리를 사용하여 해결할 수 있다.

    자식 컴포넌트의 상태를 부모 컴포넌트로 전달하는 방법

    부모 컴포넌트에서 props를 통해 상태를 변경하는 함수를 자식 컴포넌트에 전달하고, 자식 컴포넌트에서 전달받은 함수를 사용하여 부모 컴포넌트의 상태를 변경하는 방식으로 전달이 가능하다.

◽ 상태 불변성

  • 리액트가 상태 변화를 감지하기 위해서는 상태를 변경하고자 하는 데이터의 불변성을 유지해야 한다. / 원시 타입의 값은 불변성을 가지므로 상태 변화를 잘 감지하지만, 참조 타입의 값은 불변성을 가지지 않아 상태 변화를 감지할 수 없다. 그러므로 참조 타입인 상태를 변경할 때는 스프레드 연산자 혹은 배열이나 객체의 메서드(Array.from, Array.slice, Object.assign ...)를 통해 '얕은 복사'를 하고, 이 복사본을 통해 상태를 변경해야 한다.

    깊은 복사

    데이터 내부에 또 다른 참조 타입 데이터를 가지는 경우, JSON 메서드(JSON.stringify & JSON.parse) 혹은 lodash나 immer 등의 라이브러리를 통해 '깊은 복사'를 하고, 이 복사본을 통해 상태를 변경해야 한다.

💬 Hook

  • 함수 컴포넌트에서 클래스 컴포넌트의 기능을 사용하기 위한 함수이다.

◽ ⭐ useState

  • 상태 관리를 위한 훅이다. useState는 비동기적으로 상태를 변경하고, 일정 시간(16ms)동안 변경된 상태를 일괄 적용하는 Batch Update가 수행된다.

    왜 state를 직접 변경하지 않고, useState를 사용해야 하는가

    내부적으로 render 함수가 호출되어 렌더링이 진행되기 때문이다.

◽ ⭐ useEffect

  • 클래스 컴포넌트의 생명주기 메서드를 대체할 수 있는 훅이다.
  • 의존성 배열 없이 사용한 useEffect의 콜백 함수는 componentDidMount와 componentDidUpdate를 합친 것처럼 동작한다. 빈 의존성 배열을 가지는 useEffect의 콜백 함수는 componentDidMount처럼 동작하는 한편, return문에는 componentWillUnmount처럼 동작하는 정리 함수를 정의할 수 있다. 의존성 배열에 특정값이 들어있는 useEffect의 콜백 함수는 componentDidMount처럼 동작하는 동시에 의존성 배열에 지정된 값이 변경되었을 때는 componentDidUpdate처럼 동작한다.

◽ useLayoutEffect

  • 페인트 작업 전(= 돔 업데이트 직후)에 실행되는 훅으로, 페인트 작업 이후 실행되는 useEffect가 문제를 일으키는 경우에 대안으로 사용할 수 있다.
  • (ex. useEffect 내부에 DOM에 영향을 주는 코드가 있을 경우 화면의 깜빡임이 발생하는데, useLayoutEffect로 변경하면 깜빡임이 발생하지 않음)

◽ useReducer

  • 복잡한 상태 관리 로직을 간결하게 작성하도록 도와주는 훅이다.
  • (state와 action 객체를 파라미터로 받는)리듀서 함수를 switch-case문으로 정의한다. 그리고 useReducer의 파라미터로 리듀서 함수와 state의 초기값을 넣어 호출하면 state와 dispatch 함수를 반환하는데, dispatch 함수에 action 객체의 타입을 전달하여 상태를 쉽게 관리할 수 있다.

◽ ⭐ useContext

  • Context API를 통해 상태를 전역적으로 관리할 수 있도록 도와주는 훅이다.
  • (createContext를 통해 컨텍스트 객체를 생성하고 전역에서 관리할 상태들을 정의한다. 그리고 컨텍스트 객체의 Provider를 통해 공유할 상태를 value에 명시하고 컨텍스트 객체를 공유할 컴포넌트들을 감싸면, 각 컴포넌트에서 useContext를 통해 컨텍스트 객체에 명시한 상태에 접근할 수 있다.)

◽ ⭐ useRef

  • 리액트의 라이프 사이클을 고려하여 DOM에 접근하기 위한 훅이다. DOM API를 이용하여 DOM을 직접 조작하는 것은 리액트 라이프 사이클과 관련이 없기 때문에, useRef를 사용하여 DOM을 조작해야 한다.
  • (useRef를 통해 참조 객체(ref)를 생성하고 DOM 요소의 ref 속성을 통해 DOM 요소와 참조 객체를 연결하여 참조 객체에 ‘실제 DOM 요소의 참조'를 저장한다. 이를 통해 참조 객체의 current 프로퍼티를 통해 DOM을 조작할 수 있게 된다.)

◽ useRef를 이용한 상태 관리

  • useRef는 (useState와 달리) 렌더링에 직접 관여하지 않는다. 따라서 즉시 렌더링에 관여하지 않을 상태 관리를 위해 사용할 수 있다. (useRef를 사용한 변경 사항은 렌더링 시점에 적용!)

◽ Callback Ref

  • DOM 요소의 ref 속성에는 참조 객체 대신 Callback Ref라고 부르는 콜백 함수를 전달할 수도 있다. 이 작업을 통해 해당 DOM 요소가 마운트되거나 언마운트될 때마다 실행될 동작을 정의할 수 있다. (즉 useEffect의 역할 가능!)
  • (Callback Ref는 최초 렌더링 시점에 생성되므로, 불필요한 재생성을 막기 위해 useCallback과 함께 사용하는 것이 유리하다.)

◽ ⭐ forwardRef와 useImperativeHandle

  • forwardRef를 통해 자식 컴포넌트에서 참조 객체를 전달받을 수 있고, useImperativeHandle을 통해 자식 컴포넌트에서 부모 컴포넌트로 함수를 전달할 수 있다.
  • (부모 컴포넌트가 useRef를 통해 참조 객체를 생성하고 자식 컴포넌트의 ref 속성을 통해 자식 컴포넌트와 참조 객체를 연결하면, 자식 컴포넌트는 forwardRef를 통해 참조 객체를 받아올 수 있다. 그리고 (forwardRef 내부에서) useImperativeHandle의 첫 번째 인자에 '부모로부터 전달된 참조 객체'를, 두 번째 인자에 '콜백 함수'를 넣고, 콜백 함수에서 '부모 컴포넌트로 전달할 자식 컴포넌트의 함수'를 반환하면 부모 컴포넌트에서도 current 속성을 통해서 자식 컴포넌트의 함수를 사용할 수 있다.)

◽ ⭐ useMemo

  • 의존성 배열에 있는 값이 변경되지 않는 한 이전에 사용한 결과값을 재사용하는 훅이다.
  • (첫 번째 파라미터로 콜백 함수, 두 번째 파라미터로 의존성 배열을 저장한다. 콜백 함수에는 메모이제이션할 값을 계산하는 로직을 정의하고, 의존성 배열의 값이 변경된다면 콜백 함수를 다시 실행해서 결과값을 업데이트하고 변경되지 않는다면 기존 결과값을 재사용한다.)

◽ ⭐ useCallback

  • 의존성 배열에 있는 값이 변경되지 않는 한 이전에 사용한 함수를 재사용하는 훅이다.
  • (첫 번째 파라미터로 콜백 함수, 두 번째 파라미터로 의존성 배열을 저장한다. 의존성 배열의 값이 변경된다면 새로운 함수를 생성하여 사용하고 변경되지 않는다면 기존의 함수를 재사용한다.)

◽ Custom Hook

  • useState와 useEffect같은 Hook을 사용하여 재사용이 가능하도록 만든 함수이다. 'use'라는 이름으로 시작하고 상태 값과 해당 상태를 업데이트하는 함수를 반환하는 특징을 지닌다.

💬 ⭐ Code Splitting

  • 코드를 여러 개의 작은 chunk로 나누어 해당 기능이 필요한 시점에 해당 chunk를 로드하는 렌더링 최적화 기술이다. / 비동기 로딩을 위해 Dynamic import와 React.lazy를 사용하고 로딩 중임을 표시하기 위해 Suspense를 함께 사용한다.

    Code Splitting 동작 방식

    Dynamic import를 통해 코드를 여러 개의 모듈로 나눈다. 이 모듈들은 bundle.js 파일에 저장되는 것이 아니라, 별도의 chunk 파일로 저장되며, 이 chunk 파일은 해당 모듈이 필요한 시점에 비동기적으로 로드된다. / 한편 Dynamic import는 Promise를 반환하는데, React.lazy를 사용하면 Promise를 직접 처리하지 않고 내부적으로 처리하도록 하여 비동기 로딩을 쉽게 완료할 수 있다. 또한 Suspense의 fallback 속성을 통해 로딩 중일 때 표시할 컨텐츠를 지정할 수 있다.

💬 ⭐ Redux와 Recoil

  • 상태 관리 라이브러리이다.

◽ Redux

  • 하나의 중앙 집중화된 Store 객체에 상태를 저장하고 관리하는 상태 관리 라이브러리이다. / Redux Toolkit과 미들웨어(Redux Thunk, Redux Saga, Redux-observable)를 사용하여 개발 생산성을 향상(리덕스 사용성 개선, 비동기 작업의 효율적 관리)시킬 수 있고, 안정적인 DevTool을 가지고 있기 때문에 대규모 상태를 관리에 용이하다. 다만 코드의 양이 많아 보일러플레이트 코드가 발생하게 된다는 단점이 존재한다.

◽ Recoil

  • atoms에서 상태를 저장하고 관리하는 Context API 기반 상태 관리 라이브러리이다. / 비교적 간단한 구조롤 가지기 때문에 코드의 양을 줄일 수 있지만, 대규모 상태를 관리할 때는 Rudux에 비해서는 안정성이 떨어진다.
  • (atom를 통해 상태를 전역에서 관리하며 selector를 통해 atom에 저장된 상태를 새로운 값으로 계산할 수 있다. RecoilRoot를 통해 Recoil State를 공유할 컴포넌트들을 감싸고 각 컴포넌트에서는 useRecoilState 등의 메서드를 통해 Recoil State에 접근할 수 있다.)

💬 React-Query vs SWR

  • 서버 상태를 가져와 관리하는 것에 중점을 둔 라이브러리이다. Redux, Recoil 등은 서버 상태와 동기화되지 않기 때문에 웹 스토리지 등에서 따로 관리해야하는데, React-Query, SWR 등을 사용하면 이러한 불편함을 해소할 수 있다.

◽ ⭐ React-Query

  • 먼저 서버의 상태(데이터)를 가져온 후 데이터를 패칭/캐싱/동기화하는 라이브러리이다.
  • (SWR에 비해 다양한 기능을 지원한다.)

React-Query의 기능

  • 탭을 이동했다가 다시 돌아왔을 때, 데이터를 다시 보여준다.
  • 변경된 서버 데이터를 다시 불러온다. (setInterval 대체 가능, 양방향은 아니기 때문에 Web Socket은 대체가 어려움)

◽ SWR

  • 먼저 캐시에서 데이터를 반환받은 후, 서버에 데이터를 가져오는 요청을 보내고 최신 데이터를 제공받는 React Hooks 라이브러리이다.
  • (React-Query에 비해 간단하게 사용 가능하다.)

◽ React-Query의 Hook

  • useQuery: 서버의 데이터를 읽어와 fetching하는 훅으로, HTTP GET 요청 시 주로 사용한다. / (첫 번째 인자에는 고유한 queryKey, 두 번째 인자에는 Promise를 반환하는 queryFn을 넣고, 세 번째 인자에는 options을 넣을 수 있다. (querykey는 응답 데이터를 캐싱하기 위해, queryFn은 쿼리 요청을 수행하기 위해 필요하다.) 그러면 data, isLoading, isError, refetch 등 다양한 값을 반환받을 수 있는데, 이를 통해 서버 데이터와 관련된 작업을 할 수 있다.)
  • useMutate: 서버의 데이터를 변경하는 훅으로, HTTP POST, PUT, PATCH, DELETE 요청 시 주로 사용한다. / (인자로 Promise를 반환하는 mutationFn을 넣어주면 mutate 함수를 반환받는데, mutationFn은 mutate의 인자로 전달된다. 그러므로 mutationFn에는 나중에 실행할 내용을 정의하고, mutate를 통해 실행된다. (mutationFn은 queryFn과 달리 곧바로 실행되지 않는다.) 한편 두 번째 인자에는 options을 넣을 수 있는데, onSuccess, onError, onSettled를 통해 추가 로직을 작성할 수도 있다.)
  • useInfiniteQuery: 연속으로 다음 데이터를 가져오는 훅이다. / (첫 번째 인자는 고유한 queryKey, 두 번째 인자에는 Promise를 반환하는 queryFn을 넣고, 세 번째 인자인 options에서는 getNextPageParam을 통해 다음 페이지 호출에 필요한 파라미터를 정의할 수 있다. (다른 작업도 가능!) 그러면 data, isLoading, isError, fetchNextPage, hasNextPage 등 다양한 값을 반환받을 수 있는데, 이 데이터를 통해 무한 스크롤, 페이지네이션 등을 쉽게 구현할 수 있다.)

◽ React-Query의 staleTime과 cacheTime

  • staleTime: 데이터를 fresh한 상태로 유지하는 시간이다. staleTime 동안에는 캐시에 저장된 데이터를 가져오고, staleTime이 경과하고 특정 조건(백그라운드 상태에서 다시 focus될 때 등)이 만족하면 새로운 데이터를 가져온다. / (기본값 0)
  • cacheTime: 쿼리 결과를 캐시에 저장하는 시간이다. / (기본값 5분)

💬 제어 컴포넌트와 비제어 컴포넌트

  • form 요소의 값에 접근하기 위한 방식으로, 제어 컴포넌트는 useState를 사용하여 event.target.value를 상태에 저장하여 값에 접근하고, 비제어 컴포넌트는 useRef를 사용하여 DOM 요소에 접근하여 ref.current.value를 통해 값에 접근한다.
  • 제어 컴포넌트는 form 요소의 값을 리액트 State와 결합하여 단일한 출처의 값을 가진다. 따라서 높은 신뢰도의 값을 가지며 업데이트할 때마다 매번 렌더링이 발생(실시간 유효성 검사 가능)한다는 특징을 가진다. (너무 잦은 렌더링은 debounce, throttle을 통해 방지할 수 있음!)

💬 React Native

  • React를 사용하여 모바일 애플리케이션을 개발하기 위한 프레임워크이다. 하나의 코드로 iOS와 Android에 대한 애플리케이션을 개발할 수 있는 '크로스 플렛폼 앱' 개발 언어이고, 이는 네이티브 앱에 준하는 성능을 보여줄 수 있다.

React와의 공통점

  • React의 핵심 개념인 Component, JSX, props, state를 사용한다.

React와의 차이점

  • 가상 DOM을 사용하지 않고, AppRegistry를 사용한다.
  • HTML 문법을 사용하지 않고, Component만을 사용한다.
  • 기존의 CSS를 지원하지 않고, 자바스크립트 파일 안에서 StyelSheet를 사용한다.
profile
개발 공부💪🏼

0개의 댓글