JavaScript React Project를 TypeScript로 변환하기 (migrate a react-js project to typescript)

suno·2023년 1월 18일
post-thumbnail

GitHub Repository

GitHub/react-todo-list


오늘은 JavaScript 코드로 작성된 Todo List React Project를 TypeScript로 변환해 보았다.
Create React App으로 처음부터 TypeScript 기반 프로젝트를 생성해도 되지만, 실제로 현업에서도 React 버전 업그레이드, 라이브러리 변경, redux → react query 등등 기존 프로젝트를 리팩토링 하는 작업이 많다고 한다.

생각보다 변경할 코드가 많지는 않았지만 에러를 하나하나 해결하는 과정은 굉장히 오래걸렸다. 프로젝트 규모가 커질수록 그 시간은 훨씬 길어지겠지..?

다음번 프로젝트에서 TypeScript를 사용한다면 interface와 enum 등 효율적으로 타입을 쓸 수 있는 방안도 추가로 고민해봐야겠다.


How to Migrate

TypeScript 설정

1. TypeScript 패키지 추가하기

typescript와 기존 react 패키지들을 typescript 버전으로 추가한다.

yarn add typescript @types/node @types/react @types/react-dom 

2. tsconfig.json 설정

터미널 명령어 tsc --init를 입력해 tsconfig.json 파일을 초기화한다.

// tsconfig.json

{
  "compilerOptions": {
    "outDir": "./dist/", // path to output directory
    "sourceMap": true, // allow sourcemap support
    "strictNullChecks": true, // enable strict null checks as a best practice
    "module": "es6", // specify module code generation
    "jsx": "react", // use typescript to transpile jsx to js
    "target": "es5", // specify ECMAScript target version
    "allowJs": true, // allow a partial TypeScript and JavaScript codebase
    "moduleResolution": "node", // specify module resolution strategy
    "allowSyntheticDefaultImports": true
  },
  "include": ["./src/"]
}

파일 변환하기

이제부터 js/jsx 파일을 ts/tsx 파일로 하나씩 바꿔나가야 한다.

3. index.tsx, App.tsx

// index.tsx

import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux';
import store from './store/config/configStore';
import { ResetCSS, GradientBackground } from './components/styles/GlobalStyle';

const root = ReactDOM.createRoot(
  document.querySelector('#root') as HTMLElement
);
root.render(
  <Provider store={store}>
    <ResetCSS />
    <GradientBackground />
    <App />
  </Provider>
);

4. router.jsx

  • 에러: 'React' refers to a UMD global, but the current file is a module. Consider adding an import instead.
  • 해결: tsconfig.json 파일에 "jsx": "react-jsx" 옵션 추가

5. Styled Component

  • 에러: Property 'visible' does not exist on type 'ThemedStyledProps<Pick<DetailedHTMLProps<HTMLAttributes, HTMLDivElement>, "key" | keyof HTMLAttributes> & { ...; }, any>'.
  • 해결: styled component 함수에 타입을 지정
// AddForm.tsx

<styled.TodoInputErrorMessage visible={visible}>
  내용을 입력하세요.
</styled.TodoInputErrorMessage>
// AddForm.style.tsx

// 1) interface 사용하는 방법
interface TodoInputErrorMessageProps {
  visible: boolean;
}

// 2) type 사용하는 방법
type TodoInputErrorMessageProps = {
  visible: boolean;
};

export const TodoInputErrorMessage = styled.div<TodoInputErrorMessageProps>`
  display: ${(props) => (props.visible ? 'block' : 'none')};
  position: absolute;
  left: 100px;
  top: 50px;
  color: #e17aa0;
`;

6. HTML DOM Event

  • 에러: Parameter 'e' implicitly has an 'any' type, but a better type may be inferred from usage.
  • 해결: event 인자에 타입을 정의한다.
  • type이 뭔지 모르겠을 땐 vscode의 힌트를 활용하기!
// useInput.ts

const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
  setTodoValue(e.target.value);
};
// useTodoQuery.ts

const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
  e.preventDefault();

  const todo = todoValue.trim();
  if (!todo) {
    setTodoValue('');
    setVisible(true);
    return;
  }

  mutationAdd.mutate({ isDone: false, todo });
  setVisible(false);
  setTodoValue('');
};

7. React Query

invalidateQueries

  • 에러: No overload matches this call.
  • 해결: invalidateQueries 메소드에 인자로 option 객체를 전달한다.
// useInput.ts

const mutationDelete = useMutation({
  mutationFn: async (id) => {
    await axios.delete(`${SERVER_URL}/todos/${id}`);
  },
	
  onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
});

mutationFn

  • 에러: Argument of type '{ isDone: boolean; todo: string; }' is not assignable to parameter of type 'void'.
  • 해결: mutationFn의 parameter, return type을 명시한다.
//useInput.ts

interface Todo {
  isDone: boolean;
  todo: string;
}

const mutationAdd = useMutation({
  mutationFn: async (newTodo: Todo) => {
    await axios.post(`${SERVER_URL}/todos`, newTodo);
  },
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['todos'] });
  },
});

Reference
How to Migrate a React App to TypeScript - SitePoint
GitHub - microsoft/TypeScript-React-Conversion-Guide

profile
Software Engineer 🍊

0개의 댓글