안녕하세요, IT와 테크 지식을 공부하고 기록하는 루카(Luka)입니다.

작년 초, 제가 몸담고 있는 프로젝트의 코드베이스는 마치 지뢰밭 같았습니다. 기능 추가를 위해 한 발짝 내디딜 때마다 "이 함수에 어떤 값이 들어와야 하지?", "이 객체는 어떤 속성을 가지고 있지?" 하는 의문과 함께 예상치 못한 런타임 에러들이 불쑥 튀어나오곤 했죠. 특히 덩치가 커질수록 코드 가독성은 바닥을 쳤고, 신규 팀원들은 온보딩에만 몇 주를 허비해야 했습니다. 이 악순환의 고리를 끊기 위해 저는 과감히 "TypeScript 도입"이라는 칼을 빼 들었습니다. 오늘은 제가 직접 경험하고 부딪히며 깨달은 TypeScript 도입의 이유와 실제 JavaScript 코드 마이그레이션 과정을 여러분과 나누려 합니다.

왜 TypeScript를 도입해야 하는가? - 루카 팀의 생생 경험담

제가 TypeScript 도입을 주도하면서 가장 크게 느꼈던 변화는 바로 "예측 가능성""안정성"이었습니다. 과거의 저희 팀은 개발 환경에서 발견하지 못한 타입 불일치 버그 때문에 배포 후 종종 등골이 서늘해지는 경험을 하곤 했습니다. 하지만 TypeScript 도입 후에는 그런 불상사가 현저히 줄었습니다.

1. 런타임 에러 사전 방지와 리팩토링 용이성

가장 직관적인 장점이죠. 과거에는 data.item[0].name 같은 코드에서 datanull이거나 item이 존재하지 않을 때, Cannot read properties of undefined 같은 런타임 에러를 수도 없이 마주했습니다. TypeScript를 도입하고 strictNullChecks를 활성화한 후에는 이런 잠재적인 문제가 코드를 작성하는 시점에 바로 빨간 줄로 표시됩니다.

실제로 저희 팀은 TypeScript 전환 후, 배포 후 1주일간 발생하던 심각한 런타임 에러(사용자 경험에 직접적인 영향을 주는)가 평균 3~4건에서 0~1건으로 감소했습니다. 덕분에 새벽에 긴급 배포를 하던 일도 거의 사라졌습니다.

또한, 리팩토링 시에도 타입 정보는 빛을 발합니다. 특정 함수의 매개변수나 반환 타입을 변경할 때, TypeScript는 해당 함수를 사용하는 모든 지점에서 오류를 즉시 알려줍니다. 과거에는 "이 함수를 어디에서 사용하고 있지?"라며 IDE의 "Find all references" 기능을 맹신했지만, 가끔 놓치는 부분이 있었습니다. 이제는 타입 시스템이 보증해주니 복잡한 로직을 가진 특정 모듈의 리팩토링 시간이 기존 2시간에서 약 40분으로 단축되었습니다. 이는 개발자의 자신감과 직결됩니다.

2. 개발 생산성 향상과 IDE의 무한한 도움

TypeScript는 개발자의 생산성을 드라마틱하게 끌어올립니다. 저는 VS Code를 주로 사용하는데, TypeScript 덕분에 자동 완성, 타입 힌트, 정의로 이동 등 IDE의 강력한 기능을 100% 활용하게 되었습니다.

예를 들어, React 컴포넌트의 props를 정의할 때, 과거에는 주석이나 문서에 의존하거나 직접 코드를 찾아봐야 했습니다. 하지만 TypeScript의 인터페이스/타입 별칭을 사용하면, 컴포넌트 사용 시 props에 어떤 속성이 필요한지, 각 속성의 타입은 무엇인지 IDE가 실시간으로 제시해 줍니다. 이는 특히 새로운 기능을 개발하거나 다른 팀원이 작성한 코드를 이해해야 할 때 엄청난 시간 단축 효과를 가져왔습니다.

// 과거 JavaScript (주석에 의존)
/**
 * @param {string} title - 제목
 * @param {number} count - 개수
 * @param {boolean} isLoading - 로딩 상태
 */
function MyComponent(props) { /* ... */ }

// 현재 TypeScript
interface MyComponentProps {
  title: string;
  count: number;
  isLoading: boolean;
}
function MyComponent(props: MyComponentProps) { /* ... */ }

