요즘 프런트엔드 개발 좀 해봤다는 사람들 사이에서, 리액트와 타입스크립트는 사실상 ‘국룰’로 자리 잡았죠. 특히 규모가 큰 웹 앱, 그러니까 수십 개 페이지에 수백 개 컴포넌트가 얽혀 있는 대형 SPA(싱글 페이지 애플리케이션)를 만든다면? 리액트 훅스와 타입스크립트의 조합은 선택이 아니라 거의 필수입니다. 저도 최근 여러 팀 프로젝트를 진행하면서 이 조합의 진가를 직접 체감했고요. 오늘은 그 노하우를 전부 풀어보려고 해요.
💡 한눈에 보는 핵심 정리
- 함수형 컴포넌트 + 훅 기반 구조는 유지보수가 쉬운 코드 작성을 도와줍니다.
- 타입스크립트는 타입 안정성을 보장해 대규모 개발 시 버그를 크게 줄여줍니다.
- 커스텀 훅과 제네릭 타입을 활용하면 재사용성과 확장성이 급상승합니다.
- 상태 관리는 규모에 따라 Context, Redux Toolkit, Zustand 등으로 나눌 수 있습니다.
- 프로젝트 구조와 모듈화가 제대로 되어 있어야 팀원 간 협업이 원활해집니다.
1. 함수형 컴포넌트와 훅 중심의 아키텍처
리액트는 이제 클래스 컴포넌트를 거의 사용하지 않죠. 함수형 컴포넌트 + 훅(Hooks) 조합은 코드의 양도 줄이고, 가독성과 재사용성도 좋습니다. 예전엔 상태나 라이프사이클 로직을 위해 어쩔 수 없이 클래스 컴포넌트를 써야 했지만, 지금은 useState
, useEffect
, useRef
만으로도 대부분의 기능을 해결할 수 있어요.
예를 들어 로그인한 사용자 정보를 유지하는 훅을 만든다고 하면?
const useAuth = () => { const [user, setUser] = useState(null); ... }
이렇게 커스텀 훅으로 반복되는 로직을 분리하면, 어느 페이지든지 쉽게 붙여쓸 수 있고 디버깅도 편하죠. 게다가 리액트 18에서는 Concurrent Rendering도 훅과 찰떡궁합이라 성능 면에서도 이점이 많습니다.
2. 타입스크립트가 버그를 막아주는 진짜 이유
처음에는 TS(타입스크립트) 도입이 귀찮게 느껴질 수 있어요. 근데 프로젝트가 커지면 커질수록 이 선택이 얼마나 현명했는지 알게 됩니다. 타입이 명시돼 있으면 자동완성, 리팩토링, 버그 방지까지 3박자를 동시에 챙길 수 있어요.
- 컴포넌트 Props 인터페이스로 전달값 명확히 정의
- useState의 제네릭 타입으로 상태값 오류 예방
- API 응답 타입 지정으로 서버 데이터 관리
예를 들어, 버튼 컴포넌트를 만들 때도 이렇게요:
interface ButtonProps { label: string; onClick: () => void }
단순한 예지만, 이런 식으로 쌓이다 보면 프로젝트 전반이 탄탄해집니다. 그리고 무엇보다 협업할 때 서로의 코드 이해가 쉬워져서, 실수를 줄일 수 있어요.
3. Hooks + TS = 시너지 폭발
리액트 훅만 써도 편리한데, 여기에 타입스크립트까지 붙으면 어떻게 될까요? 코드가 훨씬 명확해지고, 에러 날 구석이 줄어듭니다. 특히 useReducer
쓸 때, 액션 타입과 상태 인터페이스를 지정하면 실수로 이상한 액션을 넣는 걸 미리 막아주죠.
또 하나, 커스텀 훅을 만들 때 제네릭을 사용하면 여러 타입에 대응 가능한 유틸 훅도 만들 수 있어요. 예를 들면:
function useToggle(initial: T) {...}
여기에 ESLint, Prettier 등과 함께 설정하면 규칙에 맞는 깔끔한 코드 작성이 가능하죠. 물론 초반엔 세팅이 귀찮지만, 리팩토링 때 피를 안 보게 됩니다 😂
4. 상태 관리, 대규모 프로젝트의 핵심
리액트의 최대 단점 중 하나는 상태가 커질수록 관리가 어렵다는 거예요. 그래서 Context API로 시작해 보다가, 규모가 커지면 Redux Toolkit, 또는 요즘 핫한 Zustand, Jotai 같은 경량 라이브러리로 확장하게 되죠.
상태관리 방식 | 추천 규모 | 특징 |
---|---|---|
Context API | 소규모 | 간단한 글로벌 상태 전달 |
Redux Toolkit | 중대형 | 자동 타입 생성, 미들웨어 지원 |
Zustand | 중소형 | 가벼운 상태관리 + Hooks 기반 |
이 모든 상태관리 도구들도 TS와 함께 쓰면 각 상태의 구조, 액션 타입 등을 명확하게 정의할 수 있어요. 게다가 서버 상태까지 관리하는 React Query도 타입스크립트랑 찰떡이라 API 응답을 실수 없이 다룰 수 있습니다.
5. 모듈화와 폴더 구조가 팀워크의 시작
대규모 SPA는 그냥 짜다 보면 코드가 엉망이 되기 쉬워요. 그래서 폴더 구조와 모듈화를 어떻게 하느냐가 정말 중요해요. 대표적인 구조는 아래처럼 나눕니다:
- components/: 재사용 가능한 UI 컴포넌트
- pages/: 라우팅 기반 페이지들
- hooks/: 커스텀 훅 모음
- contexts/: 글로벌 상태 제공 컨텍스트
- types/: 공통 타입 정의
그리고 tsconfig.json
에서 baseUrl과 paths를 설정하면 절대 경로 import가 가능해서 코드 가독성이 훨씬 좋아집니다. 예를 들어 @/hooks/useAuth
식으로요.
6. 빌드 도구와 최적화는 선택이 아닌 필수
빌드 도구는 최근엔 Webpack보다 Vite가 대세죠. 번개처럼 빠른 빌드 속도와 TS 호환성이 훌륭해서 중대형 프로젝트에 적합합니다. HMR(핫 모듈 리플레이스먼트)도 잘 되고요.
또한 코드 스플리팅, Tree Shaking, React.lazy 등으로 초기 로딩 속도를 최적화할 수 있어요. TS는 런타임에 사라지므로 Babel에서 preset-typescript 설정으로 JS로 트랜스파일해줍니다. CI에 ESLint와 Prettier를 붙이면 전체 품질 관리도 쉬워지죠.
7. 실전 사례: 복잡한 SPA에서 살아남기
최근에 참여했던 프로젝트 하나는 200개 넘는 페이지와 수백 개의 컴포넌트가 얽혀 있었어요. 이걸 클래스 컴포넌트로 짰다면? 상상도 하기 싫네요. 우리는 useAuth, useModal, useQueryParams 같은 훅들을 만들어서 관심사 분리를 했고, 각 상태와 API 응답에 대해 타입을 명확하게 설정했죠.
결과적으로 팀원 간 충돌도 적었고, 리팩토링 시 실수도 거의 없었습니다. 특히 타입이 있던 덕분에, IDE가 알려주는 자동완성과 타입 추론이 너무 편했어요. 새로 들어온 팀원도 타입만 봐도 구조를 바로 이해하더라고요.
마무리하며
리액트 훅스와 타입스크립트는 따로 써도 좋지만, 같이 쓰면 진짜 ‘무기’가 됩니다. 특히 SPA처럼 스케일이 큰 프로젝트에서는 이 조합이 코드 품질, 생산성, 유지보수성 모든 면에서 탁월하죠. 물론 처음엔 세팅과 학습 비용이 좀 들 수 있지만, 그만큼 얻는 게 큽니다.
“앞으로 프런트엔드를 한다면 이 조합은 피해갈 수 없다”는 말이 결코 과장이 아니라는 걸, 실제로 경험해보면 실감하게 될 거예요. 개발자의 시간을 아껴주고, 사용자에게는 더 나은 경험을 주는 이 조합, 여러분도 지금 바로 시작해보세요. 😉