[FE] 자바스크립트 프로젝트에 타입스크립트 적용하기 - feat. kanban board

이정훈·2022년 2월 7일
5
post-thumbnail
post-custom-banner

새로운 기술을 학습할 때, 가장 좋은 방법은 기존에 진행했던 프로젝트에 기술을 적용해보거나 기술로 새롭게 프로젝트에 적용해보는 것이라고 생각한다

최근에 타입스크립트에 관심이 생겨 학습을 진행했고, 이에 대한 결과물로 기존에 내가 바닐라 자바스크립트로 진행한 프로젝트에 타입스크립트를 적용해서 변환하는 과정을 글에 녹여내고자 한다 🙂



프로젝트 환경 구성

개인적으로 프로젝트를 진행하면서 어려운 일 중 하나는 환경설정을 세팅하는 것이라고 생각한다

처음에 설정 코드나 라이브러리가 어떤 역할을 하는지 제대로 파악하지 못하고 마구잡이식으로 사용하다 보면 설정이 얽혀서 문제가 발생했을 때 해결하기에 더욱 어려워질 수 있고, 수정이 두려워지기 때문이다

내가 한 환경 구성이 누군가에게 도움이 될 수 있다면 좋을 것 같다 😀



기존 프로젝트 디렉토리

JS 파일은 엔트리 파일이자 컨트롤러 역할을 하는 app.js, 상태를 관리하고 변경 메서드를 제공하는 store.js, 화면에 그리는 역할을 담당하는 view.js, 상수와 그 외 헬퍼 함수를 작성한 utils 폴더로 구분하였다

웹팩은 bundle, devserver 버전으로 나누어 설정 파일을 관리했다



필요한 의존성 설치

package.json

{
  "name": "kanban-board",
  "version": "1.0.0",
  "scripts": {
    "start": "npm run serve -- --open",
    "serve": "webpack serve --node-env development --config webpack/config.server.js",
    "bundle": "webpack --node-env development --config webpack/config.dev.js"
  },
  "devDependencies": {
    "@babel/core": "^7.17.0",
    "@babel/preset-env": "^7.16.11",
    "@babel/preset-typescript": "^7.16.7",
    "@typescript-eslint/eslint-plugin": "^5.10.2",
    "@typescript-eslint/parser": "^5.10.2",
    "css-loader": "^6.5.1",
    "eslint": "^8.8.0",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-prettier": "^4.0.0",
    "prettier": "^2.5.1",
    "style-loader": "^3.3.1",
    "ts-loader": "^9.2.6",
    "typescript": "^4.5.5",
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1",
    "webpack-dev-server": "^4.7.1"
  }
}
  • 구형 브라우저에서 동작을 위한 babel과 문법 검사와 코드 통일 및 가독성을 위해 eslint와 prettier를 설치하였다
  • 웹팩의 모듈과 devserver를 사용하기 위해 웹팩을 설치하였고, JS 파일에서 CSS 파일을 읽어들이고 해석하기 위해 style-loader와 css-loader를 설치하였다
  • 설치 방법은 package.json 파일에 devDependencies의 내용을 추가하고 npm i 명령어로 설치하는 것이 가장 간단하다


eslint + prettier + typescript 설정

.eslintrc.js

module.exports = {
  root: true,
  env: {
    browser: true,
    node: true,
  },
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/eslint-recommended',
    'plugin:@typescript-eslint/recommended',
  ],
  plugins: ['prettier', '@typescript-eslint'],
  rules: {
    'prettier/prettier': [
      'error',
      {
        singleQuote: true,
        semi: true,
        useTabs: false,
        tabWidth: 2,
        printWidth: 100,
        bracketSpacing: true,
        arrowParens: 'avoid',
      },
    ],
  },
  parserOptions: {
    parser: '@typescript-eslint/parser',
  },
};
  • eslint 설정은 기본 eslint와 prettier 설정에 typescript도 eslint로 검사하기 위한 플러그인과 옵션들을 추가하였다


웹팩 설정

webpack/dev.js

/* eslint-disable @typescript-eslint/no-var-requires */
const path = require('path');
const __root = process.cwd();

const devConfig = {
  target: ['web'],
  mode: 'development',
  devtool: 'source-map',
  entry: './src/js/app.ts',
  output: {
    path: path.resolve(__root, 'dist'),
    filename: 'js/[name].js',
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: 'ts-loader',
      },
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
  resolve: {
    extensions: ['.ts', '.js'],
  },
};

module.exports = devConfig;
  • entry 속성은 어떤 파일을 entry 진입 파일로 설정할 것인지 지정하는 속성이다. 기존에 js 확장자로 되어있던 app.js 파일명을 app.ts로 변경하고 설정 파일의 값도 수정하였다
  • rules 속성에 ts 확장자명을 가진 파일은 ts-loader로 컴파일 해주겠다는 옵션을 추가하였다
  • resolve 옵션은 import 시, ts 파일도 확장자명을 붙이지 않고 사용하기 위해 추가한 옵션이다


타입스크립트 설정 파일

tsconfig.json

