มือใหม่ Nuxt.js + TypeScript pt.3 การใช้ Vuex ร่วมกับ TypeScript เบื้องต้น

แนวทางการพัฒนาเว็บแอปพลิเคชั่นด้วย Nuxt.js + Typescript สำหรับมือใหม่

สวัสดีครับวันนี้เราจะมาใช้งาน type script ร่วมกับ state management คู่ใจของ vue.js หรือ vuex กันครับ

บทความนี้ไม่มีส่วนของพื้นฐานการใช้ type script หากใครยังไม่เข้าใจ concept สามารถศึกษาได้จากบทแรกได้ครับ https://mnagon.gitbook.io/blog/article/nuxt-typescript-pt1

บทความนี้ไม่มีส่วนของพื้นฐานการใช้ vue.js และ vuex นะครับ หากใครยังไม่คุ้นเคยกับการใช้งาน vuex สามารถศึกษาได้จากลิ้งนี้ครับ https://vuex.vuejs.org/#what-is-a-state-management-pattern

ก่อนอื่นให้เราสร้าง nuxt project ตามปกติเลยครับ ที่สำคัญอย่าลืมเลือก programming language ให้เป็น type script ก็พอครับ

สร้าง nuxt project โดยเลือก type script เป็น programming language

ข้อดีของการใช้ nuxt.js ก็คือ เขาได้ติดตั้ง vuex ให้เราเรียบร้อยโดยที่เราไม่ต้องติดตั้งอะไรเพิ่มเติมอีกเลย อีกทั้งยังสร้าง folder store ให้เราเรียบร้อยแล้วดังรูปเลยครับ

Nuxt ติดตั้ง Vuex ให้อยู่แล้วพร้อมทั้งสร้าง Folder store ให้เรียบร้อย

จริง ๆ แล้วเราสามารถเขียน vuex ให้เป็น type script ได้หลากหลายหนทางมาก ๆ ครับ แต่สำหรับบทความของผมอยากเขียน vuex ให้ใกล้เคียง vanilla vuex ที่สุดครับ เพราะเข้าใจได้ง่ายกว่าและเรียนรู้ได้ง่ายกว่าครับ

ติดตั้ง Package เสริม

Package ที่จะช่วยให้เราเขียน vuex ในรูปแบบ type script ได้ในครั้งนี้คือ nuxt-typed-vuex ครับ ให้เราเปิด terminal แล้ว run command นี้ได้เลยครับ

//install ด้วย yarn
yarn add nuxt-typed-vuex

//install ด้วย npm
npm install nuxt-typed-vuex --save

เมื่อติดตั้งเสร็จเรียบร้อยแล้วให้ไปที่ไฟล์ nuxt.config.js เพื่อเพิ่ม 'nuxt-typed-vuex' เข้าไปใน buildModules ครับ เพิ่มตามด้านล้างเลยครับ

//nuxt.config.js
export default {

  ...

  buildModules: [
    'nuxt-typed-vuex',
    '@nuxt/typescript-build',
  ],
  
  ...
  
}

เรียบร้อยแล้วครับสำหรับการติดตั้ง package เสริม เดียวเราไปเริ่มเขียน vuex กันเลยครับ

เริ่มเขียน Vuex

สร้าง State

เรามาเริ่มสร้าง state กันเลยครับ ซึ้งเราจะใช้ concept เดียวกับ data ของ component เลยครับ คือเราจะสร้าง Interface ของ state ก่อน กลังจากนั้น state ของเราจะเป็น export function ที่ return state object ออกมาอีกทีหนึ่งเหมือนตัวอย่างนี้ครับ

//store/index.ts
interface State {
    firstName: string
    lastName: string
    age: number
}

export const state = ():State => ({
    firstName: 'Supreecha',
    lastName: 'Jaijumpa',
    age: 27,
})

สร้าง Accessor

Accessor คืออะไร? accessor คือ module ที่จะช่วยให้เราเข้าถึง vuex store ของเราได้นั้นเองครับ ซึ้งหลังจากนี้เราไม่ต้องคอย import vuex ในแต่หละ component และเรียกใช้ผ่าน $store อีกต่อไปแล้ว แต่สามารถเข้าถึง vuex store ผ่าน $accessor แทนครับ

เรามาเริ่มสร้าง accessor กันเลย โดยให้เรา import module getAccessorType จาก nuxt-typed-vuex เข้ามาก่อน หลังจากนั้นให้เรา export module getAccessorType แล้วจับ state ของเรายัดเข้าไปเหมือนในตัวอย่างนี้ครับ

//store/index.ts
import { getAccessorType } from "nuxt-typed-vuex"

...

export const accessorType = getAccessorType({
    state
})

