Viblo Code
+10

Cài đặt và code theo kiểu TypeScript mang lại điều gì cho dự án Nuxt.js?

Typescript là gì ?

TypeScript là một ngôn ngữ mã nguồn mở miễn phí hiện đang được phát triển và bảo trì bởi Microsoft (theo wikipedia). Người ta thường biết đến typescript và sử dụng nó trong các ngôn ngữ như C sharp, Angular hay Node.js. Vì TypeScript là tập cha của JavaScript nên bất kì chương trình JavaScript nào đã có cũng đều là chương trình TypeScript hợp lệ. Chính vì vậy chúng ta hoàn toàn có thể áp dụng TypeScript vào project vuejs.

Vì sao lại sử dụng TypeScript?

  • Vấn đề về dự án: Việc phát triển các dự án có dễ dàng hay không cũng phụ thuộc khá nhiều vào bản thân người lập trình, nó phần lớn cũng phụ thuộc vào yếu tố khách quan nữa. Tuy nhiên, theo quan điểm của mình thì áp dụng được kỹ thuật mới và đặc biệt kỹ thuật lập trình hướng đối tượng như TypeScript làm việc phát triển các dự án lớn được dễ dàng, và việc code cũng hợp lý đúng không nào :<.
  • Việc áp dụng TypeSCript có hỗ trợ nhiều Framework dễ dàng cho việc lựa chọn: Ví dụ như AngularJS 2.0
  • Hỗ trợ các tính năng của Javascript phiên bản mới nhất: Hiện tại nó cũng hỗ trợ đến version mới của javascript là ECMAScript 2015 (ES6).
  • Bên cạnh việc không bị tính phí, TypeScript dần trở nên phổ biến và được khá nhiều người dùng sử dụng. Chính vì vậy cộng đồng phát triển TypeScript ngày càng lớn, đã được hỗ trợ dễ dàng hơn.
  • TypeScript là Javascript: Bản chất của TypeScript là biên dịch tạo ra các đoạn mã javascript nên bạn có thế chạy bất kì ở đâu miễn ở đó có hỗ trợ biên dịch Javascript. Ngoài ra bạn có thể sử dụng trộn lẫn cú pháp của Javascript vào bên trong TypeScript, điều này giúp các lập trình viên tiếp cận TypeScript dễ dàng hơn.

1. Cài đặt Typescript

Đầu tiên thì ta cần cài đặt project Nuxt.js như sau:

  • Cài đặt sử dụng npx: npx create-nuxt-app nuxt-ts-project

  • Cài đặt sử dụng npm: npm init [email protected] nuxt-ts-project

  • Cài đặt sử dụng yarn: yarn create nuxt-app nuxt-ts-project

Sau khi cài đặt xong, hãy mở folder project lên thôi nào...

cd nuxt-ts-project
//Cài đặt nuxt typescript build
npm install --save-dev @nuxt/typescript-build

2. Cấu hình Typescript trong project

Thêm @nuxt/typescript-build vào buildModules trong nuxt.config.js:

export default {
  buildModules: ['@nuxt/typescript-build']
}

Tạo file tsconfig.json và thêm các tùy chọn như bên dưới:

{
  "compilerOptions": {
    "target": "es2018",
    "module": "esnext",
    "moduleResolution": "node",
    "lib": [
      "esnext",
      "esnext.asynciterable",
      "dom"
    ],
    "esModuleInterop": true,
    "allowJs": true,
    "sourceMap": true,
    "strict": true,
    "noEmit": true,
    "baseUrl": ".",
    "paths": {
      "~/*": [
        "./*"
      ],
      "@/*": [
        "./*"
      ]
    },
    "types": [
      "@types/node",
      "@nuxt/types"
    ]
  },
  "exclude": [
    "node_modules"
  ]
}

Rồi thì tạo file vue-shim.d.ts:

declare module "*.vue" {
  import Vue from 'vue'
  export default Vue
}

Sau đó bạn cần cài eslint cho TypeScript. Nếu trước đó đã cài eslint cho Javascript rồi thì xóa nó đi nhé rồi cài lại

npm remove @nuxtjs/eslint-config
npm i -D @nuxtjs/eslint-config-typescript

Hãy sửa lại một chút file .eslinttrc:

{
  "extends": [
    "@nuxtjs/eslint-config-typescript"
  ]
}

