[기술면접] React 부분 6

Boyoung Lee·2025년 1월 14일

기술면접

목록 보기
39/49
post-thumbnail

<목차>

  1. Virtual DOM
  2. axios의 instance와 interceptor
  3. React-query와 Custom Hooks




16. Virtual DOM

  • 성능 최적화를 위해 실제 DOM 대신 사용하는 개념적인 가상 DOM
  • UI 업데이트의 효율성을 높임
  • 애플리세이션의 성능 개선

Virtual DOM의 동작 방식

  1. Virtual DOM 생성
  • React 컴포넌트가 렌더링되면 React는 UI를 구조를 나타내는 Virtual DOM 트리를 생성
  • Virtual DOM은 실제 DOM의 경량화된 사본으로 메모리에 존재
  • 일반 자바스크립트 객체로 표현
  1. 상태 또는 속성 변경
  • 컴포넌트의 상태(state)나 속성(props)이 변경되면 React는 새 Virtual DOM 트리 생성
  • 새 트리는 변경된 UI 반영
  1. Virtual DOM 비교 (Diffing)
  • Diffing이라는 알고리즘을 사용
  • 이전 Virtual DOM 트리와 새 트리를 비교(diff)
  • 이 과정에서 변경된 부분(차이점)을 찾음
  • Diffing 알고리즘: 최소한의 연산으로 변경점을 찾도록 설계
  1. 패치 (Patching)
  • 변경된 부분만 실제 DOM에 적용
  • React는 Reconciliation이라는 과정을 통해 실제 DOM을 업데이트 => 변경이 필요한 노드만 다시 렌더링
  • 이를 통해 전체 DOM 트리를 다시 렌더링하는 비용을 줄이고 성능 최적화
  1. 렌더링 완료
  • Virtual DOM이 실제 DOM과 동기화된 후 React는 브라우저에 최종 업데이트 반영
  • 최적화된 업데이트가 적용되어 UI가 더 빠르게 렌더링

Virtual DOM의 장점

  • 효율성

    • 변경된 부분만 DOM에 반영하기 때문에 성능 향상
    • 실제 DOM조작은 시간이 오래걸림
    • 바뀐 부분만 찾아내서 꼭 필요한 부분만 실제 DOM에 적용하기 때문에 빠름

    => 책의 한 문장을 고치기 위해 책 전체를 다시 쓰는 대신, 바뀐 문장만 고치는 것

  • 프로그래머 편의성

    • React는 Virtual DOM을 통해 복잡한 UI 업데이트 과정을 간단하게 처리
    • 복잡한 작업은 React가 알아서 처리
    • 프로그래머가 직접 어떤 부분이 바뀌었는지 찾지 않고 React가 바뀐 부분을 자동으로 찾아내 줌
  • Cross-Browser Compatibility : 브라우저 간 차이를 추상화하여 일관된 동작을 제공

    • 어디서든 일관된 동작
    • 브라우저마다 HTML을 다루는 방식이 조금씩 다름
    • Virtual DOM은 브라우저와 상관없이 같은 방식으로 동작하기 때문에 코드가 모든 브라우저에서 동일하게 작용
    • Chrome에서도 Safari에서도 문제없다.

Q. 리액트에서 사용되는 Virtual DOM 의 동작방식을 설명할 수 있다.






17. Axios의 instance와 interceptor

  • 요청과 응답을 효과적으로 관리하기 위해 제공되는 기능
  • HTTP 요청을 간편하게 관리하고 공통 작업을 줄이는데 매우 유용

Axios Instance

  • Axios의 설정을 미리 정의한 객체
  • instance를 사용하여 반복적인 설정 없이 Axios 요청 재사용 가능

사용 방법

import axios from 'axios';

// Axios instance 생성
const apiClient = axios.create({
  baseURL: 'https://api.example.com', // 기본 URL
  timeout: 5000,                     // 요청 제한 시간
  headers: {                         // 기본 헤더
    'Content-Type': 'application/json',
    Authorization: 'Bearer YOUR_TOKEN'
  }
});

