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 專業職人 入門篇