icon안동민 개발노트

프론트엔드 애플리케이션 개발


 React와 TypeScript를 사용한 대규모 프론트엔드 애플리케이션 개발은 타입 안전성과 개발 생산성을 크게 향상시킵니다.

 이 절에서는 프론트엔드 애플리케이션 개발의 다양한 측면을 살펴봅니다.

프로젝트 설정 및 컴포넌트 구조화

  1. Create React App을 사용한 프로젝트 생성
npx create-react-app my-app --template typescript
  1. 컴포넌트 구조화 예시
// src/components/Button/Button.tsx
import React from 'react';
 
interface ButtonProps {
  label: string;
  onClick: () => void;
  disabled?: boolean;
}
 
export const Button: React.FC<ButtonProps> = ({ label, onClick, disabled = false }) => (
  <button onClick={onClick} disabled={disabled}>
    {label}
  </button>
);
  1. 재사용 가능한 컴포넌트 라이브러리 구축
// src/components/index.ts
export { Button } from './Button/Button';
export { Input } from './Input/Input';
export { Card } from './Card/Card';

상태 관리

 Zustand를 사용한 타입 안전 상태 관리

// src/store/useStore.ts
import create from 'zustand';
 
interface BearState {
  bears: number;
  increase: (by: number) => void;
}
 
export const useStore = create<BearState>((set) => ({
  bears: 0,
  increase: (by) => set((state) => ({ bears: state.bears + by })),
}));
 
// 사용 예
import React from 'react';
import { useStore } from '../store/useStore';
 
export const BearCounter: React.FC = () => {
  const bears = useStore((state) => state.bears);
  const increase = useStore((state) => state.increase);
 
  return (
    <div>
      <h1>{bears} around here...</h1>
      <button onClick={() => increase(1)}>Add a bear</button>
    </div>
  );
};

라우팅 및 코드 스플리팅

 React Router와 React.lazy를 사용한 라우팅 및 코드 스플리팅

// src/App.tsx
import React, { Suspense } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
 
const Home = React.lazy(() => import('./pages/Home'));
const About = React.lazy(() => import('./pages/About'));
const Contact = React.lazy(() => import('./pages/Contact'));
 
const App: React.FC = () => (
  <Router>
    <Suspense fallback={<div>Loading...</div>}>
      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/about" component={About} />
        <Route path="/contact" component={Contact} />
      </Switch>
    </Suspense>
  </Router>
);

폼 처리 및 유효성 검사

 React Hook Form을 사용한 폼 처리

// src/components/LoginForm.tsx
import React from 'react';
import { useForm } from 'react-hook-form';
import { yup } from '@hookform/resolvers';
 
interface LoginFormData {
  email: string;
  password: string;
}
 
const schema = yup.object().shape({
  email: yup.string().email().required(),
  password: yup.string().min(8).required(),
});
 
export const LoginForm: React.FC = () => {
  const { register, handleSubmit, errors } = useForm<LoginFormData>({
    validationSchema: schema,
  });
 
  const onSubmit = (data: LoginFormData) => {
    console.log(data);
  };
 
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="email" ref={register} />
      {errors.email && <span>{errors.email.message}</span>}
      <input name="password" type="password" ref={register} />
      {errors.password && <span>{errors.password.message}</span>}
      <button type="submit">Login</button>
    </form>
  );
};

API 통신

 Axios와 React Query를 사용한 API 통신

// src/api/users.ts
import axios from 'axios';
import { useQuery } from 'react-query';
 
interface User {
  id: number;
  name: string;
  email: string;
}
 
const fetchUsers = async (): Promise<User[]> => {
  const { data } = await axios.get<User[]>('https://api.example.com/users');
  return data;
};
 
export const useUsers = () => {
  return useQuery<User[], Error>('users', fetchUsers);
};
 
// 사용 예
import React from 'react';
import { useUsers } from '../api/users';
 
