[重構倒數第29天] — Vue2 Option API轉換 Vue3 Composition API

Mike
I am Mike
Published in
13 min readSep 4, 2021

Vue2 重構 Vue3 的時候,我們可以從以下幾個地方著手!

前言

該系列是為了讓看過Vue官方文件或學過Vue但是卻不知道怎麼下手去重構現在有的網站而去規畫的系列文章,在這邊整理了許多我自己使用Vue重構很多網站的經驗分享給讀者們。

Vue3 的 Composition Api 可以說是改變了原本的 Option Api 撰寫上面的方式,還有撰寫 Vue 上面的一些思維的變動。

所以今天如果我們接手了一個 Vue2 的專案要升級成 Veu3 Composition Api 的話,我們可以先從甚麼地方下手,會需要注意那些地方,然後要怎麼去改寫 Option Api,我們馬上來看一下。

1. 先定義 setup 函式

跟以往的 Option Api 不同,我們可以在 setup 函式裡面去定義我們所有的東西,包含datamethodscomputedlifecycle 等方法。

<script>
export default {
setup(){
return {}
}
}
</script>

2. 檢查有多少 data

我們在改寫的時候會先來看一下有多少被定義的資料,這邊是我們在 Option Api 裡面定義的資料的部分。

<script>
export default {
data(){
return {
userData: {
name: '',
age: '',
address: '',
},
isOpen: false,
products: []
}
}
}
</script>

我們接下來來用 Vue3 的方式來改寫 ,就會像這樣。

<script>
import { ref, reactive } from "vue";
export default {
setup() {
const isOpen = ref(false)
const products = ref([])
const userData = reactive({
name: '',
age: '',
address: '',
})
return {}
},
};
</script>

在這邊你會看到我定義資料是用 refreactive 來定義的,關於我們該如何選用 refreactive 呢 ? 在大多數的情況下兩者皆可以互相替換使用,主要取決於個人喜好或是團隊習慣,根據具體情況來決定要使用哪個好,關於 refreactive 的差異我們來看一下 :

  • ref : 可以使用任何型態的資料,但是不會對 Object 或是 Array 內部的屬性做監聽。
  • reactive : 只接受 ObjectArray ,可以做深層的監聽,以及取資料的時候不用使用 .value,但是如果對 reactive 的 Object 使用解構的方式取得內容,就會失去了 Vue 更新綁定的機制。

我自己個人一般的情況下能盡量使用 ref就使用ref,可以更精準地去控制資料的更新,很多人不習慣用 .value的方式,但是 .value的方式可以讓開發者更明確知道這段 code 再進行資料的更新。

更多具體的細節可以參考我之前寫的這篇文章 ref 跟 reactive 我該怎麼選 !?

3. methods 與 this

一般我們要定義 methods 會在 methods 這個欄位裡面去定義它的 function,然後要 getset可以透過 this來使用,這個 this就是指向 Vue 本身。

<script>
export default {
data(){
return {
userData: {
name: '',
age: '',
address: '',
},
isOpen: false,
products: []
}
},
methods: {
handleTroggle(){
this.isOpen = !this.isOpen
},
setProducts(prod){
this.products = prod
},
showUserKey(key){
this.userData.name = 'mike'
console.log(this.userData.name)
}
}
}
</script>

但是改成 Composition Api 的方式就不需要寫在 methods 裡面,也不需要管 this 指向的部分,整體下來看起來乾淨很多。

<script>
import { ref, reactive } from "vue";
export default {
setup() {
const isOpen = ref(false)
const products = ref([])
const userData = reactive({
name: '',
age: '',
address: '',
})

const handleTroggle = () => {
isOpen.value = !isOpen.value
}

const setProducts = (prod) => {
products.value = prod
}

const showUserKey = (key) => {
userData.name = 'mike'
console.log(userData.name)
}

return {}
},
};
</script>

4. 換掉 mixins,把共用邏輯抽出來

以前我們會把共用的 function 或是 data 給放到 mixins,但是 mixins 最讓人詬病的是對於來源的查找很不明確,只能靠 coding style 來做區別,所以我們要透過 Composition Api 的方式來處理共用的 function 。

· 使用 mixin

先新增一個 myMixin.vue的檔案

// myMixin.vue
<script>
export default {
data(){
return {
point: 0
}
},
methods: {
addPoint() {
this.point += 1
}
}
}
</script>

然後再我們的 Component 去載入 myMixin.vue

// app.vue
<script>
import myMixin from "./myMixin.vue";
export default {
mixins: [myMixin],
data(){
...
},
methods: {
...
}
}
</script>
<template>
<div id="app">
<h1>Point: {{ point }}</h1>
<button @click="addPoint">click</button>
</div>
</template>

上面就是一般我們使用 mixins 的方式,雖然方便,但是如果多個 Component 的引入跟使用也會造成管理上的不方便,所以接下來我們使用 composition Api 來包裝它。

· 使用 Composition Api

先新增一個名叫 useAddPoints.js 的檔案

import { ref, readonly  } from "vue"
export function useAddPoints(){
const point = ref(0)
const addPoint = () => {
point.value += 1
}
return {
point: readonly(point),
addPoint
}
}

這邊使用了 readonly最主要是希望這個傳出去的值是只可以get讀取的,所以在這邊把它給用 readonly 包起來,關於 readonly 的細節可以參考官方的文件