// 요청 보내기
apiClient.get('/endpoint')
  .then(response => console.log(response.data))
  .catch(error => console.error(error));

Instance 장점

  • 코드 중복 감서 : 매번 baseURL 이나 headers를 설정하지 않아도 됨
  • 유지보수 용이 : API 요청 관련 설정을 한 곳에서 관리 가능
  • 유연성 : 여러 API 서버가 있다면 각각에 대해 별도의 instance를 생성 가능

Axios Interceptor

  • Axios 요청 또는 응답이 처리되기 전후에 실행되는 함수를 정의할 수 있는 기능
  • 요청을 수정하거나, 응답 데이터를 처리, 에러를 공통적으로 처리 가능

사용 방법

요청(Request) Interceptor

  • 요청을 보내기 전에 헤더 추가, 로딩 상태 관리, 토큰 주입 등을 처리
// 요청 인터셉터 설정
apiClient.interceptors.request.use(
  config => {
    // 요청 전에 작업 수행 (ex: 토큰 추가)
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    console.log('Request sent:', config);
    return config; // 반드시 config를 반환해야 함
  },
  error => {
    // 요청 에러 처리
    return Promise.reject(error);
  }
);

응답(Response) Interceptor

  • 응답 데이터를 처리하거나 에러를 공통적으로 처리
// 응답 인터셉터 설정
apiClient.interceptors.response.use(
  response => {
    // 응답 데이터 가공
    console.log('Response received:', response);
    return response.data; // 필요한 데이터만 반환
  },
  error => {
    // 에러 처리
    if (error.response && error.response.status === 401) {
      console.error('Unauthorized! Redirecting to login...');
      // 로그인 페이지로 이동 등의 작업 수행
    }
    return Promise.reject(error);
  }
);

Interceptor 장점

  1. 반복 작업 제거 : 모든 요청/응답에 공통 로직 적용 가능
  2. 토큰 관리 : 인증 토큰을 요청 헤더에 자동으로 추가
  3. 글로벌 에러 처리 : 응답 에러를 한 곳에서 통합 처리
  4. 데이터 전처리 : 응답 데이터를 가공하거나 필요한 부분만 추출

instance와 interceptor 전체 사용 예시

// axios.js (Axios 설정 파일)
import axios from 'axios';

const apiClient = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 5000
});

// Request Interceptor
apiClient.interceptors.request.use(
  config => {
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  error => Promise.reject(error)
);

// Response Interceptor
apiClient.interceptors.response.use(
  response => response.data,
  error => {
    if (error.response?.status === 401) {
      alert('Session expired! Redirecting to login...');
    }
    return Promise.reject(error);
  }
);

export default apiClient;

// 다른 파일에서 사용
import apiClient from './axios';

apiClient.get('/users')
  .then(data => console.log(data))
  .catch(err => console.error(err));

Q. axios의 instance와 interceptor를 이해하고 활용하여 좀 더 깊은 수준의 API call을 할 수 있습니다.






18. React-query와 Custom Hooks

React Query란?

  • React 애플리케이션에서 서버 상태(Server State)를 관리
  • 데이터 가져오기(fetch), 캐싱(caching), 동기화(synchronization), 업데이트 등을 쉽게 처리하도록 도와주는 라이브러리
  • 서버 데이터 관리와 관련된 반복적인 작업을 취소화
  • 더울 깔끔하고 효율적인 코드 작성을 도움

React Query의 장점

  1. 데이터 캐싱
  • 서버에서 가져온 데이터를 자동으로 캐싱
  • 같은 데이터 요청이 있을 때 다시 서버에 요청하지 않고, 캐시된 데이터를 반환 => 성능 최적화
  1. 자동 데이터 업데이트(Revalidation)
  • 서버 상태가 변경되었을 가능성이 있으면 데이터를 자동으로 갱신
  1. 에러 및 로딩 상태 관리
  • isLoading,isError, isFetching등 상태를 자동 관리
  • 복잡한 로직 없이도 로딩 스피너나 에러 메시지를 쉽게 처리
  1. 자동 재시도
  • 네트워크 오류가 발생하면 요청을 자동으로 재시도하여 사용자 경험 개선
  1. 백그라운드 데이터 동기화
  • 이미 화면에 표시된 데이터도 새롭게 업데이트된 데이터로 동기화
  1. 개발 경험 향상
  • Devtools를 통해 React Query가 관리하는 데이터를 시각적으로 확인, 디버깅 가능
  1. 유연한 데이터 관리
  • Pagination(페이지네이션), Infinite Scrolling(무한 스크롤링), 의존적 쿼리(Fetch Chain) 등 다양한 시나리오에 적합

React Query의 주요 훅

  1. useQuery
  • 데이터를 가져오기 위한 훅
  1. useMutation
  • 데이터를 변경(Create, Update, Delete)하기 위한 훅
  1. useQueryClient
  • 캐시 데이터를 관리하거나 직접 조작하기 위한 훅
  1. useInfiniteQuery
  • 무한 스크롤 또는 페이지네이션을 처리하기 위한 훅

React Query 사용법

1. 설치

npm install @tanstack/react-query
npm install @tanstack/react-query-devtools

2. 설정

  • 앱 최상단에 QueryClientProvider로 설정
import React from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <MyComponent />
    </QueryClientProvider>
  );
}

