[重構倒數第29天] — Vue2 Option API轉換 Vue3 Composition API
Tags: Vuejs, Vue, Vue 3, Front End Development

[重構倒數第29天] — Vue2 Option API轉換 Vue3 Composition API Mike Follow Sep 4 · 13 min read

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

前言

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

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

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

1. 先定義 setup 函式

跟以往的 Option Api 不同，我們可以在 setup 函式裡面去定義我們所有的東西，包含 data 、 methods 、 computed 、 lifecycle 等方法。

<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>

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

ref : 可以使用任何型態的資料，但是不會對 Object 或是 Array 內部的屬性做監聽。

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

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

3. methods 與 this

一般我們要定義 methods 會在 methods 這個欄位裡面去定義它的 function，然後要 get 、 set 可以透過 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 的細節可以參考官方的文件

然後要使用的話就像是這樣，非常的簡單明瞭，完全解決了使用 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 ，然後原本的 beforeCreate 、 created 沒了， 現在的 setup 這個函式就等同於 beforeCreate 、 created 這兩個效果一樣，合再一起了。

所以我今天如果是要在 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>

先告一個段落

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