React 중요 개념 정리

은아·2024년 8월 18일

면접 대비 정리

목록 보기
3/5

리액트를 사용한 이유

리액트는 컴포넌트 기반의 UI 라이브러리로, 재사용 가능한 UI 요소를 만들기 쉽고 Virtual DOM을 통해 효율적인 렌더링을 제공합니다. 또한 큰 개발자 커뮤니티와 풍부한 생태계를 가지고 있어 다양한 라이브러리와 도구를 활용할 수 있습니다.

리액트와 다른 뷰(프레임워크들과의) 차이점

Vue나 Angular와 달리 리액트는 UI 라이브러리로, 전체 애플리케이션 구조를 강제하지 않습니다.
JSX를 사용하여 자바스크립트 내에서 직접 마크업을 작성할 수 있습니다.
단방향 데이터 흐름을 강조하여 예측 가능한 상태 관리를 촉진합니다.

리액트 상태관리에 대해 설명해보세요

리액트에서 상태관리는 컴포넌트의 state와 props를 통해 이루어집니다.
복잡한 애플리케이션의 경우 Redux, MobX, Zustand 같은 상태관리 라이브러리를 사용할 수 있습니다.

상태관리를 어떻게 했는지? 왜 Zustand 상태관리 라이브러리 그걸 사용했는지

이 프로젝트에서는 Zustand를 사용하여 상태 관리를 구현했습니다.

예를 들어, useUserDataStore.js 파일을 보시면:

export const useUserDataStore = create((set, get) => ({
  userData: {
    memberDTO: { ... },
    paymentDTO: { ... },
    contractDTO: { ... },
  },
  setUserData: data => set(state => ({ ... })),
  resetUserData: () => set(() => ({ ... })),
  // ...
}));

이런 식으로 Zustand store를 정의하고 있습니다. Zustand를 선택한 이유는 다음과 같습니다:

  • 간단한 API: Redux에 비해 보일러플레이트 코드가 훨씬 적습니다.
  • React Hooks와의 좋은 통합: useUserDataStore와 같은 훅을 쉽게 만들어 사용할 수 있습니다.
  • 성능: 필요한 컴포넌트만 리렌더링되도록 최적화되어 있습니다.
  • 작은 번들 사이즈: 프로젝트의 전체적인 크기를 줄이는 데 도움이 됩니다.

특히 이 프로젝트에서는 사용자 데이터, 인보이스 정보, 상태 등 여러 글로벌 상태를 관리해야 했기 때문에 Zustand의 간단하고 효율적인 API가 큰 도움이 되었습니다.

리액트 렌더링 (웹브라우저 DOM 작동방식)

리액트는 Virtual DOM을 사용하여 실제 DOM과 비교하고 변경된 부분만 업데이트합니다. 이는 브라우저의 렌더링 과정(DOM 트리 구성, 렌더 트리 구성, 레이아웃, 페인팅)을 최소화하여 성능을 향상시킵니다.

useMemo와 useCallback은 각각 값과 함수를 메모이제이션하여 불필요한 재계산을 방지합니다. 예를 들어, 복잡한 계산이 필요한 값이나 자주 변경되지 않는 콜백 함수에 사용할 수 있습니다.

이 프로젝트에서는 여러 가지 방법으로 렌더링 최적화를 시도했습니다:

1. useMemo와 useCallback 사용

예를 들어, Statistics.jsx 파일에서:

const handleSelectContract = useCallback(
  async contract => {
    // ...
  },
  [onAlert]
);

이렇게 useCallback을 사용하여 함수를 메모이제이션하고 있습니다. 이는 불필요한 리렌더링을 방지하는 데 도움이 됩니다.

2. 컴포넌트 분리: 큰 컴포넌트를 작은 단위로 분리하여 필요한 부분만 리렌더링되도록 했습니다.

예를 들어, StatisticList.jsx와 Statistics.jsx를 분리했습니다.

3. React.memo 사용:

예를 들어, PaymentCard.jsx와 PaymentCMS.jsx 컴포넌트에서:

export default React.memo(PaymentCard);

이렇게 React.memo를 사용하여 props가 변경되지 않으면 리렌더링되지 않도록 최적화했습니다.

4. 조건부 렌더링: 불필요한 렌더링을 방지하기 위해 조건부 렌더링을 적극 활용했습니다.

예를 들어, SimpConsentPage.jsx에서:

{status !== 5 && status !== 6 && (
  <div className='fixed bottom-0 left-0 w-full'>
    {/* ... */}
  </div>
)}

이런 식으로 특정 상태에서만 컴포넌트를 렌더링하도록 했습니다.

이 프로젝트에서 컴포넌트 구조를 어떻게 설계하셨나요?

이 프로젝트에서는 기능과 책임에 따라 컴포넌트를 구조화했습니다:

  • 페이지 컴포넌트: InvoicePage.jsx, PaymentCardPage.jsx 등 각 라우트에 해당하는 페이지 컴포넌트를 만들었습니다.
  • 기능별 컴포넌트: CardInfo.jsx, ContractInfo.jsx 등 특정 기능을 담당하는 컴포넌트를 만들었습니다.
  • 공통 컴포넌트: Input.jsx, SelectField.jsx 등 여러 곳에서 재사용되는 UI 컴포넌트를 만들었습니다.
  • 레이아웃 컴포넌트: MobileBaseLayout.jsx와 같은 레이아웃 컴포넌트를 만들어 일관된 UI를 제공했습니다.

리액트 Virtual-Dom

Virtual DOM은 메모리상에 존재하는 가상의 DOM 트리입니다. 상태 변경 시 Virtual DOM을 먼저 업데이트하고, 실제 DOM과 비교하여 차이가 있는 부분만 실제 DOM에 적용합니다.