官方文件 : https://v3.vuejs.org/api/basic-reactivity.html#readonly

然後要使用的話就像是這樣,非常的簡單明瞭,完全解決了使用 mixins上面來源不清楚的問題。

<script>
import { useAddPoints } from "../composition/useAddPoints.js"
export default {
setup() {
const { point, addPoint } = useAddPoints();

return {
point,
addPoint
}
},
};
</script>
<template>
<div id="app">
<h1>Point: {{ point }}</h1>
<button @click="addPoint">click</button>
</div>
</template>

5. lifecycle (生命週期) 的更新

我們在 Option Api 裡面有許多的 lifecycle 可以用,但是到了 composition Api 裡面就有了一些變化,所以今天讓我們來稍微看一下這些變化,也好在轉移程式碼的時候避免發生一些問題。

<script>
export default {
beforeCreate(){},
created(){},
beforeMount(){},
mounted(){},
updated(){},
destroyed(){}
}
</script>

但是在 composition Api 裡面我們要從 vue 裡面把 lifecycle 取出來在setup 使用。

<script>
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onUnmounted } from "../composition/useAddPoints.js"
export default {
setup() {

onBeforeMount(()=> {})

onMounted(()=> {})

onBeforeUpdate(()=> {})

onUpdated(()=> {})

onUnmounted(()=> {})

return {}
},
};
</script>

不過這邊要特別注意,原本 Veu2 裡面 destroyed 這個生命週期函式再 Vue3 裡面 改名叫做 onUnmounted,然後原本的 beforeCreatecreated 沒了, 現在的 setup 這個函式就等同於 beforeCreatecreated 這兩個效果一樣,合再一起了。

更多完整的生命週期函式可以參考官方文件的部份
https://v3.vuejs.org/guide/composition-api-lifecycle-hooks.html#lifecycle-hooks

所以我今天如果是要在 created 裡面去打 API,我就可以直接在 setup裡面去做操作。

<script>
import { ref } from 'vue'
import axios from "axios"
export default {
setup() {
const userData = ref([])

// 等同於再 created 執行
axios.get("https://test.demo.com/api/getUser").then((res)=> {
userData.value = res.data
})

return {
userData
}
},
};
</script>

或是今天你要移除原生 JS 監聽的時候,就可以改用 onUnmounted

<script>
import { onMounted, onUnmounted } from 'vue'
import axios from "axios"
export default {
setup() {

const handleWinReSize = (e) => {
console.log(e)
}

onMounted(()=> {
window.addEventListener("resize", ,handleWinReSize)
})

// 等同於 Vue2 的 destroyed
onUnmounted(()=> {
window.removeEventListener("resize", ,handleWinReSize)
})

return { }
},
};
</script>

6. Props 跟 Emit 的使用

以前在 Option Api 上面的時候都要透過 this 的方式來取得 props 還有使用 emit,但是在 Composition Api 裡面可以統一透過 setup 函式來取得這兩個物件。

props

定義好 props 傳入的內容,我們就可以從 setup 正確的取得 props ,它會從 setup 的第一個參數傳入。

<script>
import { onMounted } from 'vue'
export default {
props: {
userType: {
type: String,
default: "user"
}
},
setup(props) {

onMounted(()=> {
console.log(props.userType)
})

return { props }
},
};
</script>
<template>
<h1>{{ props.userType }}</h1>
</template>

emits

emit 再 setup 的第二個參數的物件裡面,我們可以透過解構的方式直接取出,然後使用。

<script>
export default {
emits: ['userName'],
setup(props, { emit }) {

const getUserName = (name) => {
emit("userName", name)
}

return { getUserName }
},
};
</script>
<template>
<button @click="getUserName('mike')">mike</button>
<button @click="getUserName('jacky')">jacky</button>
<button @click="getUserName('andy')">andy</button>
<button @click="getUserName('scars')">scars</button>
<button @click="getUserName('ash')">ash</button>
</template>

先告一個段落

重構這件事情不是一個小工程,它裡面有很多需要注意的細節在裡面,包括如何讀懂人家的邏輯以及理解這個功能開發上面的前因後果,還有就是你的經驗的展現,各種問題、各種情境,除了程式碼語法版本上面的差異外,還有很多都是要下功夫的,再後面的章節,我會再一一的跟大家介紹關於重構專案上面其他需要注意的地方,還有我的思考規劃的方式,以及選用技術的考量等。

此文章同步更新於 第 13 屆 iT 邦幫忙鐵人賽
https://ithelp.ithome.com.tw/articles/10259305

那如果對於Vue3不夠熟的話呢?

Ps. 購買的時候請登入或註冊該平台的會員,然後再使用下面連結進入網站點擊「立即購課」,這樣才可以讓我獲得更多的課程分潤,還可以幫助我完成更多豐富的內容給各位。

我有開設了一堂專門針對Vue3從零開始教學的課程,如果你覺得不錯的話,可以購買我課程來學習
https://hiskio.com/packages/AYR5m7VR3

如果對於JS基礎不熟的朋友,我也有開設JS的入門課程,可以參考這個課程
https://hiskio.com/packages/Q9R4OYoyD

訂閱Mike的頻道享受精彩的教學與分享

Mike 的 Youtube 頻道
Mike的medium
MIke 的官方 line 帳號,好友搜尋 @mike_cheng

--

--

Mike
I am Mike

如果有一行code無法解決的bug,那就寫兩行!