Composition API 是以邏輯功能來分割程式碼,像是寫原生 JavaScript 一樣,我們會把實現同樣功能的程式碼寫在附近。但 Options API 是以程式碼的性質來分割程式碼,例如有 3 個函式各自用來實現不同功能,但如果它們都是 computed
函式,就全都一起寫在 computed
屬性裏。所以會產生以下問題:
關於這個題目會分兩篇來寫,這篇會整理 Composition API 的概念和語法。下一篇就會以實戰為主,試試改寫 Options API 例子。
Options API 是根據程式的性質來分割程式碼。
export default{
props: {},
components: {},
data() {
// 所有資料
},
computed() {
// filter todos 功能
},
mounted() {
// 顯示 todos 功能
},
methods() {
// 刪除 todos 功能
}
}
但是,Compositions API 是按邏輯來分割程式碼。
export default {
setup() {
// 1. 顯示 todos 的功能
// ...
// 2. filter todos 的功能
// ...
// 3. 刪除 todos 的功能
// ....
}
}
概念如下圖一樣,用不同顏色代表不同功能需求。 Vue 2 的 options API 的寫法明顯會比較難閱讀:
在 options API,需要用 mixin 來引入可重用的程式碼。但在大型專案中,有機會會在一個元件裏引入很多 mixin,以致以下問題:
Vue 3 的 composition API 就能解決 mixin 的問題。假設現在我要重用 serachKeyword 此功能
composables/ searchKeyword.js
export default function searchKeyword(keyword, key, list){
const result = list.find( item => item[key] === keyword)
return {
result
}
}
Home.vue
import searchKeyword from '@/composables/searchKeyword'
export default{
setup() {
const { result } = searchKeyword('tshirt', 'title', [{ title: 'hat', price: 100}, {title: 'tshirt', price: 200}])
console.log(result); // {title: 'tshirt', price: 200}
}
}
以上是一個很簡單例子,searchKeyword 是用來找出在 list 中合乎 keyword 的那筆資料。以上例子很清晰看到,result 的值是來自 searchKeyword
這個可重用的函式。而且,把它 import 進來 Home.vue 時,我把它也可以改名,不一定用 searchKeyword 這個名稱。因此,比起 Vue 2 的 mixin 更好用。
起手式是建立一個 setup 函式,此函式會在 app
掛載在 DOM 上後,並在元件建立前(created 生命週期)執行,而且只會執行一次。
export default {
setup(props, context) {
// 所有程式碼寫在這裏
return {
// 所有在模版上會使用的資料、函式
}
}
}
props
就是用來接收外層資料。slot
, attrs
, emit
, expose
。注意,因 setup
是在元件建立前執行,所以只可使用: props
、slot
、 attrs
、 emit
、 expose
。不可使用: data
、computed
、methods
、refs
(模版用 refs)
setup
函式會回傳一個物件,裏面要放所有在模版裏會用到的資料或函式:
<template>
<div>
<p>{{ user }} </p>
<button @click="greeting" type="button">Say Hi!</button>
</div>
</template>
export default {
setup() {
const user = 'Alysa'
const greeting = () => {
alert('Hi I am a front-end developer')
}
return {
greeting, user
}
}
}
如果資料或函式不在 return
物件裏,模版就不能使用。
如果使用解構賦值的手法,會導致該 props 的資料不再是響應式。舉例說,要把在外層(App.vue)的 count
傳入內層(List.vue):
App.vue
<template>
<p>App: {{ n }} </p>
<List :count="n"></List>
</template>
export default {
components: {
List
},
setup() {
const n = ref(1)
// 每 1 秒加 1
setInterval( () => n.value++,1000)
return {
n
}
}
}
List.vue
<template>
<p>List: {{ count }} </p>
</template>
export default {
props: {
count: Number
},
setup(props) {
// count 失去響應式,不會再更新!
const { count } = props
return {
count
}
}
}
結果:
App 裏的 count
目前已經累加到 5,但 List 裏的 count
仍然是 1。
toRef
或 toRefs
包裝const count = toRef(props, 'count')
或者
// 使用解構賦值建立 count 變數
const { count } = toRefs(props)
結果就能保持響應式,外層和內層的 count 數值同步:
此文較後部分會再詳細講解兩者的差異和用法。
在 Vue 2 時,我們全都放到 data
屬性裏就行了。但現在 Vue 3 裏就需要使用 ref()
或 reactive()
來建立響應式資料。
假設有 user、todos 這兩筆資料。
Vue 2 做法:
data() {
return{
user: 'Alysa',
todos: ['Watch Netflix', 'Buy dinner']
}
}
Vue 3 做法:
setup() {
const user = ref('Alysa')
const todos = reactive(['Watch Netflix', 'Buy dinner'])
// 用 ref 也可以,看你個人偏好
return {
user,todos
}
}
ref
的接收一個參數,可以是任何型別。ref
無法監聽陣列或物件內部屬性的變更。(硬要監聽的話用 watch 的 deep true 也可以)ref
會回傳一個響應式物件,裏面只有 value
這個屬性。因此,取值時的寫法如下:const user = ref('Alysa')
console.log(user.value) // Alysa
reactive
的參數只能是陣列或物件。reactive
可以深層監聽陣列或物件內部屬性的變更。.value
。因為 reactive
會把整個物件或陣列回傳回來。個人偏好是用 ref 定義基本型別資料,用 reactive 定義物件、陣列資料。但這也取決於公司團隊做法,也有聽說過由頭到尾都是用 ref 的做法。
上面提及過,可使用 toRef
或 toRefs
來保持 props
是響應式。此兩種語法是針對用 reactive
定義的物件。
reactive
原資料修改,畫面會變化。但 toRef
/ toRefs
修改時,畫面不會變化。reactive
原資料修改時,toRef
/ toRefs
會隨之而變化。反之,當 toRef
/ toRefs
改動時,原資料不會受影響。toRef
接收兩個參數,回傳一個值。toRefs
只會接收一個參數,回傳一個物件。const x = toRef(物件, 屬性)
toRefs
只接收一個參數,回傳一個物件:
const x = toRefs(物件)
最後,Vue 3 新增了設定資料為唯讀的功能。這能避免在多人團隊開發時,不小心修改了某些函式裏的值,以致程式出錯。
const todos = reactive(['Watch Netflix', 'Buy dinner'])
const copyTodos = readonly(todos)
const edit = () => {
copyTodos[0] = 'sleep'
}
edit() //Set operation on key "0" failed: target is readonly
ref
的資料也一樣:
const name = ref('Alysa')
const copyName = readonly(name)
setUp()
、ref
/ reactive
來建立資料等等。ref,toRef,toRefs三者的使用及區別
Vue 3 - Composition API
Mike - 2021 Vue 3 專業職人 入門篇