export default App;

3. 기본 데이터 Fetching

  • useQuery훅을 사용해 데이터를 가져올 수 있음
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';

function MyComponent() {
  // 서버에서 데이터 가져오기
  const { data, isLoading, isError } = useQuery(['todos'], async () => {
    const response = await axios.get('https://jsonplaceholder.typicode.com/todos');
    return response.data;
  });

  // 로딩 상태 처리
  if (isLoading) return <div>Loading...</div>;

  // 에러 처리
  if (isError) return <div>Error fetching data</div>;

  // 데이터 렌더링
  return (
    <ul>
      {data.map(todo => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
}

4. 데이터 Mutations

  • 데이터를 변경 (Create, Update, Delete)할 때는 useMutation훅을 사용
import { useMutation, useQueryClient } from '@tanstack/react-query';
import axios from 'axios';

function AddTodo() {
  const queryClient = useQueryClient();

  const mutation = useMutation(newTodo => axios.post('/api/todos', newTodo), {
    onSuccess: () => {
      // 변경된 데이터를 다시 가져오도록 요청
      queryClient.invalidateQueries(['todos']);
    },
  });

  const handleAddTodo = () => {
    mutation.mutate({ title: 'New Todo', completed: false });
  };

  return (
    <button onClick={handleAddTodo} disabled={mutation.isLoading}>
      {mutation.isLoading ? 'Adding...' : 'Add Todo'}
    </button>
  );
}

5. 캐싱 및 데이터 갱신

  • 캐싱된 데이터를 효율적으로 관리하며 필요한 데이터 갱신
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5000, // 데이터가 "오래되지 않음" 상태로 유지되는 시간
      cacheTime: 10000, // 데이터가 메모리에 유지되는 시간
      refetchOnWindowFocus: true, // 창에 포커스될 때 데이터 다시 가져오기
    },
  },
});

6. React Query Devtools

  • 개발 도구를 사용해 쿼리 상태를 시각적으로 확인
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <MyComponent />
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}

React Query가 적합한 경우

  • 서버 상태가 자주 변경되며 이를 UI에 실시간으로 반영해야 하는 경우
  • 로딩, 에러 상태를 반복적으로 관리해야 하는 경우
  • 데이터를 캐싱하거나, 페이지네이션 또는 무한 스크롤 기능이 필요한 경우

React Query로 Custom Hook 만들기

  1. React Query의 훅 (useQuery, useMutation등)을 사용
  2. 특정 API 호출 로직 캡슐화
  3. 재사용 가능한 Hook으로 export