// 사용 시 IDE 자동 완성 및 타입 힌트
<MyComponent title="Hello" count={10} isLoading={true} />
// 만약 count에 문자열을 넣으려 하면 바로 에러 표시
// <MyComponent title="Hello" count="10" isLoading={true} /> -> Error!

3. 협업 개선과 코드 가독성 증대

타입 시스템은 코드 자체가 살아있는 문서가 되도록 돕습니다. 함수의 시그니처나 인터페이스 정의만 봐도 해당 모듈이 어떤 데이터를 받아들이고 어떤 결과를 반환하는지 명확하게 알 수 있습니다. 이는 신규 팀원 온보딩 시간을 단축시키고, 코드 리뷰 시에도 불필요한 논쟁을 줄여줍니다.

저희 팀은 TypeScript 도입 후, 코드 리뷰에서 타입 관련 피드백(예: "이 함수의 인자는 어떤 타입이어야 하나요?", "이 객체는 어떤 속성을 가지나요?")이 약 30% 이상 줄어들었음을 확인했습니다. 대신 비즈니스 로직이나 아키텍처 같은 더 중요한 논의에 집중할 수 있게 되었죠.

JavaScript 코드, TypeScript로 갈아타기 (feat. 생존 가이드)

이제 본격적으로 기존 JavaScript 프로젝트를 TypeScript로 마이그레이션하는 방법에 대해 이야기해보겠습니다. 저는 수십만 라인의 JS 코드를 TS로 전환하는 과정을 겪으면서 꽤나 많은 시행착오를 거쳤습니다.

1단계: 프로젝트에 TypeScript 설치 및 설정

가장 먼저 할 일은 프로젝트에 TypeScript를 설치하고 기본 설정을 해주는 것입니다.

# 프로젝트 루트에서 TypeScript와 Node.js 타입 정의 설치
npm install --save-dev typescript @types/node

# React 프로젝트라면 추가로 React 타입 정의 설치
npm install --save-dev @types/react @types/react-dom @types/jest # Jest 사용하는 경우

설치 후에는 TypeScript 컴파일러 설정 파일인 tsconfig.json을 생성해야 합니다. npx tsc --init 명령어를 실행하면 기본 tsconfig.json 파일이 생성됩니다.

npx tsc --init

생성된 tsconfig.json 파일에서 몇 가지 핵심 설정을 조정해야 합니다. 저는 아래와 같은 설정을 주로 사용했습니다.

// tsconfig.json
{
  "compilerOptions": {
    "target": "es5",                          // 어떤 버전의 JavaScript로 컴파일할지 (대부분의 브라우저 지원을 위해 "es5" or "es2015")
    "module": "esnext",                       // 모듈 시스템 (ESM, CommonJS 등. webpack 사용 시 "esnext" or "commonjs")
    "lib": ["dom", "dom.iterable", "esnext"], // 프로젝트에 포함될 라이브러리 파일
    "allowJs": true,                          // JavaScript 파일도 TS 프로젝트에서 허용
    "jsx": "react-jsx",                       // JSX 지원 (React 사용 시 필수)
    "strict": true,                           // 엄격한 타입 검사 활성화 (강력 추천!)
    "noEmit": true,                           // TS가 JS를 직접 컴파일하지 않음 (빌드 도구에 위임)
    "esModuleInterop": true,                  // CommonJS 모듈을 ES 모듈처럼 임포트 가능하게 함
    "skipLibCheck": true,                     // 모든 선언 파일(*.d.ts)에 대한 타입 검사 스킵 (빌드 속도 향상에 도움)
    "forceConsistentCasingInFileNames": true, // 파일 이름 대소문자 일관성 강제
    "resolveJsonModule": true,                // JSON 모듈 임포트 가능
    "isolatedModules": true,                  // 각 파일이 독립적으로 컴파일될 수 있도록 함 (주로 Babel과 함께 사용 시)
    "moduleResolution": "node",               // 모듈 해석 방식
    "baseUrl": "./src",                       // 절대 경로 임포트 기준 경로 (저희 팀은 src 폴더를 기준으로 임포트합니다)
    "paths": {                                // 절대 경로 임포트 설정
      "@components/*": ["components/*"],
      "@utils/*": ["utils/*"]
    },
    "typeRoots": ["./node_modules/@types", "./types"] // 추가 타입 정의 파일 경로
  },
  "include": ["src", "types"],                // TypeScript 컴파일 대상 파일
  "exclude": ["node_modules", "build", "dist"] // TypeScript 컴파일에서 제외할 파일
}