Và update lại script:

"lint": "eslint --ext .ts,.js,.vue ."

3. Code theo phong cách TypeScript

1. Options API (vanilla)

Chúng ta có thể viết Typescript theo các này để không phải thay đổi code Javascript quá nhiều. Cú pháp này sẽ trông giống cách viết code mà chúng ta thường dùng.

<template>
  <div class="container">
    <p>Project: {{ project }}</p>
    <div>Calculate Age:</div>
    <input v-model="year" type="number" />
    {{ text }}
  </div>
</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
  data() {
    return {
      project: 'Viblo',
      year: null,
      text: 'May Fest'
    }
  },
  computed: {
    project(): string {
      return this.project
    }
  },
  watch: {
    year(newVal: number) {
      this.text = this.calculate(newVal)
    }
  },
  methods: {
    calculate(newVal: number): string {
      return 'May Fest:' + newVal
    }
  }
})
</script>

2. Vuex typing (vanilla)

import { GetterTree, ActionTree, MutationTree } from 'vuex'
export const state = () => ({
  count: 0 as number
})
export type RootState = ReturnType<typeof state>
export const getters: GetterTree<RootState, RootState> = {
  count: state => state.count
}
export const mutations: MutationTree<RootState> = {
  CHANGE_COUNT: (state, newVal: number) => (state.count = newVal)
}
export const actions: ActionTree<RootState, RootState> = {
  updateCount({ commit }, newVal) {
    // Some async code
    commit('CHANGE_COUNT', newVal)
  }
}

3. Class-based API

Cài đặt thư viện:

npm install --save nuxt-property-decorator

Khởi tạo một class:

Với cách code Javascript thông thường:

<script>
    export default {
        name: 'MayFest'
    }
</script>

Ta có thể thay đổi chút xíu theo kiểu Typescript:

<script lang="ts">
    import { Component, Vue } from 'nuxt-property-decorator'
    @Component
    export default class MayFest extends Vue {
    }
</script>

Thuộc tính lang="ts" là bắt buộc cho thẻ script để sử dụng TypeScript.

Import một component

Thông thường sẽ được viết với:

<script>
    import Header from '@/components/Header.vue'
    export default {
      name: 'MayFest',
      components: {
        Header
      }
    }
</script>

Để đăng ký component trong một component khác sử dụng @Component của nuxt-property-decorator:

<script lang="ts">
import Header from '@/components/Header.vue'
import { Vue, Component } from 'nuxt-property-decorator'
@Component({
  components: {
    Header
  }
})
export default class MayFest extends Vue {}
</script>

Data

Thông thường sẽ được viết với:

export default {
  title: 'Post'
  list: [
    {
      name: 'Có gì mới trong phiên bản NextJS 10',
      link: 'https://viblo.asia/p/co-gi-moi-trong-phien-ban-nextjs-10-gGJ59MRx5X2',
    },
    {
      name: 'Tailwind CSS v2 có gì mới?',
      link: 'https://viblo.asia/p/tailwind-css-v2-co-gi-moi-6J3ZgNMMKmB',
    }
  ]
}

Viết theo kiểu Typescript

export default class MayFest extends Vue {
  title: string = 'Post'
  list: Array<object> = [
    {
      name: 'Có gì mới trong phiên bản NextJS 10',
      link: 'https://viblo.asia/p/co-gi-moi-trong-phien-ban-nextjs-10-gGJ59MRx5X2',
    },
    {
      name: 'Tailwind CSS v2 có gì mới?',
      link: 'https://viblo.asia/p/tailwind-css-v2-co-gi-moi-6J3ZgNMMKmB',
    }
  ]
}

Props

Thông thường sẽ được viết với:

export default {
  props: {
   item: {
      required: true  
  },
   quantity,
   brand: {
      default: 'Apple',
    },
   type: {
      type: String
    },
   stock: {
      required: false,
      type: string,
      default: 'Available'
    }
  }
}

Viết theo Typescript

import { Component, Prop, Vue } from 'nuxt-property-decorator'
@Component
export default class Tile extends Vue {
  @Prop({ required: true }) readonly item!: object
  @Prop() quantity!: number
  @Prop({ default: 'Apple' }) brand!: string
  @Prop(String) readonly type!: string
  @Prop({ required: false, type: String, default: 'Available' })
  readonly stock!: string
}

