Notice
Recent Posts
Recent Comments
05-10 23:02
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

-

[React] 리액트 면접 준비 / 질문 답변 정리 (추가중) 본문

Study

[React] 리액트 면접 준비 / 질문 답변 정리 (추가중)

choiht 2024. 1. 30. 17:14
반응형

1. 라이브러리와 프레임워크 차이

1. 프레임워크 

개발 구조나 설계 시 제공되는 인터페이스의 집합.

애플리케이션 개발 시 코드의 품질, 필수적인 코드, 알고리즘, 암호화, DB 연동 등의 기능들이 어느정도 구성되어있는 뼈대를 제공하도록 만들어진 것.

 

2. 라이브러리

특정 기능에 대한 API를 모아놓은 집합.

불러와서 사용이 가능하다. 

 

3. 차이점

라이브러리를 사용한 애플리케이션 코드는 애플리케이션 흐름을 직접 제어한다. 

반면 프레임워크는 거꾸로 애플리케이션 코드가 프레임워크에 의해 사용된다. 

 

 

 

2. 리액트 사용 이유

1. 컴포넌트 기반의 화면 구성

화면을 구성하는 부분들을 컴포넌트라는 단위로 나누며, 독립적으로 관리한다. 

따라서 유지보수가 용이하고 반복되는 부분을 미리 만들어놓은 컴포넌트로 대체할 수 있기 때문에 코드 재사용성을 높이는 장점이 있다.

 

2. Virtual DOM을 사용한 빠른 속도

실제 DOM에서는 브라우저가 화면을 그리는데 필요한 모든 정보(태그 등)가 들어있어 실제 DOM을 조작하는 작업이 무겁다.

그래서 React에서는 실제 DOM의 변경 사항을 빠르게 파악하고 반영하기 위해 내부적으로 가상 DOM을 만들어 관리한다.

Virtual DOM을 사용하면 실제 DOM에 접근하여 조작하는 대신, DOM의 상태를 메모리에 계속 올려두고, 변경 전과 후의 상태를 비교한 뒤 최소한의 내용만 반영하는 방식이다. 

 

3. SPA(Single Page Application)

기존의 웹 서비스는 요청시마다 서버로부터 리소스들과 데이터를 해석하고 화면에 렌더링한다. 

SPA 방식은 브라우저에 최초 접속 시 페이지 전체를 로드하고, 이후에 특정 부분만 Ajax를 사용해 데이터를 바인딩 하는 방식이다. 

 

장점 : 서버의 자원을 아낄 수 있다. 

단점 : 사용자와 인터랙션이 많은 경우에는 불필요한 트래픽이 낭비될 수 있다. 

 

 

 

 

3. Virtual DOM에 대해

DOM(Document Object Model)은 XML이나 HTML 문서에 접근하기 위한 일종의 인터페이스이고, 새로운 요청이 있으면 아래와 같은 순서를 거쳐 리렌더링을 한다. 

 

HTTP response → DOM tree → CSSOM tree → render tree → painting

 

하지만 웹 사이트의 양이 많으면 퍼포먼스가 떨어질 수 있기 때문에 Virtual DOM을 사용한다. 

리액트는 Virtual DOM을 사용해서 실제 DOM에 접근하는 대신, 이를 추상화한 자바스크립트 객체를 구성하여 사용한다. 

아래 그림과 이전 내용과 현재 내용을 비교해 같이 바뀐 부분만 캐치하여 실제 DOM에 적용한다.

 

 

 

 

 

4. 클래스형 컴포넌트 vs 함수형 컴포넌트

1. 클래스형 컴포넌트

  • state를 초기화하기 위해서는 constructor(생성자) 함수를 필요로 한다.
  • 생성자 함수를 통해 state를 초기화하기 때문에 함수형 컴포넌트에 비해 코드가 길어질 수 있다.
  • 라이프 사이클 기능을 사용할 수 있으며, 임의 메소드를 정의할 수 있다. 
  • render 함수가 꼭 있어야 하고, 그 안에서 보여주어야 할 JSX를 반환해야 한다. 

 

2. 함수형 컴포넌트

  • Hooks를 사용하여 생성자 함수를 통해 state를 초기화하지 않더라도 사용이 가능하다.
  • 선언하기 편하고 메모리 자원을 덜 사용한다.
  • 커스텀 훅을 생성하여 동작시킬 수 있다. 
  • state와 라이프사이클 사용이 불가능했지만 업데이트 이후 적용된 Hooks를 통해 해결되었다.

 

 

 