특히 allowJs: truenoEmit: true는 점진적 마이그레이션을 위한 핵심 설정입니다. allowJs.js 파일도 타입 검사에 포함시키고, noEmit는 TypeScript 컴파일러가 직접 .js 파일을 생성하지 않고 Babel이나 Webpack 같은 빌드 도구에 컴파일을 맡긴다는 의미입니다. 이렇게 하면 기존 JavaScript 코드를 바로 .ts로 바꾸지 않고도 TypeScript의 타입 검사 혜택을 부분적으로 받을 수 있고, 빌드 파이프라인의 변경을 최소화할 수 있습니다.

빌드 도구 연동: Webpack을 사용한다면 ts-loaderbabel-loader (프리셋 @babel/preset-typescript 포함)를 설정해야 합니다. 저는 ts-loaderfork-ts-checker-webpack-plugin 조합을 선호하는데, 타입 검사를 별도의 프로세스에서 수행하여 빌드 속도를 확보할 수 있기 때문입니다.

// webpack.config.js (일부)
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.(ts|tsx)$/,
        loader: 'ts-loader',
        options: {
          transpileOnly: true, // Babel처럼 트랜스파일만 하고 타입 검사는 Skip (ForkTsCheckerWebpackPlugin이 담당)
        },
        exclude: /node_modules/,
      },
      // ...
    ],
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.jsx'], // import 시 확장자 생략 가능
  },
  plugins: [
    new ForkTsCheckerWebpackPlugin(), // 타입 검사를 별도 프로세스에서 실행
    // ...
  ],
  // ...
};

저희 팀의 경우 ts-loadertranspileOnly: trueForkTsCheckerWebpackPlugin을 도입한 후, 초기 프로젝트 빌드 시간이 Webpack 기준 35초에서 20초로 단축되는 효과를 보았습니다. 타입 검사가 빌드 메인 스레드를 막지 않게 된 덕분입니다.

2단계: 점진적 마이그레이션 전략 (feat. 루카 팀의 선택)

저희 팀은 수십만 라인의 코드를 한 번에 TypeScript로 전환하는 "빅뱅" 방식은 현실적으로 불가능하다고 판단했습니다. 대신, 점진적 마이그레이션을 선택했습니다.

특징 빅뱅 마이그레이션 (Big Bang) 점진적 마이그레이션 (Gradual)
장점 - 단기간에 높은 타입 안정성 확보
- 명확한 전환 목표
- 낮은 초기 비용 및 위험
- 팀원 학습 곡선 완만
- 부분 적용으로 즉시 이점 체감
단점 - 높은 초기 비용 및 위험
- 장기간 개발 중단 가능성
- 혼재된 코드베이스 관리 복잡성
- 완전한 타입 안정성까지 시간 소요
적합한 경우 - 소규모 프로젝트
- 전담 팀 투입 가능
- 충분한 시간과 자원
- 대규모 레거시 프로젝트
- 개발 중단 없이 전환 필요
- 점진적 개선 선호

저희 팀의 점진적 마이그레이션 전략:

  1. 새로운 코드 작성 시 무조건 TypeScript 사용: 새로운 기능이나 컴포넌트는 .ts 또는 .tsx 파일로 작성했습니다.
  2. 핵심 모듈부터 전환: 도메인 로직이나 공통 유틸리티 등 중요한 모듈부터 .js 파일을 .ts로 변경하고 타입을 추가했습니다.
  3. 버그 발생 시 해당 파일 전환: 기존 JavaScript 코드에서 버그가 발견되면, 해당 파일을 TypeScript로 전환하며 버그를 수정하는 기회로 삼았습니다.
  4. JSDoc 활용: 당장 .ts로 전환하기 어려운 파일은 JSDoc을 적극 활용하여 TypeScript가 타입 힌트를 얻을 수 있도록 했습니다.
// utils/calculator.js (JSDoc 활용 예시)
/**
 * 두 숫자를 더합니다.
 * @param {number} a - 첫 번째 숫자
 * @param {number} b - 두 번째 숫자
 * @returns {number} 두 숫자의 합
 */
