면접을 위한 React 질문들

김인태·2023년 7월 13일
4

테오의 프론트엔드 방 안에서 어떤분이 React에 관한 면접 질문을 한다면 어떤 질문을 하고 싶으신가요..? 라고 물어봤더니 친절하게 답해주셨다..
그 답들에 대해서 어떤 것은 생각나고 어떤 것은 생각이 나지 않으니
근본적인 부분이 좀 더 부족하고, 공부가 많이 필요하다고 느꼈다.
아무래도 아직 이직 준비를 하고 있으니 좀 더 공부해야겠다!

1. React 18에 많은 변화가 있는데 어떤 것들이 추가 되었고, 내용 혹은 방향성에 대해서 대답해보자.

  • 동시성을 대입했다 .
    • 동시성이란 순서에 상관없이 동시에 수행될 수 있다는 것.
    • 비동기와 비슷한 것 같은데 ? ❌
    • 비동기는 결과를 기다리지 않고 바로 다음작업을 실행할 수 있게 하는 방식.(메인스레드에서 작업을 다른 스레드로 분산처리시키고 그 작업이 끝나길 기다리지않고 다음작업을 생성.
    • 동시는 싱글코어에서 멀티스레드를 동작시키기 위한 방식으로 멀티 태스킹을 위해 여러 개의 스레드가 번갈아가면서 실행되는 방식
  • concurrent mode
    • 동시성은 여러 작업을 작은 단위로 나눈 뒤, 그들 간의 우선순위를 정하고 그에 따라 작업을 번갈아 수행하는 방법이다. 서로 다른 작업들이 실제로 동시에 수행되는 것은 아니지만, 작업 간의 전환이 매우 빠르게 이루어지면서 동시에 수행되는 것처럼 보이는 것이다
    • 특정 state가 변경되었을 때 현재 UI를 유지하고, 해당 변경에 따른 UI 업데이트를 동시에 준비한다. 준비 중인 UI의 렌더링 단계가 특정 조건에 부합하면 실제 DOM에 반영한다.
    • render 함수에서 → createRoot로 함수가 바뀜
  • Automatic Batching

    • 배칭이란? 배칭은 우리말로 "일괄 처리"정도로 해석할 수 있을 것 같습니다. 개별적으로 어떤 요청이 있을 때마다 실시간으로 통신하는 것이 아닌 한꺼번에 일괄적으로 처리하는 것입니다. React적으로 의미를 좁히면, 여러 개의 state 업데이트를 하나의 리렌더링으로 묶는 것을 의미합니다.

    • 자동 배칭 기능을 사용하기 위해 무언가를 할 필요가 없습니다. React가 알아서 하기 때문입니다. React 18의 createRoot를 통해, 모든 업데이트들은 어디서 왔는가와 무관하게 자동으로 배칭됩니다. ( React 18 이전에는 React 이벤트 핸들러 내부에서 발생하는 업데이트만 배칭을 했습니다.)

  • transitions

    • 사용자가 키보드로 무언가를 입력할 때 UI에는 두 가지 변화가 일어납니다. 첫번째는 입력필드에 무언갈 입력할 때마다 커서가 깜빡이며 무언가가 입력되고 있다는 것을 알려주는 시각적 피드백입니다. 두 번째는 제가 입력한 내용이 검색된 입력창 하단의 백그라운드입니다. 방금 입력한 글자 옆에 커서가 깜빡이는 UI 변화는 "긴급한 것"입니다. 입력이 감지될 때마다 UI가 즉시 업데이트 되어야 하죠. (위 사진에서 T를 입력할 때가 되서야 C옆에 커서가 깜빡인다고 상상해보세요!) 이에 비하면 "검색"은 그렇게 긴급하지 않습니다. 이처럼 비교적 긴급하지 않은 UI 업데이트를 transition이라고 합니다. 우선순위가 낮은 업데이트를 transition으로 표시해주면, React는 렌더링을 더 쉽게 최적화 하고, 업데이트의 우선 순위를 잘 매길 수 있게 됩니다.
  • suspense
    • 어떤 컴포넌트가 읽어야 하는 데이터가 아직 준비가 되지 않았다고 리액트에게 알려주는 새로운 매커니즘 이를 이용해서 컴포넌트 렌더링을 어떤 작업이 끝날 때까지 잠시 중단시키고, 다른 컴포넌트를 먼저 렌더링할 수 있습니다.

  • 성공하는 경우와 실패하는 경우가 fetchAccounts 안에서 섞여서 처리되고 있기 떄문에 함수가 하는 진짜 역할을 보기 어려움
  • 코드를 작성할 때 매번 에러 유무를 확인해야함.
  • suspense의 목표
    • 성공한 경우에만 집중할 수 있는

    • 로딩 상태와 에러 상태가 분리된

    • 동기처럼 사용할 수 있는

    • 컴포넌트를 '쓰는 쪽'에서 로딩 처리와 에러 처리를 합니다. 컴포넌트를 사용할 때 그 컴포넌트를 위 코드처럼 Suspense로 감싸주면, 컴포넌트의 렌더링을 특정 작업 이후로 미루고, 그 작업이 끝날 때까지는 fallback 속성으로 넘긴 컴포넌트를 대신 보여줄 수 있습니다.

2. Virtual DOM이란 무엇이고 왜 이 기술을 사용했는지, 어떤 역할을 하는지 설명해주세요

  • Virtual DOM?
    Virtual DOM이란 말 그대로 가상의 DOM 이란 뜻이고, DOM을 추상화한javascript 객체입니다.

  • 사용하는 이유?
    동적인 웹 애플리케이션을 만들기 위해서
    -> 기존의 정적인 DOM 으로 만들게 되면 브라우저가 많은 연산을 하게되고 전체적인 프로세스가 떨어진다.

  • 어떤 역할?
    React의 state, props 등이 변경될 때 마다 이전 Virtual DOM과 새로운 Virtual DOM을 비교합니다. 변경이 발생할 때 전체 페이지를 렌더링 하는 것이 아니라, 변경한 부분만 렌더링하는 역할을 수행합니다.

3.본인이 생각하는 좋은 컴포넌트란 어떤 것인가요? 최근에 작성한 컴포넌트 중에서 좋은 컴포넌트라고 생각될 만한게 있나요? 어떤 이유로 그렇게 생각하시나요?

  • 이건 답이 있는 것이 아니라 개인적인 생각을 말하면 될 것 같다.
    내가 생각하는 좋은 컴포넌트는
    재사용이 가능(코드 중복을 줄이면서 유지보수) 하면서
    하나의 책임만을 수행하고(책임 영역이 명확해져 로직이 복잡해지지 않음), 읽기 쉽고 유지 보수 하기 쉬우며(다른 개발자와 협업할 때 용이하게)
    성능 최적화가 잘 되어있는(불필요한 렌더링이 적은) 컴포넌트... 정도라고 생각한다.

4. setState는 동기일까요? 비동기일까요? setState가 적용되는 동작방식에 대해서 아는대로 한번 설명해주세요.

import React, { useState } from "react";

function App() {
  const [state, setState] = useState(1);

  const onClick = () => {
    setState(state + 1);
    console.log(state);
  };

  return (
    <div className="App">
      <button onClick={onClick}>+1</button>
      <p>현재 값 {state}</p>
    </div>
  );
}

export default App;

현재 setState는 이벤트 핸들러 내에서 비동기적입니다. state값은 반영이 되지만, console.log()에 찍힌 값은 이전 상태 값을 출력합니다. React가 브라우저 이벤트가 끝날 시점에 state를 일괄적으로 업데이트 하기 때문에

import React, { useState } from "react";

function App() {
  const [state, setState] = useState(1);

  const onClick = () => {
    setState(state + 1);
    setState(state + 1);
    setState(state + 1);
  };

  return (
    <div className="App">
      <button onClick={onClick}>+1</button>
      <p>현재 값 {state}</p>
    </div>
  );
}

export default App;

setState를 3 번 호출해서 매번 + 3 되길 원하지만 오직 + 1만 증가하는 것을 볼 수 있습니다. 그 이유는 React가 batch update를 하기 때문입니다.
업로드중..
setState 함수가 호출되면 리액트는 전달받은 state를 바로 바꾸는 것이 아니라 이전의 리액트 엘리먼트 트리와 전달받은 state 엘리먼트 트리를 비교하는 작업을 거치고, 최종적으로 변경된 부분만 DOM에 적용합니다. batch update는 16ms 단위로 진행하며 그 동안 변경된 상태 값들을 모아 단 한 번 리렌더링을 진행합니다.
setState() 함수는 인자로 새로운 state 객체를 인자로 받을 수 있지만, 이전 state 기준으로 값을 계산해야 한다면 updater 함수를 전달하면 됩니다! setState 호출은 일괄적으로 처리되기 때문에 여러 업데이트 사항이 충돌 없이 차례대로 반영되도록 합니다.

import React, { useState } from "react";

function App() {
  const [state, setState] = useState(1);

  const onClick = () => {
    setState((prevState) => prevState + 1);
    setState((prevState) => prevState + 1);
    setState((prevState) => prevState + 1);
  };

  return (
    <div className="App">
      <button onClick={onClick}>+1</button>
      <p>현재 값 {state}</p>
    </div>
  );
}

export default App;

이것처럼요 !
결론적으로는 State가 비동기적으로 처리되게 만든 이유는

  • 일정시간 동안 변화하는 상태를 한 번에 모아 렌더링 하기 위해서
  • 모아서 렌더링 하면 ?! 불필요한 렌더링을 줄여 빠른속도로 동작하기 위함!
  • 왜 리렌더링 대신에 즉시 state를 업데이트 하지 않느냐? -> props와 state의 일괄성을 해칠 수 있고, 디버깅하기 힘든 이슈가 생길 수 있음

5.state와 props의 차이점

  • state는 ?
    현재 컴포넌트에서 생성되고 변할 수 있는 데이터 입니다. state는 오직 생성된 컴포넌트에서만 변경이 가능합니다. state를 업데이트 하려면 setState라는 메소드를 사용해야합니다.
  • props는 외부 컴포넌트 등에서 상속받는 데이터이며 데이터를 변경할 수 는 없습니다.

6. useCallback과 useMemo를 쓰는 경우에는 어떤 경우일까요? 사용하는 이유와 사용시 주의해야 점 등을 설명해주세요.

  • 사용하는 이유
    메모이제이션(Memoization)을 사용해 성능을 최적화 하기 위함,
    -> 기존에 수행한 연산의 결과값을 어딘가에 저장해두고 동일한 입력이 들어오면 재활용하는 프로그래밍 기법
  1. 자식 컴포넌트에서 useEffect가 반복적으로 트리거 되거나, 무한 루프에 빠질 위험이 있을 때
  2. 자식 컴포넌트에 함수를 props로 넘길 때, 불필요한 렌더링이 일어난다고 판단될 때
  3. 사용자의 입력값이 map 혹은 filter 등을 사용하여 이후 렌더링에서도 동일한 참조를 사용할 가능성이 높을 경우
    등등..
  • 둘의 차이
    useCallback은 메모이제이션 된 함수를 반환
    useMemo는 메모이제이션 된 을 반환
  • 주의할 점
    1. 연산이 복잡하지 않은경우에 사용했을시에 메모리가 낭비될 수 있음.
    2. 함수 내부에서 setState나 dispatch 함수등을 호출하는 경우는 사용하지 않는게 좋다 => 이미 리액트 자체에서 useState와 useDispatch에 대한 성능최적화를 했기 때문에,
    3. 의도적으로 매번 새로운 함수나 값을 계산해야 할경우 사용할 필요가 없음.

결론적으로는 동일한 입력이 있거나 복잡한 계산이 반복될 때 사용되며 무작정 최적화를 위한다며 사용하면 메모리 낭비와 복잡한 코드 때문에 오히려 손해를 볼 수 있을 것이다!

7. 서버 컴포넌트가 무엇일까요? 서버 컴포넌트는 어떤 역할을 하고 왜 생겨났을까요? 서버 컴포넌트에 대해서 아는대로 한번 설명해주세요.

next.js 13버전에서 부터는 app 라우팅으로 세팅되어 있고, app 라우팅에서는 기본적으로 모든 컴포넌트가 서버 컴포넌트입니다. 그렇다면 서버컴포넌트란 무엇이냐? 그 말대로 서버에서 렌더되고 요청되는 컴포넌트입니다.

  • onclick과 같은 상호작용을 포함하지 않고
  • React State가 필요없으며
  • React Hooks를 사용할 수 없고, fallback과 함수들은 props로써 전달 될 수 없습니다.

데이터베이스나 , 파일 시스템에 기반한 작업들을 위해서 만들어 졌습니다.
서버 컴포넌트들은 클라이언트 사이드 번들을 포함하지 않기 때문에 적은양의 자바스크립트 코드를 다운로드 하도록해서 빠른 렌더링을 꾀할 수 있습니다. "use client"를 사용한 컴포넌트만 웹팩 번들에 포함됩니다.

8. 본인이 직접 커스텀 훅을 만들어서 사용해본 경험이 있나요? 어떤 이유로 어떤 형태로 만들었는지 한번 설명해주세요.

저의 경우는 useInput 이라는 훅을 만들어 보았습니다.
왜 만들었냐 ? -> 컴포넌트간에 input들이 사용되는 경우가 많고, 같은 함수들이 중복되기 때문에

import { useState } from "react";
import { supabase } from "../lib/supabase";
import { ManagerProps, CategoryProps } from "../types/InputValueProps";
import { redirect } from "next/navigation";
import { useRouter } from "next/router";
import { type } from "os";

export async function signInWithEmail(managerIntfo: ManagerProps) {
  try {
    const { data, error } = await supabase.auth.signInWithPassword({
      email: managerIntfo.email,
      password: managerIntfo.password,
    });
    console.log("redirect 해볼게 얍");
  } catch (err) {
    alert(err);
  }
}

export const useInput = (state: ManagerProps | CategoryProps) => {
  const [values, setValues] = useState(state);

  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    setValues({ ...values, [name]: value });
    // 조건부를 넣어볼 것.
    // if(typeof state === "string"){
    //   setValues(e.target.value);
    // }else if("email" in state && "password" in state){
    //   setValues((prevValues) => ({
    //     ...prevValues,
    //     [name]: value,
    //   }));
    // }

    console.log(values);
  };

  const onSubmit = (
    e: React.FormEvent<HTMLFormElement>,
    url: string,
    state: ManagerProps
  ) => {
    e.preventDefault();
    if (url.includes("login")) {
      signInWithEmail(state);
    }
  };
  return { values, onChange, onSubmit };
};