Computed properties

Cách viết thông thường:

export default {
  buttonText() {
   if (this.quantity) {
      return 'Buy Now!'
    } else {
      return 'Coming Soon!'
    }
  }
}

Viết theo kiểu Typescript:

export default class Tile extends Vue {
  get buttonText(): string {
    if (this.quantity) {
      return 'Buy Now!'
    } else {
      return 'Coming Soon!'
    }
  }
}

Sử dụng gettersetter Viết code thông thường:

searchText: {
  get: function () {
    return this.searchTextValue
  },
  set: function (val) {
    this.searchTextValue = val
  }
}

Viết code Typescript:

export default class MayFest extends Vue {
 get searchText() {
    return this.searchTextValue
  }
  set searchText(val) {
    this.searchTextValue = val
  }

Methods

Viết code thông thường:

export default {
  data() {
    return {
      laptopPrice: 1400
        quantity: 0
    }
  }
  methods: {
    calculateTotal() {
      return this.laptopPrice * this.quantity
    }
  }
}

Viết code Typescript:

import { Vue, Component } from 'nuxt-property-decorator'
@Component
export default class Laptop extends Vue {
  laptopPrice: number = 1400
  quantity: number = 0
  calculateTotal(): number {
    return this.laptopPrice * this.quantity
  }
}

Watcher

Watcher có nhiều cách viết, ví dụ chúng ta hay viết kiểu:

watch: {
  total: function(newval) {
    //do something
  }
}

và cách viết ít được sử dụng:

watch: {
  total: {
    handler: 'totalChanged'
  }
}
methods: {
  totalChanged(newVal) {
    // do something
  }
}

Tuy nhiên, viết theo kiểu Typescript sẽ giống với cách viết thứ 2 và sử dụng @watch để truyền tên biến mà bạn cần theo dõi (watch).

@Watch('name')
totalChanged(newVal: string) {
  if(newVal > 20000) {
    this.status = 'limit exceeded for user'
  }
}

Chúng ta cũng thiết lập immediatedeep cho watchers:

@Watch('itemList', { 
  immediate: true, deep: true 
})
itemChanged(newVal: Product, oldVal: Product) {
  // do something
}

Có khác đôi chút so với cách viết JS thông thường:

watch: {
  itemList: {
      handler: 'itemChanged',
      immediate: true,
      deep: true
    }
}
methods: {
  itemChanged(newVal, oldVal) {
    // do something
  }
}

Emit

Viết code thông thường:

<some-component add-to-count="someMethod" />
<some-component reset-data="someMethod" />


//Javascript Equivalent
 methods: {
    addToCount(n) {
      this.count += n
      this.$emit('add-to-count', n)
    },
    resetCount() {
      this.count = 0
      this.$emit('resetData')
    }
}

và theo cách viết của Typescript

@Emit()
addToCount(n: number) {
  this.count += n
}
@Emit('resetData')
resetCount() {
  this.count = 0
}

Lifecycle hooks

Cách viết thông thường

export default {
  asyncData() {
    //do something
  }
  beforeUpdate() {
    // do something
  }
}

Do không có đối số nào nên cách viết không hề thay đổi

export default class MayFest extends Vue {
  asyncData() {
    //do something
  }
  beforeUpdate() {
    // do something
  }
}

Mixins

Cách viết thông thường là:

export default {
  data() {
    return {
      cartProducts: []
    }
  },
  methods: {
    addToCart(newItem) {
     this.cartProducts = { ...this.cartProducts, ...newItem }
    }
  }
}

Tạo một file CartMixin.ts trong folder mixín:

/mixins/CartMixin.ts
import { Component, Vue } from 'nuxt-property-decorator'
@Component
class CartMixin extends Vue {
  public cartProducts: Array<object> = []
  public addToCart(newItem: object): void {
    this.cartProducts = { ...this.cartProducts, ...newItem }
  }
}
export default CartMixin

Sử dụng mixin với JS thông thường:

<template>
  <div class="phones">
    <div class="item">
      <img src="@/assets/images/iphone-11.png" />
      <div>iphone 11</div>
      <button @click="add">Add to Cart</button>
    </div>
    <div class="cart">
      <div v-for="(item, i) in cartProducts" :key="i" class="item">
        <div>Item: {{ item.name}}</div>
        <div>Quantity: {{ item.quantity }}</div>
      </div>
    </div>
  </div>
</template>
<script>
import CartMixin from '@/mixins/CartMixin'
export default {
  mixins: [ CartMixin],
  methods: {
     public add() {
      this.addToCart({ name: 'phone', quantity: 1 })
    }
  }
}
</script>

Sử dụng với Typescript:

<template>
  <div class="phones">
    <div class="item">
      <img src="@/assets/images/iphone-11.png" />
      <div>iphone 11</div>
      <button @click="add">Add to Cart</button>
    </div>
    <div class="cart">
      <div v-for="(item, i) in cartProducts" :key="i" class="item">
        <div>Item: {{ item.name}}</div>
        <div>Quantity: {{ item.quantity }}</div>
      </div>
    </div>
  </div>
</template>
<script lang="ts">
import { Vue, Component, mixins } from 'nuxt-property-decorator'
import CartMixin from '@/mixins/CartMixin'
@Component
export default class Phones extends mixins(CartMixin) {
  public add() {
    this.addToCart({ name: 'phone', quantity: 1 })
  }
}
</script>

Vuex

Viết thông thường:

export default {
  namespaced: true,
  state: {
    info: {
      first: 'Preetish',
      last: 'HS',
      address1: '',
      address2: '',
      state: '',
      country: '',
      phone: 9000000009
    }
  },
  getters: {
    fullName() {
      return this.info.first + ' ' + this.info.last
    }
  }
  mutations: {
    updateUserInfo(data) {
      this.info = { ...this.info, ...data }
    }
  }
}

Sử dụng Typescript: Cài đặt vuex-module-decorators

npm install -D vuex-module-decorators

Sau đó tạo file users.ts trong store

import { Module, VuexModule, Mutation } from 'vuex-module-decorators'
interface UserData {
  first: string
  last: string
  address1: string
  address2: string
  state: string
  country: string
  phone: number
}
@Module({
  name: 'user',
  stateFactory: true,
  namespaced: true
})
export default class User extends VuexModule {
  public info: UserData = {
    first: 'Preetish',
    last: 'HS',
    address1: '',
    address2: '',
    state: '',
    country: '',
    phone: 9000000009
  }
  get fullName(): string {
    return this.info.first + ' ' + this.info.last
  }
  @Mutation
  public updateUserInfo(data: UserData) {
    this.info = { ...this.info, ...data }
  }
}

Sử dụng vuex trong components

Viết JS thông thường:

<script>
import { mapState, mapGetters, mapMutations } from 'vuex'
export default {
  data() {
    return {
      localData: {}
    }
  },
  computed: {
    ...mapState('user', ['info']),
    ...mapGetters('user', ['fullName'])
  },
  mounted() {
    this.localData = { ...this.localData, ...this.info }
  },
  methods: {
    ...mapMutations('user', ['updateUserInfo']),
    update() {
      this.updateUserInfo(this.localData)
    }
  }
}
</script>

Sử dụng Typescript:

<template>
  <div class="user">
    <div class="title">Welcome {{ fullName }}</div>
    <div>
      First:
      <input type="text" v-model="localData.first" />
    </div>
    <button @click="update">Update Info</button>
  </div>
</template>
<script lang="ts">
import { Vue, Component, namespace } from 'nuxt-property-decorator'
const user = namespace('user')
@Component
export default class User extends Vue {
  public localData: object = {}
  @user.State
  public info!: object
  @user.Getter
  public fullName!: string
  @user.Mutation
  public updateUserInfo!: (data: object) => void
  mounted() {
    this.localData = { ...this.localData, ...this.info }
  }
  public update(): void {
    this.updateUserInfo(this.localData)
  }
}
</script>

4. Tạm kết

Việc áp dụng TypeScript vào Vuejs cũng khá đơn giản đúng không nào. Ban đầu, các bạn sẽ thấy việc áp dụng này khiến chúng ta phải code phức tạp hơn, code nhiều file hơn, hay thậm chí là dài hơn chẳng hạn. Tuy nhiên, với những lợi ích mà nó mang lại thì không thể phủ nhận được. Nếu bạn là một người yêu thích tìm hiểu cái mới và không ngại thử thách, hãy thử áp dụng ngay nhé. Thú vị lắm đấy 😉


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.