Vue 3 มาแล้ว !! ใช้ให้ชิน แล้วจะฟินนาเล่
ออกกันมาได้สักพักแล้วนะครับกับ Vue.js Next หรือ Vue.js version ที่ 3 (Codename: One Piece) ที่หลายๆคนรอกันมาอย่างยาวนาน หลังจากได้ลองเล่นมาได้สักพัก เลยถือโอกาสนี้จะมาแนะนำเพื่อนๆกันใน 2 หัวข้อใหญ่ๆ ได้แก่
- ทำไมต้อง Migrate ? ควร Migrate เลยไหม ?
- Composition API !!
ทำไมต้อง Migrate ? ควร Migrate เลยไหม ?
แม้ว่า Feature ชูโรงของ Vue.js 3 นั้นคือ Composition API ที่มาใหม่แกะกล่องก็ตาม แต่มันยังคงซัพพอร์ตการเขียน Option API เหมือนเดิม นั่นทำให้การ Migrate นั้นไม่ยุ่งยากและคุ้มค่ามากขึ้น เพราะเราไม่จำเป็นต้อง Rewrite code จาก Option API ไปเป็น Composition API แต่กลับได้ Features ใหม่และประสิทธิภาพที่ดีขึ้น
Migrate แล้วได้อะไรนอกจาก Features ใหม่ ? แค่ Migrate เฉยๆไม่ต้องแก้อะไรเพิ่มเติม Application ของคุณก็จะมี Performance ที่ดีขึ้น เพราะ Vue.js 3 Core Lib มีการ Rewrite ใหม่ทั้งหมดโดยเฉพาะในส่วนของ Virtual DOM มีการ Optimize ให้มีประสิทธิภาพมากยิ่งขึ้นนั่นเอง
สำหรับคนที่จะขึ้น Project ใหม่ผมก็แนะนำให้ขึ้นเป็น Vue.js 3 ไปเลย ส่วนจะเขียนด้วย Option API แบบเดิมหรือจะเขียนเป็น Composition API ก็แล้วแต่โลด
แต่ถ้าถามว่าควร Migrate ทันทีเลยไหม สำหรับสายแข็งก็สามารถทำได้เลย แต่ถ้ายังไม่เข้มพออยากให้รอไปก่อน เพราะใน docs ระบุว่าเดี๋ยวกำลังจะอัพเดตเวอชันใหม่ที่มาช่วยซัพพอร์ตในเรื่อง Migration ซึ่งจะทำให้การ Migrate นั้นลื่นไหลมากขึ้น
สำหรับสายแข็งก็อย่าลืมเช็ค breaking change กันด้วยล่ะ จะได้ตามแก้ถูก
อ่านรายละเอียดเพิ่มเติม คลิก
Composition API
จริงๆ Vue.js 3 ก็มี Features ใหม่ๆอยู่พอสมควร แต่ Hilight ของมันคงหนีไม่พ้นเจ้า Composition API ที่ลูกพี่อีวานเขา Proundly Present เหลือเกิน โดยผมจะเน้นการเปรียบเทียบ เพื่อให้เห็นภาพความแตกต่างและวิธีการใช้งาน ระหว่าง Option API (แบบเก่า) กับ Composition API (แบบใหม่) ก่อน อาจจะยังไม่ได้ลงลึก แบบละเอียดยิบๆ เพราะจะยาวเกิ๊น
0. การ init Application
ก่อนอื่นมาทำความเข้าใจกันก่อนครับว่าใน Vue.js 3 นั้นเวลาคุณต้องการจะทำอะไรสักอย่างเช่น Create component, router, vuex และ อื่นๆ มันมักจะถูกเตรียมเป็น Function เอาไว้ให้เรียกใช้งานแทนที่จะเป็น Built-in ใน Vue ดังเดิม
มาลองเริ่มต้นกันด้วยอะไรง่ายๆกันก่อน นั่นก็คือการ Init Application
การ Init Application (แบบเดิม)
import Vue from 'vue'
import App from './App'
import router from './router'new Vue({
render: h => h(App),
router
}).$mount('#app')
การ Init Application (แบบใหม่)
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'createApp(App).use(router).mount('#app')
การสร้าง router หรือแม้แต่ vuex และอื่นๆ จะแตกต่างจากเดิม เพราะจะเป็นการ import function เข้ามา เราจะไม่ได้เห็น Vue.use อะไรทำนองนี้อีกแล้ว มาดูตัวอย่างการสร้าง router กันสักเล็กน้อยจะได้เข้าใจมากขึ้น
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'const routes: Array<RouteRecordRaw> = [{
path: '/',
name: 'Home',
component: () => import('../views/Home.vue')
}]const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
1. การสร้าง Component
การสร้าง Component (Option API)
import HelloWorld from './components/HelloWorld.vue'export default {
name: 'Home',
components: {
HelloWorld
}
}
การสร้าง Component (Composition API)
import { defineComponent } from 'vue'
import HelloWorld from '@/components/HelloWorld.vue'export default defineComponent({
name: 'Home',
components: {
HelloWorld
}
})
สำหรับการสร้าง Component นั้น ถ้าเราอยากให้มันซัพพอร์ต TypeScript เต็มตัวเราควรสร้าง Component ด้วย function defineComponent แทนครับผม
2. จัดการ Component
การจัดการ Component (Option API)
export default {
data () {
return {
count: 0
}
},
computed: {
double: () => {
return this.count * 2
}
},
mounted () {
console.log('mounted')
},
methods: {
increment() {
this.count++
}
}
}
การจัดการ Component (Composition API)
import { onMounted, reactive, computed } from 'vue'export default defineComponent({
setup() {
const state = reactive({
count: 0,
double: computed(() => state.count * 2)
}) onMounted(() => {
console.log('mounted')
}) function increment() {
state.count++
} return {
state,
increment
}
}
})
ตรงจุดนี้น่าจะเริ่มเห็นความแตกต่างละ ทั้งการประกาศตัวแปร และ methods สำหรับใช้งาน โดยจุดเริ่มต้นของ Component ที่เขียนด้วย Composition API จะเริ่มด้วย Function setup เสมอ แล้วเราก็จัดการทุกอย่างภายใน Function นี้ได้เลยครับผม
ส่วนค่าที่ Return ออกมาจาก Function setup นั้นสามารถเป็นได้ทั้ง Render function (ในกรณีใช้ JSX) หรือเป็น ตัวแปร และ Methods ในกรณีใช้ Html-Template
3. การเขียนแบบ Composables
มาถึงจุดนี้ หลายๆคนคงจะเริ่มรู้สึกว่า ทำไมต้อง Composition API ? เพราะจากตัวอย่างก็เห็นกันอยู่ว่า ดูมีความซับซ้อนกว่า ทั้งการประกาศตัวแปรต้องใช้ Ref, Reactive และยังต้องมานั่ง Import สิ่งที่อยากได้ทีละอย่าง รวมไปถึง LoC ที่เยอะกว่า
ทุกคนเข้าใจถูกต้องทั้งหมดเลยครับ…แต่ พลังของของ Composition API มันอยู่ที่การนำมันมาประยุกต์ใช้งาน หลายคนอาจจะยัง งง เพราะไม่มีตัวอย่างที่ชัดเจน งั้นเรามาดูตัวอย่างการใช้ Composition API ด้วยการเขียนแบบ Composables Module (Extract and Reuse) กันครับ
มาลองวางโจทย์กันก่อนครับ
เราอยากจะสร้างหน้าเว็บหน้านึงที่ดึงข้อมูลภาพยนต์มาแสดงพร้อมมี pagination ซึ่งหน้านี้จะเป็นหน้า Home ดึงข้อมูลภาพยนต์ทุกประเภทมาแสดง
ผมจะไม่เขียนแบบ Option API มาเทียบแล้วนะครับ เพราะแต่ละคนน่าจะนึกภาพตามได้ว่าถ้าเราจะเขียน Vue.js ตามโจทย์ด้านบนจะเขียนกันแบบไหน แต่ ณ ตอนนี้ผมจะเขียนแบบ Composables ให้ดูกันว่าข้อดีของ Composition API มันคืออะไรกันแน่
เริ่มด้วยการสร้าง useMovies.js
// src/composables/useMovies.jsimport { fetchMovies } from '@/api/movies'
import { ref, onMounted, watch } from 'vue'
export default function useMovies(page) {
const movies = ref([])
const getMovies = async () => {
movies.value = await fetchMovies(page)
}
onMounted(getMovies)
watch(page, getMovies) return {
movies,
getMovies
}
}
จากนั้นใน Component File ก็เรียกใช้งานซะ…
// Home.vue
<template>
<ul>
<li v-for="item in movies">{{ item.name }}</li>
</ul>
<button @click="previousPage">previous</button>
<button @click="nextPage">next</button>
</template><script>
import { ref } from 'vue'
import useMovies from '@/composables/useMovies'export default defineComponent({
name: 'Home',
setup () {
const page = ref(1)
const { movies, getMovies } = useMovies(page) function nextPage () {
page++
} function previousPage () {
page--
} return {
movies,
nextPage,
previousPage
}
})
}
</script>
หลายคนอาจจะเริ่มเห็น Ref, Reactive ในการเขียน Composition API กันพอสมควรอธิบายง่ายๆมันคือการประกาศตัวแปรนั่นเองครับ โดย Function Ref และ Reactive นั้นจะรับค่าเข้าไปและคืน Reactive Variable/Object กลับมาให้เรา
ทำไมต้องให้ตัวแปรเป็น Reactive ? โดยปกติตัวแปรประเภท Primitive type เวลาส่งค่าผ่านไปที่ต่างๆมันจะเป็นการ Pass by value ซึ่งขัดกับ Concept ของการเขียนแบบ Composables Module ที่เราต้องการให้ Composable logic นั้น reuse ไปได้ทุกที่
ลองไล่ code และทำความเข้าใจ
- Component เรียกใช้ useMovies โดยส่ง page ที่เป็น reactive variable เข้าไป และรับ movies (reactive variable) และ getMovies function กลับออกมา
- useMovies ผูก function getMovies เอาไว้ให้คอยอัพเดท movies variable ทุกครั้งที่มีการเรียกใช้งานโดยส่งค่า page เรียกไปที่ API อีกทีหนึ่ง
- useMovies เรียก getMovies ทุกครั้งที่ Component onMounted ทำงาน
- useMovies ทำการ watch page เอาไว้ เมื่อ page มีการเปลี่ยนแปลง จะเรียกใช้งาน getMovies ทันที ซึ่งจะเกิดการทำงานตามข้อ 2 อีกครั้ง
สิ่งที่เกิดขึ้น
- Component สามารถเซตค่า page เพื่อให้ useMovies ทำงานตามที่เราเขียนไว้
- Component สามารถเรียก getMovies แบบ Manual เองก็ทำได้
- เราจะเห็นการทำงานของ reactive variable ชัดเจนขึ้น เมื่อ page เปลี่ยน useMovies ก็จะทำงาน เมื่อ useMovies ทำงาน movies variable ก็จะถูก update โดยทั้ง 2 ฝั่งจะ reactive กันและกันโดยทันที
- useMovies สามารถ Reuse ได้ในทุกๆ Component ไม่ต้องมี Code Duplicated เช่นในส่วนของ onMounted และการยิง API
Note
- Code ตัวอย่างยังไม่ใช่ Real-world Situation เพียงต้องการแสดงให้เห็นวิธีการใช้งาน Reactive variable และการเขียนแบบ Composable Module ที่น่าจะนำไปต่อยอดหรือปรับเปลี่ยนวิธีการ Code ของเราให้ดีขึ้นได้
- เมื่อเราเริ่มชินกับ Composition API เราก็จะเริ่ม เขียนโค้ดแบบแบ่งเป็น module ย่อยๆมากขึ้น การทำงานกับทีมใหญ่คนเยอะก็จะมีปัญหาน้อยลง รวมไปถึงการเขียน Test ก็จะง่ายมากขึ้นตามไปด้วย
- ใน Vue.js 3 ยังคงซัพพอร์ต Option API เหมือนเดิมครับ ไม่ต้องห่วงถ้าใครรู้สึกว่าการใช้งานด้วย Composition API มันซับซ้อนเกินไป
- Ref นั้นรับ variable เข้าไป และ สร้าง Object คืนกลับม โดย Object จะ Wrap ค่า variable เดิมไว้ใน key ชื่อ value และค่านั้นจะกลายเป็น Reactive Variable
- Reactive นั้นรับ Object เข้าไป และคืน Reactive Object ที่มี key ตามเดิม โดยค่าของแต่ละ key จะกลายเป็น Reactive Variable
- การ Destructing variable อาจทำให้สูญเสียความเป็น Reactive ไป เช่น const { user } = getUser() ตัว user ที่ destruct ออกมาจะไม่เป็น Reactive