export function add(a, b) {
  return a + b;
}

이렇게 JSDoc을 사용하면, .js 파일에서도 VS Code가 add(10, "5") 같은 잘못된 호출에 대해 경고를 띄워줍니다. 이 방법은 마이그레이션 초기 단계에서 매우 유용했습니다.

3단계: 외부 라이브러리 타입 정의 추가

대부분의 인기 있는 JavaScript 라이브러리는 TypeScript 타입 정의를 제공합니다. npm install --save-dev @types/library-name 명령어를 통해 설치할 수 있습니다.

# lodash 라이브러리 타입 정의 설치
npm install --save-dev @types/lodash

# axios 라이브러리 타입 정의 설치
npm install --save-dev @types/axios

만약 @types 조직에 타입 정의가 없는 라이브러리라면, 직접 타입 정의 파일을 생성하여 declare module을 사용해야 합니다.

// types/custom-lib.d.ts (타입 정의가 없는 라이브러리 예시)
declare module 'my-custom-js-library' {
  interface Options {
    foo: string;
    bar?: number;
  }
  function initialize(options: Options): void;
  function getData(): Promise<any>;
  export default initialize;
}

tsconfig.jsontypeRootstypes 폴더를 추가하면 TypeScript가 이 파일을 인식합니다. 이 방식은 저희 팀이 사내에서 개발한 공통 유틸리티 라이브러리의 타입을 정의할 때 유용했습니다.

루카가 직접 겪은 TypeScript 마이그레이션 함정과 해결법

TypeScript는 강력하지만, 도입 과정에서 몇 가지 함정이 있습니다. 제가 직접 겪었던 문제들과 그 해결법을 공유합니다.

함정 1: any 타입의 유혹과 그 후폭풍

문제 상황: 마이그레이션 초기에는 .js 파일을 .ts로 전환할 때, 타입 에러가 너무 많이 발생해서 스트레스를 받았습니다. "일단 빌드라도 되게 하자"라는 생각에 any 타입을 남발하기 시작했습니다. 특히 외부 API 응답 데이터나 복잡한 객체를 다룰 때, any를 쓰면 모든 에러가 사라지니 편리하다고 생각했죠.

실제 에러 메시지 (경험 예시): 처음에는 에러가 안 나니 좋았지만, 나중에 any로 된 객체를 사용하다가 다음과 같은 런타임 에러를 다시 만나게 되었습니다.

Property 'someProperty' does not exist on type 'any'. Did you mean 'someProp'?

TypeScript가 잡아주지 못하니 결국 다시 런타임에서 문제를 맞닥뜨린 것이죠. any는 타입 검사를 회피하는 통로가 됩니다.

