Published on

Vue에 Typescript 적용하기

Vue에 Typescript를 적용하는 방법 두가지

  • Vue.extend를 사용한 객체형태
  • Decorator(@)를 사용한 Class 기반 컴포넌트

그리고 Typescript 적용시 겪는 이슈들에 대하여 정리하였다.

Vue.extend 를 사용한 객체형태

  • Typescript를 적용하여 Vue 컴포넌트 작성시 Vue.extend()를 선언해줘야 Typescript 코드가 Vue의 속성들과 함께 동작한다.
    그리고 Webpack으로 빌드 할때 vue-loader가 Typescript를 컴파일하기 위해서 <script lang="ts"> 로 선언해야 한다.
<template>
  <div>
    {{ result }}
  </div>
</template>

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  data() {
    return {
      result: '',
    }
  },
  methods: {
    sayHi(someone: string) {
      this.result = 'hello ' + someone
    },
  },
  created() {
    this.sayHi('hi')
  },
})
</script>
  • 하지만 이 방법은 Typescript에서 객체 형태로 컴포넌트를 정의할 때 props 의 타입으로 Typescript의 타입을 사용할 수 없다는 단점을 가지고 있다.
    Typescript에서 아래 코드는 타입으로 사용되야 하는 Todo가 값으로 사용되고 있다고 에러를 출력한다.
    즉 type: Todo[] 은 타입을 선언한 것이 아니고 type 에 Todo[] 라는 값을 할당한 것이다.
interface Todo {
  title: string;
}

export default Vue.extend({
  props: {
    todos: {
      type: Todo[],
      required: true,
      default: []
    }
  },
  //...

Decorator(@)를 사용한 Class 기반 컴포넌트

  • Vue는 컴포넌트를 컴포넌트 생성 옵션 객체로 정의하는 방법을 기반으로 디자인되었기 때문에, Vue.extend와 객체를 이용하는 방법이 적합하다.
    하지만 현재 위에서 본 Typescript 와의 조합시 문제를 가지고 있기 때문에, Decorator(@)를 활용한 Class 기반 컴포넌트가 현재의 대안이며 Typescript의 장점을 더 살릴 수 있다.
<template>...</template>
<script lang="ts">
    import { Component, Prop, Vue } from 'vue-property-decorator';
    import { modal_dialog, checkbox } from './../../uikit';
    import { Check, Schedule } from './interface';

    @Component({
        components: {
            modal_dialog,
            checkbox
        }
    })
    export default class ScheduleDialog extends Vue {
        @Prop() title!: string;
        @Prop() pCheckList!: Check[];
        @Prop({default: 'normal'}) theme!: string;

        public checkList: Check[] = [];

        get schedule(): Schedule {
            return this.$store.state.schedule;
        }

        private created() {
            ...
        }

        private beforeDestroy() {
            ...
        }

        public onAddName(name: string) {
            this.$store.dispatch('addName', name);
        }
    }
</script>
<style lang="less">
    @import './../../uikit/common';

    .schedule_dialog {
    }
</style>

Typescript 적용 시 해줘야 할 일들

  • webpack.config.js 에서 ts-loader 설정을 한다.
module.exports = (env) => {
  return {
    module: {
      rules: [
        //...
        {
          test: /\.vue$/,
          loader: 'vue-loader',
          options: {
            loaders: {
              less: 'vue-style-loader!css-loader!less-loader',
            },
          },
        },
        {
          test: /\.tsx?$/,
          loader: 'ts-loader',
          exclude: /node_modules/,
          options: {
            appendTsSuffixTo: [/\.vue$/],
          },
        },
      ],
    },
    resolve: {
      extensions: ['.ts', '.js', '.vue', '.json'],
      alias: {
        '@': resolve('src'),
      },
    },
    //...
  };
};
  • Typescript에서 .vue 파일을 import 할때 발생하는 에러 수정을 위해 vue-shims.d.ts 파일을 작성한다.
    이는 Typescript에게 .vue 파일들이 Vue constructor와 같은 type이라고 선언한다.
// src/vue-shims.d.ts
declare module '*.vue' {
  import Vue from 'vue';
  export default Vue;
}
  • vue-loader 버전에 따라, 위에서 vue-shims.d.ts 파일 작성후에도 Typescript가 *.vue 파일을 찾지 못하는 에러가 발생할 수 있다.
    vue-loader를 "^14.2.2" 에서 "^15.2.4" 버전으로 올려서 해결하였다.
    버전을 올리고 webpack.config.js에서 vue-loader plugin을 추가해주어야 한다.
// package.json
"dependencies": {
  "ts-loader": "^6.2.1",
  "tslint": "^5.20.1",
  "typescript": "^3.1.3",
},
"devDependencies": {
  "vue-loader": "^15.2.4",
}
// webpack.config.js
const VueLoaderPlugin = require('vue-loader/lib/plugin');
//...

module.exports = (env) => {
  return {
    //...
    plugins: [new VueLoaderPlugin()],
    //...
  };
};
  • Webstorm에서 .vue 파일의 TSLint 지원은 2019.1 build 버전부터 제대로 동작한다.
// tslint.json
{
  "defaultSeverity": "error",
  "extends": ["tslint:recommended"],
  "jsRules": {},
  "rules": {
    "quotemark": [true, "single", "avoid-escape"],
    "max-line-length": false,
    "object-literal-sort-keys": false,
    "semicolon": false,
    "trailing-comma": false,
    "ordered-imports": false,
    "no-console": false,
    "one-variable-per-declaration": false,
    "member-access": false,
    "member-ordering": false,
    "eofline": false,
    "object-literal-shorthand": false
  },
  "rulesDirectory": []
}
  • tsconfig.json 설정
{
  "compilerOptions": {
    "outDir": "./built/",
    "sourceMap": true,
    "strict": true,
    "module": "es2015",
    "moduleResolution": "node",
    "target": "es5",
    "experimentalDecorators": true,
    "noImplicitThis": false,
    "resolveJsonModule": true,
    "allowSyntheticDefaultImports": true
  },
  "include": ["./src/**/*"]
}

참조