{
  "compilerOptions": {
    "allowJs": true,
    "sourceMap": true,
    "target": "ES5",
    "module": "ES2015",
    "outDir": "./dist",
    "lib": ["ES2015", "DOM", "DOM.Iterable"],
    "noImplicitAny": true,
    "downlevelIteration": true,
  },
}
  • allowJs는 JS 파일도 허용하겠다는 속성이다
  • sourceMap을 true로 설정해야 정확한 코드 위치를 매핑하여 console에서 확인할 수 있었다
  • target 속성은 컴파일 할 결과물의 버전을 설정한다
  • lib 속성은 DOM 타입을 인식시켜주기 위해 설정했다
  • noImplicitAny 옵션은 타입스크립트는 타입 추론이 안되는 경우 암묵적으로 any로 타입을 추론하기 때문에 이를 방지하고, 타입 추론이 되지 않는 경우 개발자가 명시적으로라도 any 타입을 지정해줘야 하는 옵션이다
  • downlevelIteration 옵션은 타겟이 ES3이나 ES5여도 스프레드 연산자 등의 문법을 작성하겠다는 옵션



점진적인 적용

타입스크립트를 적용하면서 세운 규칙은 2가지이다

  1. 기능적인 변경은 하지 않을 것
  2. 처음부터 타입을 엄격하게 적용하지 않고, 점진적으로 구체적인 타입을 적용할 것

tsconfig.json파일에 "noImplicitAny": true 옵션을 추가하고, js 확장자명을 가진 파일을 ts 파일로 바꾸니 수많은 에러가 검출되었다

우선 이를 타입스크립트 환경에서도 문제가 발생하지 않도록 하기 위해 any 타입을 활용하여 에러를 해결하였다

ts 파일로 바꾸고 난 후 발생한 수많은 에러들을 any 타입을 이용해서 해결하고, 정상적으로 코드가 동작하는 것을 확인하였다

다음 단계는 any 타입으로 정의한 타입들을 좀 더 구체적으로 변경하는 작업이었다


// types/Issue/index.ts
export interface DragIssue {
  $el: HTMLLIElement;
  index: number;
  boardType: string;
}

export interface Issue {
  id: string;
  regDate: string;
  title: string;
  writer: string;
}

export interface UpdateIssue {
  id: string;
  type: string;
  title: string;
  writer: string;
}

export interface Issues {
  [key: string]: Issue[];
}

export interface IssuesInfo {
  idNumber: number;
  issues: Issues;
}

export interface DragDropInfo {
  dragIndex: number;
  dragBoardType: string;
  dropIndex: number;
  dropBoardType: string;
}

export interface DropInfo {
  $drop: HTMLLIElement;
  $ul: HTMLUListElement;
  $el: HTMLLIElement;
  dragIndexOfTargetList: number;
  dropIndex: number;
}

위의 코드와 같이 객체의 경우 interface로 정의하여 매개변수로 받을 타입을 지정하고, 원시값을 넘겨받는 경우에는 string, number, 유니온 타입을 적절히 사용하여 전체 파일을 타입스크립트 파일로 변경하였다


그 다음 단계로는 tsconfig.json 파일에서 strict 옵션을 true로 변경하였다

{
  "compilerOptions": {
    ...
    "strict": true
  },
}

이렇게 옵션을 설정하면 기존보다 더 엄격하게 타입을 검사하고 예기치 못한 에러를 발생시키지 않도록 검사한다

대부분의 발생한 오류의 경우 DOM이나 타입이 null일 수도 있는 경우 발생하는 타입 에러였다

이를 해결하기 위해서 옵셔널 체이닝 연산자, if 문을 사용한 타입 가드, 타입 단언 등을 사용하여 문제를 해결했다




진행하며 느낀 점

점진적으로 타입스크립트의 타입을 구체화하며 기존에 바닐라 자바스크립트로 동작되던 프로젝트를 타입스크립트 환경에서도 동작하도록 변환하는 데 성공하였다

REST API 요청을 하지 않고, 작은 규모의 프로젝트임에도 불구하고 예상한 것보다 변환하는 데 시간이 소요되었다. 직접 타입스크립트를 공부하고 프로젝트를 진행하기 전까지는 단순히 타이핑을 도와주는 도구라고 생각하여 쉬울 것이라고 생각했었는데, 직접 사용해보니 잘 사용하기 위해서는 추가적인 학습과 시간이 필요할 것 같다는 생각이 들었다

앞으로는 두 가지 방향으로 타입스크립트 학습을 이어나가고자 한다

  1. 타입스크립트를 어떻게 더 잘 활용할 수 있을지 책과 공식 문서를 참고하고, 스터디와 실습을 통해 타입스크립트로 코드를 작성하는 데 익숙해지기
  2. 타입스크립트를 프레임워크와 함께 사용하는 것에 대해 학습하고, 기존에 프레임워크를 사용해 진행했던 프로젝트를 타입스크립트 방식으로 변환하기
post-custom-banner

2개의 댓글

comment-user-thumbnail
2022년 2월 14일

좋은글 보고갑니다~

답글 달기
comment-user-thumbnail
2023년 9월 1일

좋은 글이여요 . ㅎ

답글 달기