JS로 작성된 React 코드를 TypeScript로 변환하기

베이시스·2022년 7월 18일
5
post-thumbnail
post-custom-banner

🌁 들어서며

기존에 JavaScript 기반으로 작성된 React 프로젝트를 TypeScript로 변환하는 과정과 그 과정에서 발생한 에러를 해결하는 과정을 다루도록 하겠습니다.

✏️ 과정 1: 웹팩이 JS 대신 TS를 로딩하도록 설정

CRA(Create-react-app)를 사용한 경우와 사용하지 않은 경우로 분리하여 설명하겠습니다.

with CRA

CRA를 사용하신다면 프로젝트를 TypeScript로 다시 생성하면 됩니다.

npx create-react-app "path" --typescript

그리고 기존 컴포넌트를 새 프로젝트의 src로 옮기고 과정 2를 진행합니다.

without CRA

  1. 우선 기존 프로젝트에 TypeScript를 추가합니다.
yarn add typescript @types/node @types/react @types/react-dom
yarn add -D @babel/preset-typescript ts-loader
  1. babel.config.js에 TypeScript 프리셋을 추가합니다.
module.exports = {
  presets: [
    '@babel/preset-react',
    '@babel/preset-env',
    '@babel/preset-typescript',
  ],
};
  1. webpack.config.js에서 ts 파일을 로드하도록 설정을 변경합니다.
    진입점을 꼭 바꿔 주어야 합니다.
{...}

module.exports = {
  {...},
  entry: './src/index.tsx',
  resolve: {
    extensions: ['*', '.ts', '.tsx', '.js'],
  },
  module: {
   	rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
      {...}
    ], 
  }
};
  1. tsconfig.json을 추가합니다.
    또는 Google TypeScript Style을 이용할 수도 있습니다.
tsc init # TypeScript 기본
npx gts init # Google style

✏️ 과정 2: 모든 js, jsx 파일을 ts, tsx 확장자로 변경

JSX 문법을 포함하는 JS 파일은 tsx, 일반 JS 파일은 ts 확장자로 모두 바꿉니다.
과정을 완료하면 위와 같은 구조가 됩니다. 사진이 너무 길쭉해져 디렉터리를 닫아 놓았을 뿐 하위 디렉터리의 모든 파일도 변경하여야 합니다.

✏️ 과정 3: 각 파일에서 발생하는 Type 에러를 모두 해결

가장 시간이 오래 걸리며 프로젝트 규모에 따라 영겁의 세월이 걸릴 수도 있는 과정입니다.

아래는 TypeScript에서 기본적으로 요구하는 사항 외에 React 또는 연관 라이브러리에 의해 발생하거나 잘 알려진 에러에 대한 해결 방법입니다.


Argument of type 'HTMLElement | null' is not assignable to parameter of type 'Element | DocumentFragment'

💩 에러가 생기는 코드

const container = document.getElementById('root');
const root = createRoot(container); // error!

React 루트 엘리먼트를 만들 때 container가 null일 수 있어 발생하는 문제입니다.
해당 container가 null일 때에 대해 예외 처리를 해 줍니다.
혹은 타입 단언을 해 주셔도 됩니다.

✅ 수정 결과

const container = document.getElementById('root');
if (!container) throw new Error('Failed to find root element');
const root = createRoot(container);

Binding element implicitly has an 'any' type

💩 에러가 생기는 코드

const MyComponent = ({ foo }) => {
  return <div>{foo}</div>;
}; // error!

React 함수형 컴포넌트의 인자 타입이 지정되지 않아 발생하는 문제입니다.
type을 지정합니다.

✅ 수정 결과

type MyComponentProps = {
  foo?: string;
};

const MyComponent = ({ foo }: MyComponentProps) => {
  return <div>{foo}</div>;
}

Module can only be default-imported using the 'esModuleInterop' flag

💩 에러가 생기는 코드

import React from 'react'; // error!

CommonJS 모듈을 ES6 모듈 코드베이스로 가져오려 할 때 발생하는 문제입니다.
Default named import를 다음과 같이 변경합니다.

✅ 수정 결과

import * as React from 'react';

No overload matches this call: useRef()

💩 에러가 생기는 코드

const ref = useRef(); // error!

useRef에 맞는 정의가 오버로딩되어 있지 않아 발생하는 문제입니다.
이 정의에 대해 잘 설명해 주신 포스팅이 있습니다.

✅ 수정 결과

const ref = useRef() as React.MutableRefObject<HTMLDivElement>;

No overload matches this call: styled-components

💩 에러가 생기는 코드

const StyledAppbar = styled.div`
  ${(props) =>
    props.left &&
    css`
      {...}
    `}
  ${(props) =>
    props.right &&
    css`
	  {...}
    `}
`; // error!

props를 사용 중인 경우 해당 프로퍼티에 맞는 인터페이스가 없어 발생하는 문제입니다.
해당 컴포넌트에 맞는 인터페이스를 생성합니다.

✅ 수정 결과

interface AppbarInterface {
  left?: boolean;
  right?: boolean;
}
const StyledAppbar = styled.div<AppbarInterface>`
  ${(props) =>
    props.left &&
    css`
      {...}
    `}
  ${(props) =>
    props.right &&
    css`
	  {...}
    `}
`;

📚 마치며

이외에도 적지 않은 TS 에러를 마주칠 수 있습니다. 하지만 그리 걱정하지는 않아도 될 것이, 여러분이 겪었던 에러는 전세계의 누군가는 똑같이 겪었으며 해결 방법도 어딘가에는 올라와 있습니다. 우리의 친구인 Stackoverflow에도요.

부족한 글 읽어 주셔서 감사합니다.

profile
사진찍는 주니어 프론트엔드 개발자
post-custom-banner

0개의 댓글