Custom Hook 장점

  1. 재사용성 : API 호출 로직을 여러 컴포넌트에서 재사용 가능
  2. 가독성 향상 : 데이터 로직과 UI 로직을 분리해 코드가 깔끔해짐
  3. 유지보수성 : API 로직이 한 곳에 모여 있어 수정이나 확장이 쉬움
  4. 중복 코드 제거 : 같은 API 호출에 대한 설정과 로직을 한 번만 작성

Custom Hook 예시

1. 데이터 Fetching

[특정 데이터를 가져오는 Hook]

  • Custom Hook
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';

const fetchUsers = async () => {
  const response = await axios.get('https://jsonplaceholder.typicode.com/users');
  return response.data;
};

// Custom Hook
export const useUsers = () => {
  return useQuery(['users'], fetchUsers, {
    staleTime: 5000, // 데이터가 5초 동안 "오래되지 않음" 상태로 유지
    cacheTime: 10000, // 데이터가 메모리에 10초 동안 유지
    refetchOnWindowFocus: false, // 창 포커스 시 재요청 비활성화
  });
};
  • Custom Hook 사용
import React from 'react';
import { useUsers } from './hooks/useUsers';

function UserList() {
  const { data: users, isLoading, isError } = useUsers();

  if (isLoading) return <p>Loading...</p>;
  if (isError) return <p>Error loading users!</p>;

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

2. Mutation

[특정 데이터 생성, 수정하는 Hook]

  • Custom Hook
import { useMutation, useQueryClient } from '@tanstack/react-query';
import axios from 'axios';

const addUser = async (newUser) => {
  const response = await axios.post('https://jsonplaceholder.typicode.com/users', newUser);
  return response.data;
};

// Custom Hook
export const useAddUser = () => {
  const queryClient = useQueryClient();

  return useMutation(addUser, {
    onSuccess: () => {
      // 사용자 추가 후 사용자 목록을 다시 가져옴
      queryClient.invalidateQueries(['users']);
    },
  });
};
  • Custom Hool 사용
import React, { useState } from 'react';
import { useAddUser } from './hooks/useAddUser';

function AddUserForm() {
  const [name, setName] = useState('');
  const mutation = useAddUser();

  const handleSubmit = (e) => {
    e.preventDefault();
    mutation.mutate({ name });
    setName('');
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Enter name"
      />
      <button type="submit" disabled={mutation.isLoading}>
        {mutation.isLoading ? 'Adding...' : 'Add User'}
      </button>
    </form>
  );
}

3. 매개 변수가 필요한 Custom Hook

  • Custom Hook
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';

const fetchUserById = async (userId) => {
  const response = await axios.get(`https://jsonplaceholder.typicode.com/users/${userId}`);
  return response.data;
};

// Custom Hook
export const useUser = (userId) => {
  return useQuery(['user', userId], () => fetchUserById(userId), {
    enabled: !!userId, // userId가 있을 때만 쿼리 실행
  });
};
  • Custom Hook 사용
import React, { useState } from 'react';
import { useUser } from './hooks/useUser';

function UserDetail() {
  const [userId, setUserId] = useState(null);
  const { data: user, isLoading, isError } = useUser(userId);

  return (
    <div>
      <input
        type="number"
        placeholder="Enter User ID"
        onChange={(e) => setUserId(Number(e.target.value))}
      />
      {isLoading && <p>Loading...</p>}
      {isError && <p>Error loading user data!</p>}
      {user && (
        <div>
          <h2>{user.name}</h2>
          <p>Email: {user.email}</p>
        </div>
      )}
    </div>
  );
}

Q. React-query의 장점과 사용법을 이해하고 있다.Custom Hooks를 작성하여 재사용 가능한 로직을 구현할 수 있습니다.

profile
정리정돈을 잘하는 개발자가 꿈

0개의 댓글