Vue3 มีอะไรเปลี่ยนแปลงไปบ้าง

Vue 3 เพิ่งเปิดตัวมาเมื่อเดือนที่แล้ว ซึ่งมาพร้อมกับฟีเจอร์ใหม่ๆ และสิ่งที่เปลี่ยนแปลงไป เรามาดูกันดีกว่า

เขียนใหม่ด้วย TypeScript

ภาษา JavaScript นั้นไม่มี Type ของตัวแปรทำให้เวลาเขียนโปรแกรมมีโอกาสเกิดข้อผิลพลาดเยอะ ดังนั้นการเขียนงานโปรเจคใหญ่ๆ คนเลยนิยมเปลี่ยนไปใช้ TypeScript แทน (ถ้ายังไม่รู้จัก TypeScript อ่านต่อได้ที่นี่)

สำหรับ Vue 3.0 นี้ก็เป็นการเขียนใหม่ด้วย TypeScript แทน แต่เวลาเราเอามาใช้งาน เราสามารถเลือกได้ว่าจะใช้แบบ JavaScript ตามปกติ หรือจะใช้แบบ TypeScript ก็ได้

interface Book {
  title: string
  author: string
  year: number
}

const Component = defineComponent({
  data() {
    return {
      book: {
        title: 'Vue 3 Guide',
        author: 'Vue Team',
        year: 2020
      } as Book
    }
  }
})

อ่านเรื่อง TypeScript ต่อได้ที่นี่

JSX

เป็นฟีเจอร์ที่เอามาจาก React นั้นคือแทนที่จะใช้ Hyperscript แบบนี้

Vue.h(
  Vue.resolveComponent('anchored-heading'),
  {
    level: 1
  },
  [Vue.h('span', 'Hello'), ' world!']
)

Vue.h ย่อมาจาก Hyperscript ซึ่งหมายถึง script ที่เอาไว้สร้าง HTML structures นั่นเอง

การเขียน JSX ได้จะทำให้เราเขียน HTML ลงไปในโค้ด JavaScript ตรงๆ ได้เลย

import AnchoredHeading from './AnchoredHeading.vue'

const app = createApp({
  render() {
    return (
      <AnchoredHeading level={1}>
        <span>Hello</span> world!
      </AnchoredHeading>
    )
  }
})

app.mount('#demo')

อ่านเรื่อง JSX ต่อได้ที่นี่

API ที่เปลี่ยนไปใน Vue 3

ในหัวข้อนี้ขอยกตัวอย่างโค้ดข้างล่างนี่ ซึ่งรันได้ปกติใน Vue 2

<div id="app">
    <h1>{{ title }}</h1>
    <my-button @whenclick="changeTitleText"></my-button>
</div>

<script src="https://unpkg.com/vue@2.6.12"></script>
<script>
Vue.component('my-button', {
    template: `
        <button @click="handleClick">Click Me!</button>
    `,
    methods: {
        handleClick(){
            this.$emit('whenclick')
        }
    },
})

let app = new Vue({
    el: '#app',
    data: {
        title: 'นี่คือ Title แบบ Vue 2',
    },
    methods: {
        changeTitleText(){
            this.title = 'โค้ดนี้ทำงานได้ใน Vue 2'
        }
    },
})
</script>

See the Pen
Vue3-change-1
by Ta Tanapoj Chaivanichanan (@tanapoj)
on CodePen.

ในตัวอย่างนี้มีการสร้าง component ชื่อ my-button ขึ้นมาหนึ่งตัว แล้วตั้งค่าไว้ว่าถ้ามีการคลิก ให้เปลี่ยนข้อความ title ซะ

ทีนี้ ถ้าเราเปลี่ยนไปใช้ Vue 3 ตัวนี้แทน https://unpkg.com/vue@3.0.0 เราจะพบว่าโค้ดของเรารันไม่ได้ไปซะแล้ว

สาเหตุก็เพราะว่า API การเรียกใช้งานมีการเปลี่ยนแปลง

วิธี createApp

จากเดิมใน Vue 2 ถ้าเราจะสร้าง Application ขึ้นมา ก็จะใช้วิธีการ new Vue ขึ้นมาตรงๆ เลย

const app = new Vue({
    el: '#app',
    //TODO
})

แต่สำหรับ Vue 3 จะเปลี่ยนไปใช้คำสั่ง createApp แทน (ไม่สามารถสร้างเองได้แล้ว ต้องสร้างผ่าน factory function ที่ Vue เตรียมไว้ให้เท่านั้น)

const app = Vue.createApp({
    //TODO
})

app.mount('#app')

หรือ

import { createApp } from 'vue'

const app = createApp({
    //TODO
})

app.mount('#app')

อีกเรื่องหนึ่งคือการ mount แอพเข้ากับ html โดยใช้ el นั้นโดนตัดออกไปแล้ว