리렌더링되는 조건은 어떤건지

state가 변경될 때
props가 변경될 때
부모 컴포넌트가 리렌더링될 때
context value가 변경될 때

컴포넌트 프롭스와 스테이트 차이에 관해 설명해주세요

props는 부모 컴포넌트로부터 전달받은 데이터로, 읽기 전용입니다.
state는 컴포넌트 내부에서 관리되는 데이터로, 변경 가능합니다.

display:none; 이면 브라우저에서 어떻게 처리하는지, 어느 정도까지 새로 그리는 지

display: none이 적용된 요소는 렌더 트리에서 제외되어 레이아웃과 페인팅 과정에서 무시됩니다. 요소와 그 자식 요소들은 여전히 DOM에 존재하지만 화면에 표시되지 않고 레이아웃에 영향을 주지 않습니다.

이 프로젝트에서 비동기 처리는 어떻게 구현하셨나요?

이 프로젝트에서는 주로 axios를 사용하여 비동기 API 호출을 처리했습니다.

예를 들어, auth.js 파일을 보시면:

export const postLogin = async info => {
  const res = await publicAxios.post('/v1/vendor/auth/login', info);
  return res;
};

이런 식으로 axios를 사용하여 비동기 요청을 처리하고 있습니다.

또한, React의 useEffect 훅을 사용하여 컴포넌트 마운트 시 데이터를 가져오는 등의 비동기 작업을 처리했습니다. 예를 들어, SimpConsentPage.jsx에서:

useEffect(() => {
  if (contractId) {
    axiosContractInfo();
  }
}, []);

이렇게 컴포넌트가 마운트될 때 계약 정보를 가져오는 비동기 작업을 수행하고 있습니다.

에러 처리의 경우, try-catch 구문을 사용하여 예외를 처리했습니다. 예를 들어:

try {
  const res = await sendSimpleConsentSignImage(vendorId, data);
  console.log('기존 계약 서명이미지 업데이트 성공');
} catch (err) {
  console.error('axiosSendSimpleConsentSignImage => ', err.response);
}

이런 방식으로 비동기 작업의 실패를 처리하고 있습니다.

이 프로젝트에서 사용한 라우팅 전략에 대해 설명해주세요.

이 프로젝트에서는 React Router를 사용하여 라우팅을 구현했습니다.

App.jsx 파일을 보시면:

import { RouterProvider } from 'react-router-dom';
import root from '@/routes/root';

const App = () => {
  return (
    <div className='flex h-dvh w-vw justify-center'>
      <AlertDialog>
        <ConfirmDialog>
          <RouterProvider router={root} />
        </ConfirmDialog>
      </AlertDialog>
    </div>
  );
};

이렇게 RouterProvider를 사용하여 라우팅을 설정하고 있습니다.

라우트 구조는 중첩 라우팅을 활용하여 구성했습니다.

예를 들어, MemIndex.jsx에서:

import { Outlet } from 'react-router-dom';
import BaseLayout from '@/layouts/MobileBaseLayout';

const MemIndex = () => {
  return (
    <BaseLayout>
      <Outlet />
    </BaseLayout>
  );
};

이렇게 Outlet을 사용하여 중첩 라우트의 자식 컴포넌트를 렌더링하고 있습니다.

또한, 동적 라우팅을 사용하여 URL 파라미터를 활용했습니다. 예를 들어, InvoicePage.jsx에서:

const { invoiceId } = useParams();

이렇게 useParams 훅을 사용하여 동적 라우트 파라미터를 가져오고 있습니다.

이러한 라우팅 전략을 통해 SPA(Single Page Application)의 장점을 살리면서도 복잡한 페이지 구조를 효과적으로 관리할 수 있었습니다.

이 프로젝트에서 사용한 스타일링 방식에 대해 설명해주세요.

이 프로젝트에서는 주로 Tailwind CSS를 사용하여 스타일링을 구현했습니다. Tailwind CSS는 유틸리티 우선 CSS 프레임워크로, 미리 정의된 클래스를 조합하여 스타일을 적용합니다.

예를 들어, MoveButton.jsx 컴포넌트를 보면:

<div
  className={`flex justify-center items-center rounded-lg border bg-white border-mint mr-1
            ${color === 'mint' ? 'hover:border-mint_hover' : ''} `}>
  <button
    className={`flex items-center rounded-lg py-2 px-4 text-sm border border-white transition-all duration-200
            ${color === 'mint' ? 'bg-mint text-white font-700 hover:bg-mint_hover' : 'font-800 text-mint hover:bg-mint_hover_light'} `}
    onClick={onClick}>
    <img className='mr-2 w-5 h-5' src={imgSrc} alt='button icon' />
    <p>{buttonText}</p>
  </button>
</div>

이런 식으로 Tailwind CSS 클래스를 사용하여 스타일을 적용하고 있습니다.

또한, Tailwind CSS의 설정을 커스터마이즈하여 프로젝트에 맞는 디자인 시스템을 구축했습니다.
tailwind.config.js 파일을 보면:

theme: {
  extend: {
    colors: {
      mint: '#4FD1C5',
      mint_hover: '#51B1A8',
      // ...
    },
    // ...
  },
},

이렇게 프로젝트에 필요한 커스텀 색상, 폰트 크기, 간격 등을 정의하여 사용했습니다.

이러한 접근 방식을 통해 일관된 디자인을 유지하면서도 빠르고 유연한 스타일링이 가능했습니다. 또한, 클래스 이름을 통해 스타일을 직관적으로 이해할 수 있어 팀 내 협업에도 도움이 되었습니다.

profile
Junior Developer 개발 기술 정리 블로그

0개의 댓글