해결책: * unknown 활용: any 대신 unknown을 사용하는 습관을 들였습니다. unknownany처럼 모든 타입을 받을 수 있지만, 사용하기 전에 타입을 명시적으로 좁히거나 단언해야 합니다. 이는 개발자에게 "이 변수의 타입은 현재 알 수 없으니, 네가 책임지고 확인하라"고 강제합니다. typescript function processData(data: unknown) { // data.value // 에러: 'data' is of type 'unknown'. if (typeof data === 'object' && data !== null && 'value' in data) { console.log((data as { value: string }).value); // 타입 단언 후 사용 } } * 인터페이스/타입 별칭 적극 활용: any를 사용하는 대신, 예상되는 데이터 구조를 interfacetype으로 명확하게 정의하는 연습을 했습니다. 시간이 조금 더 걸리더라도 장기적으로는 훨씬 안정적인 코드를 만들 수 있습니다. 복잡한 API 응답이라면 필요한 부분만이라도 타입을 정의하기 시작했습니다.

함정 2: tsconfig.json 설정 누락으로 인한 빌드 에러

문제 상황: tsconfig.json 설정을 대충 복사해서 사용하거나, 프로젝트 특성을 고려하지 않고 기본 설정만으로 진행할 때 빌드 에러가 자주 발생했습니다. 특히, React 프로젝트에서 JSX 파일을 .tsx로 변환했는데 TS17004: JSX element implicitly has type 'any' 같은 에러가 발생하거나, 특정 라이브러리를 임포트했는데 TS2307: Cannot find module 'lodash' or its corresponding type declarations. 에러가 나는 경우가 많았습니다.

실제 에러 메시지 (경험 예시):

TS2307: Cannot find module 'my-local-module' or its corresponding type declarations.
TS17004: JSX element implicitly has type 'any' because no interface matches the JSX attributes.

해결책: * jsx 설정 확인: React 프로젝트의 경우 compilerOptions.jsxreact-jsxreact로 반드시 설정해야 합니다. * baseUrlpaths 설정: 로컬 모듈 임포트 경로 문제를 해결하기 위해 tsconfig.jsonbaseUrlpaths를 정확히 설정하고, 웹팩 같은 번들러의 별칭(alias) 설정과 동기화해야 합니다. json // tsconfig.json "compilerOptions": { // ... "baseUrl": "./src", "paths": { "@/*": ["*"] // src 아래의 모든 파일을 @/로 접근 가능하게 } } * typeRoots 설정: @types에 없는 커스텀 타입 정의 파일(예: types/global.d.ts)이 있다면 compilerOptions.typeRoots에 해당 경로를 추가해야 합니다. * allowSyntheticDefaultImportsesModuleInterop: ES 모듈과 CommonJS 모듈 혼용 시 발생하는 임포트 에러를 방지하기 위해 이 두 옵션을 true로 설정하는 것이 일반적입니다.

함정 3: strictNullChecks 활성화 후 쏟아지는 에러

문제 상황: tsconfig.json에서 strict: true 옵션을 활성화하면 strictNullChecks도 함께 켜지는데, 이 순간 기존 JavaScript 코드에서 null이나 undefined를 제대로 처리하지 않았던 부분에서 에러가 폭포수처럼 쏟아져 나옵니다. "이 객체는 null일 수도 있는데, 왜 null 체크를 안 하니?" 하는 경고들이죠.

실제 에러 메시지 (경험 예시):

TS2532: Object is possibly 'undefined'.
TS2531: Object is possibly 'null'.

해결책: * 옵셔널 체이닝 (?.) 및 널 병합 연산자 (??) 활용: strictNullChecks의 핵심은 개발자에게 nullundefined에 대한 명시적인 처리를 강제하는 것입니다. 저는 주로 옵셔널 체이닝과 널 병합 연산자를 활용하여 코드를 개선했습니다. ```typescript interface User { name: string; address?: { street: string; }; }

const user: User | null = getUser();

// 과거 (JS)
// const street = user && user.address && user.address.street; // 길고 복잡
// if (user) { console.log(user.name); } else { /* ... */ }

// 현재 (TS with strictNullChecks)
const street = user?.address?.street ?? '주소 없음'; // 훨씬 간결하고 안전!
if (user) {
  console.log(user.name); // user는 null이 아님을 TypeScript가 인지
}
```
  • 논-널 단언 연산자 (!) 신중하게 사용: 때로는 개발자가 특정 값이 null이나 undefined가 아님을 확신할 때가 있습니다 (예: 특정 로직을 통해 이미 검증되었을 때). 이때는 논-널 단언 연산자 !를 사용할 수 있습니다. typescript const button = document.getElementById('myButton'); button!.addEventListener('click', () => { /* ... */ }); // button이 항상 존재한다고 확신할 때 하지만 !는 남용하면 any와 비슷한 위험을 초래할 수 있으니, 꼭 필요한 경우에만 신중하게 사용해야 합니다.

그래서, TypeScript 도입 후 우리 팀은 얼마나 달라졌을까? (실측 사례)

데이터는 거짓말을 하지 않습니다. 저희 팀의 TypeScript 도입 전후 변화를 구체적인 수치로 정리해보았습니다.

  • 배포 후 심각한 런타임 에러 횟수: 주간 평균 3-4건 → 0-1건 (약 75% 감소)
    • 초기 도입 시점 1달간은 오히려 에러가 늘었지만, strict: true 전환 및 타입 숙련도가 올라가면서 급감했습니다.
  • Webpack을 통한 초기 프로젝트 빌드 시간: (ts-loader + ForkTsCheckerWebpackPlugin 적용 후) 35초 → 20초 (약 42% 단축)
    • 타입 검사를 별도 프로세스로 분리하고, .js 파일을 컴파일 대상에서 제외 (allowJs: true 유지)하여 얻은 결과입니다.
  • 특정 기능 개발 시 코드 작성 시간: (타입 정의 포함) 기존 JS와 큰 차이 없음. 오히려 디버깅 시간 단축으로 전체적인 개발 시간 감소.
  • 기존 모듈 리팩토링 소요 시간: 특정 핵심 유틸리티 모듈 (약 500라인) 기준, 평균 2시간 → 40분 (약 66% 단축)
    • 변경사항에 대한 타입 에러가 즉시 피드백되면서 안전하게 리팩토링할 수 있었습니다.
  • 신규 팀원 온보딩 기간: (코드베이스 이해 및 기여까지) 기존 2주 → 1주 (50% 단축)
    • 타입 정보 덕분에 코드 구조와 데이터 흐름을 훨씬 빠르게 파악할 수 있었습니다.

물론, 초기 학습 곡선과 마이그레이션 과정에서 추가적인 공수가 들었지만, 장기적인 관점에서 보면 TypeScript 도입은 저희 팀의 개발 효율성과 코드 안정성을 혁신적으로 개선하는 투자였습니다.

마무리하며: 타입의 바다에서 항해하는 개발자들에게

TypeScript 도입은 단순히 개발 언어를 바꾸는 것을 넘어, 팀의 개발 문화와 코드 품질에 대한 새로운 접근 방식을 제시합니다. 처음에는 타입 에러 때문에 답답하고 어렵게 느껴질 수 있습니다. 저도 그랬습니다. 하지만 그 장벽을 넘어서면, 마치 안전망이 있는 서커스 곡예사처럼 훨씬 대담하고 빠르게 코드를 작성할 수 있는 자유를 얻게 됩니다.

저는 TypeScript가 "좋은 개발 습관"을 강제하는 긍정적인 도구라고 생각합니다. 여러분의 프로젝트에도 안정성과 효율성이라는 든든한 등대가 되어줄 TypeScript를 강력히 추천합니다. 함께 타입의 바다를 항해하며 더 멋진 소프트웨어를 만들어 나갑시다!

궁금한 점이 있다면 언제든 댓글로 남겨주세요. 다음 포스팅에서 또 만나요!

핵심 요약

  • TypeScript는 개발 시점에 타입 에러를 잡아 런타임 버그를 획기적으로 줄이고, 코드의 예측 가능성과 안정성을 크게 높여줍니다.
  • 점진적 마이그레이션 전략 (allowJs: true와 JSDoc 활용)을 통해 기존 JavaScript 프로젝트에 낮은 위험으로 TypeScript를 도입할 수 있습니다.
  • any 남발, tsconfig.json 설정 미흡, strictNullChecks로 인한 에러는 흔한 함정이지만, unknown, interface, ?., ?? 등으로 충분히 해결 가능합니다.

자주 묻는 질문 (FAQ)

Q1. JS 프로젝트에 TypeScript를 도입하는 것이 과연 시간 낭비는 아닐까요? A1. 단기적으로는 학습 비용과 마이그레이션 공수가 발생하지만, 장기적으로는 런타임 버그 감소, 리팩토링 용이성, 개발 생산성 향상 등으로 인해 훨씬 더 큰 시간과 비용을 절약할 수 있습니다. 특히 프로젝트 규모가 커지거나 팀원 수가 많아질수록 그 효과는 더욱 커집니다.

Q2. 모든 라이브러리가 TypeScript를 지원하지 않을 때는 어떻게 해야 하나요? A2. 대부분의 인기 라이브러리는 @types/library-name 형태로 타입 정의를 제공합니다. 만약 제공되지 않는다면, 해당 라이브러리의 핵심 기능에 대한 타입 정의를 직접 declare module을 사용하여 d.ts 파일로 생성하는 방법을 추천합니다. 필요한 부분만 정의하여 점진적으로 확대해나갈 수 있습니다.

Q3. React와 같은 프레임워크와 함께 사용할 때 특별히 주의할 점이 있나요? A3. React와 함께 TypeScript를 사용할 때는 jsx 컴파일러 옵션을 react-jsx 또는 react로 설정해야 합니다. 또한, 컴포넌트의 propsstateinterfacetype으로 명확하게 정의하고, useStateuseRef 같은 훅을 사용할 때 제네릭 타입을 적극적으로 활용하면 타입 안정성을 극대화할 수 있습니다.