พอเรา export accessorType เรียบร้อยแล้วให้เราสร้างไฟล์ชื่อว่า index.d.ts ใน root folder แล้วใส่โค้ดด้านล่างนี้เข้าไปเลยครับ

//index.d.ts
import { accessorType } from '~/store'

declare module 'vue/types/vue' {
  interface Vue {
    $accessor: typeof accessorType
  }
}

declare module '@nuxt/types' {
  interface NuxtAppOptions {
    $accessor: typeof accessorType
  }
}

แค่นี้ก็เรียบร้อยครับสำหรับการสร้าง accessor พร้อมที่จะนำไปใช้งานได้แล้วครับ

แสดงค่าใน State ผ่าน Component

เมื่อเราสร้าง accessor เรียบร้อยเราจะมาลองแสดงค่าของ state ผ่าน vue component กันครับ โดยให้เราไปที่ไฟล์ pages/index.vue แล้วแก้ไขในส่วนของ template เพื่อแสดงข้อมูลออกมาแบบนี้ครับ

//pages/index.vue
<template>
  <div>
    {{ $accessor.firstName }} {{ $accessor.lastName }} : {{ $accessor.age }}
  </div>
</template>

...

จะเห็นว่าเราสามารถเข้าถึงตัวแปรใน state ได้เลยโดยไม่ต้องพิมพ์ state เลย ซึ้ง $accessor.firstName ก็จะเหมือนกับ $store.state.firstName นั้นเองครับ

สร้าง Getters

ต่อไปเราจะมาสร้าง getters กันครับ ซึ้งเราต้อง import module มาช่วยชื่อว่า getterTree แล้วหลังจากนั้นเราจะ export getterTree ออกไปครับ ซึ้ง module getterTree จะรับ argument อยู่สองตัวครับ ตัวแรกคือ state ของเรา (หมายความว่าเราต้องสร้าง state ให้เสร็จก่อนสร้าง getter เพื่อนำมาเป็น argument ให้กับ module นี้ครับ) และตัวที่สองคือ object ที่บรรจุเหล่า getter function ของเราครับ พอสร้าง getter เสร็จแล้วก็อย่าลืมเอาไปยัดไว้ใน accessorType ของเราด้วยนะครับเพื่อจะได้เรียกผ่าน $accessor ของเราได้ เหมือนตัวอย่างด้านล่างเลยครับ

//store/index.ts
import { getAccessorType, getterTree } from "nuxt-typed-vuex"

...

export const getters = getterTree(state, {
    fullName: (state: State): string => state.firstName + ' ' + state.lastName,
    birthYear: (state: State): number => new Date().getFullYear() - state.age
})

export const accessorType = getAccessorType({
  state,
  getters,
})

เรียกใช้ Getters ผ่าน Component

เราสามารถเรียก getters ผ่าน $accessor ได้เหมือน state เลยครับ โดยเราทดสอบโดยแก้ไข template ของไฟล์ pages/index.vue ดังนี้ครับ

//pages/index.vue
<template>
  <div>
    <div>{{ $accessor.fullName }} birth year is {{ $accessor.birthYear }}</div>
  </div>
</template>

...

$accessor.fullName ก็จะเหมือนกับ $store.getters.fullName ครับ

สร้าง Mutation

สำหรับ mutation เราก็ต้อง import module เพิ่มเหมือนกันครับชื่อว่า mutationTree ครับ module ตัวนี้มีโครงสร้างเหมือนกับตัว getterTree เลยครับ เอาเป็นว่าดูตัวอย่างดีกว่าครับ 😅

import { getAccessorType, getterTree, mutationTree } from "nuxt-typed-vuex"

...

export const mutations = mutationTree(state, {
  SET_FIRST_NAME: (state: State, firstName: string): void => {
    state.firstName = firstName
  },
  SET_LAST_NAME: (state: State, lastName: string): void => {
    state.lastName = lastName
  },
  SET_AGE: (state: State, age: number): void => {
    state.age = age
  },
})

export const accessorType = getAccessorType({
  state,
  getters,
  mutations
})

(อย่าลืมจับ mutations ของเราใส่ accessorType นะครับ)

เรียกใช้ Mutations ผ่าน Component ?

การเรียกใช้ mutations ผ่าน component ดูเหมือนว่าจะไม่ใช้ best practice ที่ดีเท่าไหร่? แต่อยากจะให้เห็นว่าถ้าจะทำก็สามารถทำได้ครับ โดยได้ลองเพิ่ม mounted ให้กับ index.vue แบบนี้จะเห็นได้ว่าค่าใน firstName ใน state ของเราได้เปลี่ยนไปครับ

//pages.index.vue

...

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

export default Vue.extend({
  mounted(): void {
    this.$accessor.SET_FIRST_NAME('Mnagon')
  },
})
</script>