export const UserList: React.FC = () => {
  const { data: users, isLoading, error } = useUsers();
 
  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>An error occurred: {error.message}</div>;
 
  return (
    <ul>
      {users?.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
};

테스팅

 Jest와 React Testing Library를 사용한 테스팅

// src/components/Button.test.tsx
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import { Button } from './Button';
 
describe('Button component', () => {
  it('renders with correct label', () => {
    const { getByText } = render(<Button label="Click me" onClick={() => {}} />);
    expect(getByText('Click me')).toBeInTheDocument();
  });
 
  it('calls onClick when clicked', () => {
    const handleClick = jest.fn();
    const { getByText } = render(<Button label="Click me" onClick={handleClick} />);
    fireEvent.click(getByText('Click me'));
    expect(handleClick).toHaveBeenCalledTimes(1);
  });
});

성능 최적화

 React.memo와 useMemo를 사용한 성능 최적화

// src/components/ExpensiveComponent.tsx
import React, { useMemo } from 'react';
 
interface ExpensiveComponentProps {
  data: number[];
}
 
export const ExpensiveComponent: React.FC<ExpensiveComponentProps> = React.memo(({ data }) => {
  const processedData = useMemo(() => {
    return data.map((item) => item * 2);
  }, [data]);
 
  return (
    <ul>
      {processedData.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
});

접근성(a11y) 구현

 접근성을 고려한 컴포넌트 예시

// src/components/AccessibleButton.tsx
import React from 'react';
 
interface AccessibleButtonProps {
  label: string;
  onClick: () => void;
  ariaLabel?: string;
}
 
export const AccessibleButton: React.FC<AccessibleButtonProps> = ({ 
  label, 
  onClick, 
  ariaLabel 
}) => (
  <button 
    onClick={onClick} 
    aria-label={ariaLabel || label}
  >
    {label}
  </button>
);

프론트엔드 아키텍처 가이드라인

 1. 컴포넌트 설계

  • 단일 책임 원칙 준수
  • 프레젠테이션과 컨테이너 컴포넌트 분리
  • 재사용 가능한 컴포넌트 라이브러리 구축

 2. 상태 관리

  • 전역 상태와 지역 상태 구분
  • 불변성 유지
  • 상태 정규화

 3. 타입 안전성

  • 엄격한 타입 체크 설정 (strict : true)
  • any 타입 사용 최소화
  • 유니온 타입과 제네릭 활용

 4. 성능 최적화

  • 컴포넌트 메모이제이션 (React.memo, useMemo, useCallback)
  • 코드 스플리팅 및 레이지 로딩
  • 가상화 기법 활용 (대량의 데이터 렌더링 시)

 5. 테스팅

  • 단위 테스트, 통합 테스트, E2E 테스트 구현
  • 테스트 커버리지 목표 설정 및 유지
  • 스냅샷 테스트 활용

 6. 코드 품질

  • ESLint와 Prettier를 사용한 일관된 코드 스타일 유지
  • 주석과 문서화
  • 코드 리뷰 프로세스 확립

 7. 접근성

  • WCAG 가이드라인 준수
  • 키보드 네비게이션 지원
  • 스크린 리더 호환성 확보

 8. 빌드 및 배포

  •  환경별 빌드 설정 (개발, 스테이징, 프로덕션)

  •  CI/CD 파이프라인 구축

  •  번들 크기 최적화 9. 에러 처리

  •  전역 에러 바운더리 구현

  •  사용자 친화적인 에러 메시지 제공

  •  로깅 및 모니터링 시스템 연동

 10. 보안

  • XSS 방지
  • CSRF 대응
  • 안전한 데이터 저장 (LocalStorage, SessionStorage 사용 시 주의)

 React와 TypeScript를 함께 사용하여 프론트엔드 애플리케이션을 개발할 때 타입 안전성과 개발 생산성을 동시에 높일 수 있습니다.

 컴포넌트 기반 아키텍처를 통해 재사용성과 유지보수성을 향상시키고 상태 관리 솔루션을 활용하여 복잡한 애플리케이션 상태를 효과적으로 관리할 수 있습니다.