이번 편의 결과물: 지금 bookshelf(JS) 코드에서 타입이 있었다면 컴파일 시점에 잡혔을 오류 지점을 두 곳 확인합니다. 코드는 아직 바꾸지 않습니다. · 다루는 개념: allowJs/checkJs로 점진 전환하는 원칙, 빅뱅 전환의 위험, React 19의 타입 지형(@types/react, ReactNode), 이 과목의 최종 목표(모노레포) 미리보기
이 편에서 만드는 파일
이 편은 개념 편입니다. 새로 만들거나 고치는 파일이 없습니다. 대신 react_3를 마친 bookshelf의 기존 파일을 근거로 이야기합니다.
bookshelf/src/
├── components/BookCard.jsx ← 지금은 props 타입을 문서·주석으로만 약속
└── hooks/useBooks.js ← 지금은 반환값 모양을 호출부가 기억해서 씀개념 정리
지금 코드에서 타입이 못 잡아주는 지점
react_1부터 react_3까지 20편이 넘게 쌓인 bookshelf는 잘 동작합니다. 하지만 “잘 동작한다”와 “잘못 쓰면 즉시 알려준다”는 다른 이야기입니다. components/BookCard.jsx는 book 객체를 받아 제목·저자·상태를 렌더링합니다.
// bookshelf/src/components/BookCard.jsx (일부)
function BookCard({ book }) {
return (
<li className="book-card">
<h3>{book.title}</h3>
<p>{book.author}</p>
</li>
)
}이 함수는 book이 무엇인지 코드 어디에도 적혀 있지 않습니다. 다음 두 호출은 모두 편집기에서 아무 경고 없이 통과하고, 브라우저에서 실행해야만 문제가 드러납니다.
<BookCard book={{ title: '클린 코드' }} />
<BookCard />첫 번째는 author가 없어 화면에 빈 문단이 나옵니다. 두 번째는 book이 undefined라 book.title을 읽는 순간 Cannot read properties of undefined 예외가 던져져 화면 전체가 하얗게 됩니다. 두 경우 모두 코드를 저장하는 순간이 아니라 그 코드가 실행되는 순간에야 알 수 있습니다. hooks/useBooks.js도 마찬가지입니다. 이 훅이 정확히 어떤 모양의 객체를 반환하는지는 훅 안을 직접 읽어야만 알 수 있고, 사용하는 쪽에서 필드 이름을 하나 잘못 적어도 편집기는 조용합니다.
타입스크립트는 이 두 문제를 코드를 실행하기 전, 편집기 위에서 바로 알려줍니다. book의 타입을 한 번 정의해 두면 book.titel처럼 오타가 난 순간 빨간 줄이 뜨고, BookCard 없이 호출하면 “필수 프로퍼티가 없다”는 오류가 즉시 나타납니다.
빅뱅 전환과 점진적 전환
bookshelf의 모든 파일을 하루 만에 .tsx/.ts로 바꾸는 방법(빅뱅 전환)도 있습니다. 이 과목은 그 대신 점진적 전환을 택합니다.
| 기준 | 빅뱅 전환 | 점진적 전환(이 과목) |
|---|---|---|
| 작업 단위 | 프로젝트 전체를 한 번에 | 파일 하나씩, 편마다 |
| 전환 중 앱 상태 | 다 바꾸기 전까지 실행 불가할 수 있음 | 매 편이 끝날 때마다 정상 렌더링 |
| 오류 발생량 | 수십–수백 개가 한꺼번에 쏟아짐 | 방금 바꾼 파일 범위로 한정됨 |
| 롤백 난이도 | 어려움(변경 범위가 큼) | 쉬움(파일 하나만 되돌리면 됨) |
| 팀 작업 병행 | 사실상 불가능(충돌 위험) | 다른 기능 개발과 병행 가능 |
실무에서 다루는 앱은 대부분 bookshelf보다 훨씬 큽니다. 빅뱅 전환은 그 규모에서 “전환이 끝날 때까지 아무 기능도 못 만든다”는 위험을 감수해야 합니다. 점진적 전환은 매 편(실무에서는 매 커밋)마다 앱이 계속 동작한다는 것을 대가로, 한동안 .js와 .ts 파일이 섞여 있는 상태를 견뎌야 합니다. 이 과목은 이 혼용 상태를 안전하게 유지하는 도구로 allowJs와 checkJs를 씁니다.
allowJs와 checkJs
| 옵션 | 기본값 | 켜면 생기는 일 |
|---|---|---|
allowJs | false | .js/.jsx 파일을 컴파일 대상에 포함하고, .ts 파일에서 import할 수 있게 한다 |
checkJs | false | allowJs가 켜진 상태에서, .js/.jsx 파일 안의 타입 오류까지 검사한다 |
checkJs는 allowJs가 꺼져 있으면 켤 수 없습니다. .js 파일을 아예 컴파일 대상에서 빼놓고 그 안의 타입만 검사할 방법은 없기 때문입니다. 이 과목은 02편에서 allowJs: true, checkJs: false로 시작합니다. 아직 전환하지 않은 .jsx 파일은 컴파일에는 포함되어 화면에 정상적으로 렌더링되지만, 타입 오류 검사 대상에서는 빠집니다. 파일을 하나씩 .tsx로 바꿀 때마다 그 파일만 checkJs를 켠 것과 같은 효과가 생깁니다. 04편부터 이 흐름을 실제로 진행합니다.
React 19의 타입 지형
React 자체는 타입 선언을 포함하지 않습니다. @types/react, @types/react-dom 패키지가 useState, JSX.Element, 이벤트 객체 같은 타입을 제공합니다. 이 과목에서 자주 마주칠 타입은 아래와 같습니다.
| 타입 | 뜻 | 쓰이는 곳 |
|---|---|---|
ReactNode | 문자열, 숫자, JSX 엘리먼트, null 등 자식으로 올 수 있는 모든 값의 합집합 | children prop 타입(04편) |
ReactElement | JSX 엘리먼트만(문자열·숫자 제외) | 특정 컴포넌트만 자식으로 받고 싶을 때 |
ChangeEvent<T>, FormEvent<T> | DOM 이벤트 객체에 대상 요소 타입까지 붙인 제네릭 타입 | 입력·폼 핸들러(05편) |
JSX.Element | 컴포넌트 함수가 반환하는 값의 타입 | 컴포넌트 반환 타입을 명시할 때 |
JSX 문법을 쓰는 파일은 확장자가 .tsx여야 합니다. .ts 파일 안에 <BookCard /> 같은 태그를 쓰면 컴파일러가 이를 비교 연산자로 오해해 오류를 냅니다. 04편에서 컴포넌트 파일을 .jsx에서 .tsx로 바꿀 때 이 규칙이 그대로 적용됩니다.
이 과목의 최종 목표: bookshelf-ts 모노레포
02편부터 22편까지 거치는 전체 경로를 미리 봅니다.
bookshelf-ts/ ← pnpm 워크스페이스(14편부터)
├── apps/
│ └── web/ ← bookshelf(JS)가 전부 .tsx/.ts로 전환된 상태
├── packages/
│ ├── shared-types/ ← 도메인 타입·zod 스키마 공유(15편)
│ └── ui/ ← BookCard 등 재사용 컴포넌트, tsdown 빌드(17~18편)
└── .github/workflows/ci.yml ← 타입 체크·린트·테스트·빌드 자동화(21편)02편에서는 아직 apps/·packages/ 구분 없이 bookshelf-ts/ 단일 프로젝트로 시작합니다. 03편에서 이 구조를 나누는 이유를 먼저 짚고, 실제 전환은 14편부터 진행합니다.
직접 해보기
hooks/useBooks.js를 열어, 이 훅이 반환하는 객체에 어떤 필드가 있는지 코드만 보고 나열해 보세요. 그다음 BookListPage.jsx에서 그 필드들을 실제로 어떤 이름으로 쓰고 있는지 대조해 보세요.
useBooks가 useQuery를 감싸고 있다면 반환값은 data, isLoading, isError, error 등입니다. 이 이름을 하나라도 다르게 적어 써도(isloading처럼) 지금은 편집기가 알려주지 않고, 실행해서 화면이 이상하게 나와야 알아챌 수 있습니다. 06편에서 이 훅에 반환 타입을 명시하면 이런 오타가 바로 빨간 줄로 표시됩니다.
자주 하는 실수
| 증상 | 원인 | 고치는 법 |
|---|---|---|
| 전환을 시작하자마자 파일 수십 개에서 오류가 쏟아진다 | .js를 전부 .ts로 확장자만 바꾸고 strict 옵션을 처음부터 최고 강도로 켬 | allowJs로 기존 .js는 그대로 두고, 파일 하나를 .tsx로 바꿀 때만 그 파일의 오류를 본다 |
.jsx 파일 안 JSX 태그에서 알 수 없는 오류가 난다 | 확장자를 .ts로만 바꾸고 .tsx로 바꾸지 않음 | JSX를 쓰는 파일은 반드시 .tsx |
checkJs를 켰는데 오류가 안 남 | allowJs가 꺼져 있어 checkJs 자체가 무시됨 | allowJs: true를 먼저 켠다 |