5. Props vs State

1. Props

컴포넌트 속성을 설정할 때 사용하는 요소.

props 값은 해당 컴포넌트를 불러와 사용하는 부모 컴포넌트에서 설정할 수 있다. 

 

 

2. State

컴포넌트 내부에서 바뀔 수 있는 값.

props는 컴포넌트가 사용되는 과정에서 부모 컴포넌트가 설정하는 값이며, 컴포넌트 자신은 해당 props를 읽을 수만 있다. 

오직 부모 컴포넌트에서만 변경 가능.

 

 

자식 → 부모로 값을 전달할 수 있을까?

부모 → 자식으로만 데이터를 줄 수 있다. 

하지만 함수를 이용하면 자식 → 부모로 값 전달이 가능하다. 

 

부모가 함수를 넣어 props로 자식에게 넘겨주면, 자식이 데이터를 파라미터로 넣어 호출하는 방식으로 동작한다. 

즉, 부모가 props로 함수를 넣어주면 자식이 그 함수를 이용해 값을 건네준다. 

 

 

 

 

6. Redux란?

Redux는 상태 관리 라이브러리 중 하나이며, Store라는 변수를 이용하여 전역 상태관리를 한다. 

전역으로 상태를 관리하기 때문에 props-state를 통해 부모 → 자식 컴포넌트로 내려주지 않아도 상태를 관리할 수 있다는 것이 장점이다.

 

 

 

 

1. Action

상태를 변경하기 위해서는 액션을 통해 변경해야 한다. 

액션은 객체 형식으로 표현되고, type을 필수로 가지고 있어야 한다. 

{
    type: 'ADD_MESSAGE',
    data: {
        id: 1
        message: "Action 함수"
    }
}

 

 

 

2. 액션 생성 함수

액션은 객체 형식으로 바로 만드는 것이 아니라 액션 생성 함수를 통해 만든다. 

function addMessage(data) {
  return {
    type: "ADD_MESSAGE",
    data,
  };
}

const addMessage = (data) => ({ type: "ADD_MESSAGE", data });

 

액션 생성은 액션 생성 함수를 호출함으로써 이루어진다. 

addMessage({ id: 1, message: "Action 생성" });

 

 

 

3. Reducer

리듀서는 액션이 발생했을 때 state를 변경시키기 위한 함수이다. 

리듀서는 현재 상태와 액션 객체를 받아 새로운 상태를 반환한다. 

function message(state, action) {
    switch(action.type) {
        case 'ADD_MESSAGE':
            return {
                ...state,
                action.data
            }
        default:
            return state
    }
}

 

역기서 액션에 전달한 부가적인 데이터인 `data`는 `action.data` 와 같은 형식으로 사용할 수 있다. 

 

 

4. Store

하나의 앱에는 하나의 스토어만 존재하고, 유일한 스토어를 사용하여 앱의 전체 상태를 관리한다. 

스토어 생성은 다음과 같다.

import { createStore } from "redux";
import message from "./messageReducer";

const store = createStore(message);

 

 

 

5. Dispatch

디스패치는 스토어의 내장 함수 중 하나다. 

상태를 업데이트할 수 있는 유일한 방법이며, 액션을 리듀서에게 전달해 상태를 변경시킨다. 

 

 

 

6. Redux의 장점

  • 복잡하고 어려운 상태 관리를 스토어 한 곳에서 관리하기 때문에 redux를 사용하지 않았을 때에 비해 훨씬 효율적이고 간단하다.
  • 단방향 데이터 흐름이라는 특징을 갖고 있어 흐름을 파악하기 쉽고, 디버깅하기 쉽다.
  • action을 dispatch로 전달할 때마다 기록이 남기 때문에 에러를 찾기 쉽다.
  • reducer는 같은 input을 입력하면 같은 output을 출력하는 함수이기 때문에 상태 변화를 예측하기 쉽다. 
  • 하나의 state를 여러 컴포넌트에서 사용할 수 있다.

 

 

 

 

7. 컴포넌트의 라이프 사이클 

라이프사이클 메소드의 종류는 총 9가지이다. 

 

 

will 이 붙은 메소드는 어떤 작업을 작동하기 전 실행되는 메소드이고, 

did 가 붙은 메소드는 어떤 작업을 작동한 후에 실행되는 메소드이다.

 

 