⚠️ สำหรับใครที่ใช้ Vue dev tool อยู่ จะรู้ว่าทุก ๆ การ mutation จะถูก recording และเราสามารถ tracking ตามดูการเปลี่ยนแปลงของ stateได้ แต่ทว่า nuxt-typed-vuex จะมีปัญหานิดหน่อยตรงที่ state ไม่ยอม update!! ทำให้เราตาม tracking ค่าต่าง ๆ ที่เปลี่ยนแปลงได้ยาก 😱 แต่อย่าพึ่งตกใจไปครับมันมีวิธีแก้อยู่ครับ ให้เราไปที่ Vue dev tool => Settings => New Vuex backend ครับแล้วให้ปรับเป็น Enable เพียงเท่านี้ state ของเราก็จะมีการ update ในทุก ๆ mutation แล้วครับ อย่าพึ่งรีบร้องไห้เสียใจ (เหมือนผม 😂 )

สร้าง Action

Module สำหรับ action คือ actionTree ครับ แต่ action จะติดต่อกับ module อื่น ๆ ใน store เยอะไม่ใช่แค่ state ดังนั้น actionTree module จึงต่างออกไปจาก getterTree และ mutationTree module นิดหน่อยครับ นั้นคือ argument แรกของ actionTree จะเป็น object ที่รวม module ทั้งหมดที่เราสร้างก่อนหน้านี้มาใช้ครับ ส่วน action function จะทำงานเหมือน vanilla vuex เลยครับ ไปดูตัวอย่างกันด้านล่างเลยครับ

import { getAccessorType, getterTree, mutationTree } from "nuxt-typed-vuex"

...

export const actions = actionTree(
  { state, mutations, getters },
  {
    changeName: ({ commit }, fullname: string): void => {
      const [firstname, lastName]: string[] = fullname.split(' ')
      commit('SET_FIRST_NAME', firstname)
      commit('SET_LAST_NAME', lastName)
    },
  }
)

export const accessorType = getAccessorType({
  state,
  getters,
  mutations,
  actions,
})

เรียกใช้ Action ผ่าน Component

//pages.index.vue

...

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

export default Vue.extend({
  mounted(): void {
    this.$accessor.changeName('Mnagon boy')
  },
})
</script>

ไม่มีอะไรมากครับ เรียก action function ผ่าน accessor ใน component ได้เลย

Module store

หากเราต้องการแยก store ออกเป็น module เพียงเราสร้างไฟล์ใหม่ ใน stores folder หลังจากนั้นก็สร้าง state, getters, mutations, actions ด้วยวิธีการเดียวกับด้านบนได้เลยครับ ตัวอย่างเช่น

//stores/email.ts
import { getterTree, mutationTree, actionTree } from 'nuxt-typed-vuex'

interface State {
  emails: string[]
}

export const state = (): State => ({
  emails: ['mail1@mail.com', 'mail2@mail.com'],
})

export const getters = getterTree(state, {
  emailsLenght: (state: State): number => state.emails.length,
})

export const mutations = mutationTree(state, {
  PUSH_EMAIL: (state: State, email: string): void => {
    state.emails.push(email)
  },
})

export const actions = actionTree(
  { state, mutations, getters },
  {
    addEmail: ({ commit }, email: string): void => {
      commit('PUSH_EMAIL', email)
    },
  }
)

หลังจากนั้นเราก็ import module store ของเราไปที่ไฟล์ store/index.ts แล้วจับจัดลง accessorType ในคีย์ชื่อ modules เหมือนตัวอย่างด้านล่างเลยครับ

//store/index.ts
import * as email from './email'

...

export const accessorType = getAccessorType({
  state,
  getters,
  mutations,
  actions,
  modules: {
    email,
  },
})

เพียงเท่านี้ module store ของเราก็พร้อมใช้งานแล้วครับ

เรียกใช้ store module ผ่าน component

สำหรับการเรียกใช้ store module เพียงเราจุดแล้วตามด้วยชื่อ module ข้างหลัง accessor ก็จะสามารถเข้าถึง store module ของเราได้แล้วครับเหมือนตัวอย่างด้านล่างเลย

//pages.index.vue
<template>
  <div>
    <span>
      {{ $accessor.fullName }} birth year is {{ $accessor.birthYear }}
    </span>
    <ul>
      <li v-for="email in $accessor.email.emails" :key="email">
        {{ email }}
      </li>
    </ul>
  </div>
</template>

จบแล้วครับ สำหรับพื้นฐานการใช้ type script ร่วมกับ vuxe ใน nuxt.js หวังว่าจะเป็นประโยชน์ ไม่มากก็น้อยสำหรับคนที่เริ่มจะศึกษานะครับ หากมีข้อผิดพลาดประการใดฝากขออภัยมา ณ ที่นี้ด้วยครับ ขอบคุณที่อ่านจนจบครับ 🙏

Last updated

Was this helpful?