ไปใช้วิธีการ mount ด้วยเมธอด .mount() หลังจากสร้างแอพไปแล้วแทน ซึ่งเป็นการแยกส่วนโลจิคออกจากการกำหนดส่วนแสดงผล (HTML) ออกจากกัน ทำให้ตอนนี้เราสามารถสร้างแอพเปล่าๆ โดยยังไม่ต้อง mount ได้แล้ว ถ้าอยากเอาไปทำเทสเพิ่มก็ทำได้ง่ายขึ้น

data ต้องเป็น method เท่านั้น

ปกติ data ของ Vue 2 นั้นสามารถกำหนดเป็น object หรือ function ที่รีเทิร์น object กลับมาแบบไหนก็ได้

new Vue({
    data: {
        title: 'นี่คือ Title แบบ Vue 2',
    },
})

แต่สำหรับ Vue 3 ถูกกำหนดว่า data จะต้องเป็น function เท่านั้น

Vue.createApp({
    data(){
        return {
            title: 'นี่คือ Title แบบ Vue 2',
        }
    },
})

Component ผูกกับ app

ใน Vue 2 เวลาเราสร้างคอมโพเนนท์ เราจะสั่งผ่านตัวแปร Vue ที่เป็นระดับ global แปลว่าคอมโพเนนท์นี้สามารถเรียกใช้จาก Application ของ Vue ตัวไหนก็ได้

Vue.component('my-button', {
    template: `
        <button @click="handleClick">Click Me!</button>
    `,
    methods: {
        handleClick(){
            this.$emit('whenclick')
        }
    },
})

แต่สำหรับ Vue 3 แล้วการจะสร้างคอมโพเนนท์จะต้องสร้างให้แอพตัวใดตัวหนึ่งไปเลย

const app = Vue.createApp({
    //TODO
})

app.component('my-button', {
    emits: ['whenclick'], //optional
    template: `
        <button @click="handleClick">Click Me!</button>
    `,
    methods: {
        handleClick(){
            this.$emit('whenclick')
        }
    },
})

emits เป็น properties ที่จะกำหนด หรือไม่กำหนดก็ได้ หน้าที่ของมันคือเอาไว้เก็บชื่อเมธอดทั้งหมดที่คอมฑโพเนนท์ตัวนี้ทำได้ (มันก็คือ $emit อะไรได้บ้างนั่นแหละ) แต่การใส่ใน emits นั้นจะทำให้เวลาเรามาอ่านว่าคอมโพเนนท์ตัวนี้มีอะไรให้เรียกใช้ได้บ้างก็จะดูจากตรงนี้ได้เลย ไม่ต้องเสียเวลาไปไล่โค้ดข้างในอีกที

See the Pen
OJNGLEV
by Ta Tanapoj Chaivanichanan (@tanapoj)
on CodePen.

Vue Router ต้องสร้างด้วย createRouter()

เร้าเตอร์ก็เป็นอีกตัวที่มีการเปลี่ยนรูปแบบการเขียน

import Vue from 'vue'
import VueRouter from 'vue-router'

import App from './App.vue'
import FirstPage from './pages/FirstPage.vue'
import Page from './pages/FirstPage.vue'

Vue.use(VueRouter)

new Vue({
    render: (h) => h(App),
    router: new VueRouter({
        mode: 'history',
        routers: [
            { path: '/', component: FirstPage },
            { path: '/second', component: SecondPage },
        ],
    }),
}).$mount('#app')

โดยการสร้าง router จะต้องสร้างแยกตั้งหากด้วยคำสั่ง createRouter()

รวมถึงโหมด history ที่ตอนแรกเป็นค่าคอนฟิก, ใน Vue 3 ก็แยกออกมาเป็น createWebHistory() แล้วเหมือนกัน

import {createApp} from 'vue'
import {createRouter, createWebHistory} from 'vue-router'

import App from './App.vue'
import FirstPage from './pages/FirstPage.vue'
import Page from './pages/FirstPage.vue'

const router = createRouter({
    history: createWebHistory(),
    routers: [
        { path: '/', component: FirstPage },
        { path: '/second', component: SecondPage },
    ],
})

const app = createApp(App)
app.use(router)

router.isReady().then(() => {
    app.mount('#app')
})

Vuex ต้องสร้าง store ด้วย createStore()

คล้ายๆ กับข้อเมื่อกี้คือถ้าในโค้ดเรามีการใช้ Vuex การจะสร้าง store ขึ้นมาแทนที่จะ new Vuex.Store ตรงๆ ก็จะเปลี่ยนไปสร้างผ่าน createStore() แทน

import Vue from 'vue'
import Vuex from 'vuex'

import App from './App.vue'

const store = new Vuex.Store({
    //...
})

new Vue({
    store: store,
    render: (h) => h(App),
}).$mount('#app')

แบบนี้

import {createApp} from 'vue'
import {createStore} from 'vuex'

import App from './App.vue'

const store = createStore({
    //...
})

const app = createApp(App)
app.use(store)
app.mount('#app')

พวกค่าต่างๆ ของ store เช่น state, mutations, getters, actions ก็เซ็ตเหมือนเดิมทุกอย่าง ไม่มีอะไรเปลี่ยนแปลง