1. 마운트

  • DOM이 생성되고 웹 브라우저상에 나타는 것을 마운트라고 한다. 
  • componentDidMount : 컴포넌트가 웹 브라우저 상에 나타난 후 호출하는 메소드

 

2. 업데이트

컴포넌트는 다음과 같은 4가지 경우에 업데이트를 한다. 

  • props가 바뀔 때
  • state가 바뀔 떄 
  • 부모 컴포넌트가 리렌더링 될 때
  • this.forceUpdate로 강제 렌더링을 트리거할 때

 

  • componentDidUpdate : 컴포넌트의 업데이트 작업이 끝난 후 호출하는 메소드

 

3. 언마운트

  • 컴포넌트를 DOM에서 제거하는 과정이다. 
  • componentWillUnmount : 컴포넌트가 웹 브라우저 상에서 사라지기 전에 호출하는 메소드이다. 

 

 

 

 

8. Hooks의 종류

1. useState

첫번 째 원소는 상태 값, 두 번째 원소는 상태를 설정하는 함수이다. 

이 함수에 파라미터를 넣어 호출하면 전달받은 파라미터로 값이 바뀌고, 컴포넌트가 정상적으로 리렌더링 된다. 

 

import { useState } from "react";

const [value, setValue] = useState(0);

 

 

2. useEffect

리액트 컴포넌트가 렌더링 될 때마다 특정 작업을 수행하도록 설정할 수 있는 Hook 이다.

클래스형 컴포넌트에서 componentDidMount와 componentDidUpdate가 합쳐진 기능이라고도 할 수 있다. 

 

마운트만 실행시키고 싶을 경우 : 두 번째 파라미터로 비어있는 배열을 넣음

useEffect(() => {
  console.log("마운트될 때만 실행됩니다.");
}, []);

 

 

특정 값이 업데이트 될 때만 실행하고 싶을 경우 : 두 번째 파라미터로 전달되는 배열에 검사하고 싶은 값을 넣는다.

useEffect(() => {
  console.log(name);
}, [name]);

 

 

 

 

3. 뒷정리하기

useEffect는 렌더링된 직후마다 실행되며, 두 번째 파라미터 배열에 무엇을 넣는지에 따라 실행되는 조건이 달라진다. 

따라서, 컴포넌트가 언마운트 되기 전이나 업데이트 되기 직전에 어떤 작업을 수행하고 싶으면 useEffect에서 뒷정리 함수를 반환해야 한다. 

언마운트 시에 작업할 코드를 넣어주는 것이다. 

 