로그인할 때와 에디터에 있는 내용을 등록할 때를 분기처리 하기 위하여 고민중이고, state와 onChange , onSubmit을 export해주어 사용할 수 있게 하였습니다.

9. React가 렌더링을 한다고 했는데, 어떤 경우에 렌더링이 발생하는지 설명해주세요.

  1. 초기 렌더링: 컴포넌트가 처음으로 마운트될 때 초기 렌더링이 발생합니다. 이때 컴포넌트의 render() 메서드가 호출되어 가상 DOM을 생성하고, 이를 실제 DOM에 반영합니다.

  2. 상태(State) 변경: 컴포넌트의 상태가 setState() 메서드를 통해 업데이트되면, React는 새로운 상태로 다시 렌더링합니다. 이때 render() 메서드가 다시 호출되며, 변경된 부분만 가상 DOM에 반영합니다.

  3. 속성(Props) 변경: 컴포넌트의 속성이 부모 컴포넌트로부터 변경되었을 때도 렌더링이 발생합니다. 이때 render() 메서드가 호출되어 새로운 속성값으로 가상 DOM을 업데이트하고, 변경된 부분만 실제 DOM에 반영합니다.

  4. 강제 렌더링: forceUpdate() 메서드를 호출하여 컴포넌트를 강제로 렌더링할 수 있습니다. 일반적으로 상태나 속성 변경에 의해 자동으로 렌더링이 처리되지만, 특별한 경우에 강제로 렌더링해야 할 때 사용됩니다.

  5. 라우팅 변화: React Router 또는 다른 라우팅 라이브러리를 사용할 때, 라우팅 경로가 변경되면 해당하는 컴포넌트가 다시 렌더링됩니다.

  6. 부모 컴포넌트의 렌더링: 부모 컴포넌트가 렌더링되면 자식 컴포넌트도 함께 렌더링됩니다. 이때 자식 컴포넌트의 render() 메서드가 호출되어 새로운 가상 DOM을 생성하고, 변경된 부분만 실제 DOM에 반영합니다.

[출처]
리액트 공식문서 한글번역
https://react-ko.dev/
React 18버전 내용
https://velog.io/@perfumellim/쉽게-떠먹여주는-React-18-업데이트
state 동기, 비동기
https://taenami.tistory.com/49
props와 state의 차이점
https://ljh86029926.gitbook.io/coding-apple-react/1/props-and-state
useMemo, useCallback
https://hayeondev.gatsbyjs.io/230202-usememo-and-usecallback/
서버컴포넌트
https://velog.io/@brgndy/React-Server-vs-Client-Component-in-Next.js-13-%ED%95%B4%EC%84%9D

profile
새로운 걸 배우는 것을 좋아하는 프론트엔드 개발자입니다!

0개의 댓글