...

โดยรวมแล้ว Vue 3 มีการเปลี่ยน API ให้แยกส่วนกันมากขึ้น ทำให้โครงสร้างโค้ดถูกแบ่งเป็นโมดูลๆ จัดการและเทสง่ายขึ้น

Fragment

สำหรับใช้ที่เคยใช้ React มาก่อน Fragment ใน Vue ก็ใช้คอนเซ็ปเดียวกันนั่นแหละ

ตามปกติแล้วเวลาเราสร้าง template จำเป็นจะต้องมี root element 1 ตัวครอบทุกอย่างเอาไว้ ซึ่งหลายๆ ครั้งมันก็ไม่สะดวกเอาซะเลย

<!-- Layout.vue -->
<template>
  <div>
    <header>...</header>
    <main>...</main>
    <footer>...</footer>
  </div>
</template>

สำหรับ Vue 3 นั้นอนุญาตให้เราสร้าง root element หลายตัวใน template ได้แล้ว

<!-- Layout.vue -->
<template>
  <header>...</header>
  <main v-bind="$attrs">...</main>
  <footer>...</footer>
</template>

Teleport Component

teleport เป็นฟีเจอร์ที่ทำให้คอมโพเนนท์บางส่วนที่เราสร้างขึ้นมาใน Application สามารถ "เทเลฟอร์ท" หรือ "วาร์ป" ออกไปข้างนอกได้!

ตัวอย่างที่เข้าใจง่ายที่สุดน่าจะเป็น Modal หรือการที่มีกล่องเหรือเฟรมอะไรสักอย่างเด้งขึ้นมาซ้อนทับ content ของเราเป็นอีกเลเยอร์หนึ่ง

สำหรับ Vue เวอร์ชันก่อนๆ ที่ต้องเขียนทุกอย่างอยู่ในขอบเขตของ Application เท่านั้น

ถ้าเราจะสร้าง Modal ขึ้นมาก็จะต้องสร้างไว้ข้างในแอพนั้นแหละ

แต่สำหรับ Vue 3 เราสามารถกำหนดสิ่งที่เรียกว่า teleport เพื่อบอกว่าโค้ดตรงนี้นะ เราจะส่งมันออกไปแสดงผลข้างนอกแอพ!

จากเดิมเป็นโค้ดแบบนี้

const app = Vue.createApp({});

app.component('modal-button', {
  template: `
    <button @click="modalOpen = true">
        Open full screen modal!
    </button>

    <div v-if="modalOpen" class="modal">
      <div>
        I'm a modal! 
        <button @click="modalOpen = false">
          Close
        </button>
      </div>
    </div>
  `,
  data() {
    return { 
      modalOpen: false
    }
  }
})

ให้เราเพิ่ม <teleport to="body"> ลงไปครอบคอมโพเนนท์ส่วนที่ต้องการส่งออกไปข้างนอก (เช่นเคสนี้ส่งไปแสดงผลที่ชั้น body)

app.component('modal-button', {
  template: `
    <button @click="modalOpen = true">
        Open full screen modal! (With teleport!)
    </button>

    <teleport to="body">
      <div v-if="modalOpen" class="modal">
        <div>
          I'm a teleported modal! 
          (My parent is "body")
          <button @click="modalOpen = false">
            Close
          </button>
        </div>
      </div>
    </teleport>
  `,
  data() {
    return { 
      modalOpen: false
    }
  }
})

See the Pen
gOPNvjR
by Vue (@Vue)
on CodePen.


จริงๆ ยังมีอีกหลายฟีเจอร์ที่ Vue 3 มีการเพิ่มฝเปลี่ยนแปลง แต่อันนี้เลือกมาเฉพาะๆ ตัวที่คิดว่าสำคัญ

ป.ล. Vue 3 นั้นออกเวอร์ชัน 3.0.0 เรียบร้อยแล้ว แต่เฟรมเวิร์คหลัๆ บางตัวก็ยังไม่เสร็จดี เช่น router หรือ Vuex และสำหรับการติดตั้งจะต้องใช้ npm i vue@next หากต้องการจะใช้เวอร์ชัน 3 (ถ้าใช้ npm i vue จะได้เวอร์ชัน 2) ซึ่งการเปลี่ยนแปลงให้ vue 3 เป็นตัวหลักแทนน่าจะดำเนินการเสร็จภายในสิ้นปี 2020 นี้

อ่านเพิ่มเติมได้ที่ Release Note นะ

12 Total Views 6 Views Today
Ta

Ta

สิ่งมีชีวิตตัวอ้วนๆ กลมๆ เคลื่อนที่ไปไหนโดยการกลิ้ง .. ถนัดการดำรงชีวิตโดยไม่โดนแสงแดด
ปัจจุบันเป็น Senior Software Engineer อยู่ที่ Centrillion Technology
งานอดิเรกคือ เขียนโปรแกรม อ่านหนังสือ เขียนบทความ วาดรูป และ เล่นแบดมินตัน