useEffect(() => {
    console.log("렌더링이 완료되었습니다!");
    console.log("none clean up: ", { name, nickname });
    return () => {
    	console.log("clean up!");
        console.log("clean up name: ", { name });
    };
  }

 

 

 

4. useReducer

useReducer는 useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트 해주고 싶을 때 사용한다. 

리듀서는 현재 상태와 업데이트시 필요한 정보를 담은 액션값을 전달받아 새로운 상태에 반환하는 함수이다. 

리듀서 함수에서 새로운 상태를 만들 때는 불변성을 지켜야 한다. 

 

 

 

5. useMemo

useMemo를 사용하면 함수 컴포넌트 내부에서 발생하는 연산을 최적화할 수 있다. 

 

리스트에 숫자를 추가하면, 추가된 숫자의 평균을 보여주는 함수 컴포넌트가 있다고 가정하자. 

그런데 숫자를 등록할 때 뿐만 아니라 input 내용이 수정될 때도 평균을 내는 함수가 호출되는 문제가 있다. 

input 내용이 바뀔 때는 평균을 다시 계산할 필요가 없으므로 불필요한 리소스 낭비다. 

 

useMemo를 사용하면 이런 작업을 최적화할 수 있다. 

렌더링하는 과정에서 특정 값이 바뀌었을 때만 연산을 실행하고, 원하는 값이 바뀌지 않았으면 이전에 연산했던 결과를 다시 사용하는 방식이다. 

 

import React, { useCallback, useMemo, useRef, useState } from "react";

const getAverage = (numbers) => {
  console.log("평균값 계산 중..");
  if (numbers.length === 0) return 0;
  const sum = numbers.reduce((a, b) => a + b);
  return sum / numbers.length;
};

const Average = () => {
  const [list, setList] = useState([]);
  const [number, setNumber] = useState("");
  const inputEl = useRef();

  const onChange = useCallback((e) => {
    setNumber(e.target.value);
  }, []); // 컴포넌트가 처음 렌더링될 때만 함수 생성

  const onInsert = useCallback(
    (e) => {
      const nextList = list.concat(parseInt(number));
      setList(nextList);
      setNumber("");
      inputEl.current.focus();
    },
    [number, list] // number 혹은 list가 바뀌었을 때만 함수 생성
  );

  const avg = useMemo(() => getAverage(list), [list]);

  return (
    <div>
      <input value={number} onChange={onChange} ref={inputEl} />
      <button onClick={onInsert}>등록</button>
      <ul>
        {list.map((value, index) => (
          <li key={index}>{value}</li>
        ))}
      </ul>
      <div>
        <b>평균값:</b> {avg}
      </div>
    </div>
  );
};

export default Average;

 

 

 

 

6. useCallback

useMemo와 비슷하다. 

useCallback을 사용하면 이미 만들어놨던 함수를 재사용할 수 있다. 

첫 번째 파라미터에는 생성하고 싶은 함수를, 두 번째 파라미터에는 배열을 넣으면 되는데, 이 배열에는 어떤 값이 바뀌었을 때 함수를 새로 생성해야하는지 명시해야 한다. 

비어있는 배열을 넣으면 컴포넌트가 처음 렌더링 될 때만 함수가 생성된다.

 

const onChange = useCallback((e) => {
  setNumber(e.target.value);
}, []); // 컴포넌트가 처음 렌더링될 때만 함수 생성


const onInsert = useCallback(
  (e) => {
    const nextList = list.concat(parseInt(number));
    setList(nextList);
    setNumber("");
  },
  [number, list] // number 혹은 list가 바뀌었을 때만 함수 생성
);


useMemo(() => {
  const fn = () => {
    console.log(‘hello world!‘);
  };
  return fn;
}, [])

 

 

7. useMemo vs useCallback

  • useMemo 함수는 memoization된 을 반환한다. 
  • useCallback 함수는 memoization된 함수를 반환한다.

 

8. useLayoutEffect

리액트 Hook 함수 바탕의 생명주기는 다음과 같다. 

 

 

브라우저가 렌더링될 때, `.js` 파일은 브라우저에서 자바스크립트 엔진으로 권한을 넘겨 `.js` 파일을 파싱하고 그린다. 

따라서 위 그림처럼 브라우저가 스크린에 페인팅 작업을 모두 마친 후에 `useEffect`가 실행된다. 

만약 `useState(0)`처럼 초기 useState 값이 비어있으면, 0을 출력했다가 `useEffect`를 통해 값을 채우는 구조이다. 

이러한 구조는 웹 페이지를 새로고침 했을 때, 아주 잠시동안 0이 먼저 출력되고, 그 다음에 값이 채워지는 불편함이 있다. 

 

 

`useLayoutEffect`는 이런 문제를 해결하기 위한 hook이다. 

`useLayoutEffect`는 브라우저가 화면에 DOM을 그리기 전에 이펙트를 수행하기 때문에 `useLayoutEffect`를 사용하지 않았을 때와 아래 코드를 실행하는 순서가 다르다.

 

import { useLayoutEffect, useState } from "react";

function App() {
  const [age, setAge] = useState(0);
  const [name, setName] = useState("");

  useLayoutEffect(() => {
    setAge(30);
    setName("철수");
  }, []);

  return (
    <>
      <div className="App">{`그의 이름은 ${name} 이며, 나이는 ${age}살 입니다.`}</div>
    </>
  );
}

export default App;

 

1. `useLayoutEffect` 내부의 `setAge`, `setName` 호출

2. `<div>그의 이름은 철수이며, 나이는 30살입니다.</div>`를 페인트

 

 

 

 

9.  리액트 성능 개선 방법

  • hook 함수 사용 (useMemo, useCallback 등)
  • 코드 스플리팅 (react.lazy(), Next.js 사용 등)

 

 

 

 

10.  SPA란?

SPA는 Single Page Application의 약어이다. 

한 개의 페이지로 이루어진 애플리케이션이라는 뜻인데, 전통적인 웹 페이지는 아래 사진처럼 여러 페이지로 구성되어있다. 

 

기존에는 사용자가 다른 페이지로 이동할 때마다 새로운 html을 받아오고,

페이지를 로딩할 때마다 서버에서 리소스를 전달받아 해석한 뒤 화면에 보여주었다. 

 

하지만 요즘에는 웹에서 제공되는 정보가 워낙 많아 새로운 화면을 보여줄 때마다 서버에서 모든 뷰를 준비하면 성능상의 문제가 생길 수 있다. 트래픽이 너무 많이 나오거나, 사용자가 몰려 서버에 높은 부하가 걸릴 수도 있다. 

 

그래서 React 같은 프레임워크를 사용하여 View 렌더링을 사용자의 브라우저가 담당하도록 하고, 애플리케이션을 브라우저에 불러와서 실행시킨 후에, 사용자와의 interaction이 발생하면 필요한 부분만 자바스크립트를 사용해서 업데이트한다. 만약 새로운 데이터가 필요하다면 서버 API를 호출하여 필요한 데이터만 새로 불러와 애플리케이션에서 사용할 수도 있다. 

 

 

 

SPA의 단점

앱의 규모가 커지면 자바스크립트 파일이 너무 커진다는 것이 단점이다. 페이지 로딩 시 사용자가 실제로 방문하지 않을 수도 있는 페이지의 스크립트도 불러오기 때문이다. 하지만 이는 코드 스플리팅을 통해 라우트별로 파일을 나눠서 개선할 수 있다. 

 

React Router 처럼 브라우저에서 자바스크립트를 사용하여 라우팅을 관리하는 것은 자바스크립트를 실행하지 않는 일반 크롤러에서는 페이지의 정보를 제대로 수집하지 못하는 단점도 있다. 따라서 검색엔진의 검색 결과에 페이지가 잘 나타나지 않을 수도 있다. 또한 자바스크립트가 실행될 때까지 페이지가 비어있기 때문에 자바스크립트 파일이 로딩되어 실행되는 짧은 시간 동안 흰 페이지가 나타날 수도 있다. 

이런 문제는 서버 사이드 렌더링으로 해결이 가능하다. 

 

 

 

 

 

 

11.  SSR(Server Side Rendering) 이란?

리액트는 대표적인 CSR(Client Side Rendering) 이다. 

이런 리액트를 SSR로 바꾸기 위해 Next.js 같은 라이브러리를 사용한다.

 

SSR을 사용하는 가장 큰 이유는 효율적인 SEO를 위해서이다. SEO는 구글, 네이버같은 검색 엔진들이 내 웹사이트에서 html 태그 안의 내용들을 분석해서 사용자가 입력한 정보를 바탕으로 적합한 정보를 찾을 수 있게 하는 기능이다. 리액트는 CSR 방식으로 작동되는데, 사용자가 내 도메인에 접속하면, 클라이언트 서버가 html과 js 등의 파일을 다운받아 보여주는 형식이다. 

CSR 상태에서는 사전에 html을 갖고 있지 않기 때문에 검색 엔진에 노출되는 빈도가 현저히 적거나, 아예 노출되지 않기도 한다. 그래서 SSR을 통해 서버가 사전에 html과 일부 js 파일들을 넘겨주게 되면, 검색 엔진에서 이를 캐치하여 사용자가 원하는 정보가 담긴 사이트를 보여줄 수 있다. 

 

 

 

 

 

 

12.  SEO(Search Engine Optimization) 란?

SEO란, 구글, 네이버와 같은 검색 엔진들이 서버에 등록된 웹 사이트를 하나씩 돌아다니면서 웹사이트의 html 문서를 분석하고, html에 사용된 태그를 바탕으로 사용자가 검색했을 때, 웹 사이트를 빠르게 검색할 수 있게 도와주는 것이다. 

하지만 CSR에서 사용되고 있는 html의 body는 비어있다가 사용자가 해당 도메인을 가진 페이지에 접근하면, 클라이언트 서버에서 js 및 html 태그를 불러오는 형식이기 때문에 사전에 html 정보를 가지고 있지 않다. 그래서 검색 엔진이 해당 도메인에 접근할 때 어려움이 있는 것이다. 

 

따라서 우리는 검색 엔진 최적화(SEO)를 하기 위해 SSR을 사용하여 사전에 필요한 html 문서를 검색 엔진이 찾을 수 있도록 제공하여 SEO를 향상시킬 수 있게 된다. SSR은 서버에서 필요한 데이터를 가져와 사전에 html 파일을 만들게 되고, 이렇게 만들어진 html 파일을 초기 세팅에 필요한 js와 함께 클라이언트 서버에 보내준다. 그러면 클라이언트 측에서는 서버에서 만들어준 문서를 받아와 사용자에게 보여줄 수 있게 된다. 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형
Comments