iT邦幫忙

2021 iThome 鐵人賽

DAY 29
1
Modern Web

不只懂 Vue 語法:Vue.js 觀念篇系列 第 29

不只懂 Vue 語法:試說明 Composition API 與 Options API 概念和語法的分別?

問題回答

Composition API 是以邏輯功能來分割程式碼,像是寫原生 JavaScript 一樣,我們會把實現同樣功能的程式碼寫在附近。但 Options API 是以程式碼的性質來分割程式碼,例如有 3 個函式各自用來實現不同功能,但如果它們都是 computed 函式,就全都一起寫在 computed 屬性裏。所以會產生以下問題:

  • 程式碼較難閱讀,因為不是按功能邏輯來放程式碼,以致在比較大型的專案上,閱讀時就要跳來跳去,也比較耗時。
  • 如果要重用程式碼,就需要用 mixin。但如果 mixin 太多,每次看程式碼時就很難追蹤,需要逐一打開所有引入的 mixin 來看。

關於這個題目會分兩篇來寫,這篇會整理 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 的寫法明顯會比較難閱讀:

解決 mixin 難以追蹤和命名衝突問題

在 options API,需要用 mixin 來引入可重用的程式碼。但在大型專案中,有機會會在一個元件裏引入很多 mixin,以致以下問題:

  • 假設有一個元件裏的功能出錯,我就要逐一打開此元件用到的 mixin 去查看,找出哪個 mixin 有涉及到此功能,然後再檢查這些程式碼有沒有問題。
  • 假設某元件裏的程式碼已經很多,命名了很多函式。當我想在此再引入一個 mixin 來用時,有可能 mixin 裏的函式名稱,與我目前這個元件裏所定義的函式名稱相同而產生衝突。這樣的話,Vue 2 無視 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 函式

起手式是建立一個 setup 函式,此函式會在 app 掛載在 DOM 上後,並在元件建立前(created 生命週期)執行,而且只會執行一次。

export default {
  setup(props, context) {
    // 所有程式碼寫在這裏
    return {
        // 所有在模版上會使用的資料、函式
    }
  }
}
  • props:跟 options API 一樣,props 就是用來接收外層資料。
  • context:一個物件。裏面會有 slot, attrs, emit, expose

注意,因 setup 是在元件建立前執行,所以只可使用: propsslotattrsemitexpose。不可使用: datacomputedmethodsrefs(模版用 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 的響應式資料

如果使用解構賦值的手法,會導致該 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。

解決方法:使用 toReftoRefs 包裝

const count  = toRef(props, 'count')

或者

// 使用解構賦值建立 count 變數
const { count } = toRefs(props)

結果就能保持響應式,外層和內層的 count 數值同步:

此文較後部分會再詳細講解兩者的差異和用法。

使用 ref 與 reactives 定義響應式資料

在 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 的接收一個參數,可以是任何型別。
  • ref 無法監聽陣列或物件內部屬性的變更。(硬要監聽的話用 watch 的 deep true 也可以)
  • ref 會回傳一個響應式物件,裏面只有 value 這個屬性。因此,取值時的寫法如下:
const user = ref('Alysa')
console.log(user.value) // Alysa

reactive

  • reactive 的參數只能是陣列或物件。
  • reactive 可以深層監聽陣列或物件內部屬性的變更。
  • 取值時不用使用 .value。因為 reactive 會把整個物件或陣列回傳回來。

個人偏好是用 ref 定義基本型別資料,用 reactive 定義物件、陣列資料。但這也取決於公司團隊做法,也有聽說過由頭到尾都是用 ref 的做法。

使用 toRef 或 toRefs 引用響應式資料

上面提及過,可使用 toReftoRefs 來保持 props 是響應式。此兩種語法是針對用 reactive 定義的物件。

  • 畫面方面:當 reactive 原資料修改,畫面會變化。但 toRef / toRefs 修改時,畫面不會變化。
  • 資料方面:當 reactive 原資料修改時,toRef / toRefs 會隨之而變化。反之,當 toRef / toRefs 改動時,原資料不會受影響。
  • 參數方面:toRef 接收兩個參數,回傳一個值。toRefs 只會接收一個參數,回傳一個物件。
const x = toRef(物件, 屬性)

toRefs 只接收一個參數,回傳一個物件:

const  x  = toRefs(物件)

readonly

最後,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) 

總結

  • Composition API 是按邏輯來區分程式碼,但 Options API 是按程式碼的性質來區分
  • Options API 在閱讀上會比 Composition API 困難。
  • Composition API 新增多個語法,例如 setUp()ref / reactive 來建立資料等等。

參考資料

ref,toRef,toRefs三者的使用及區別
Vue 3 - Composition API
Mike - 2021 Vue 3 專業職人 入門篇


上一篇
不只懂 Vue 語法:試說明有哪些方式可以全域註冊方法?
下一篇
不只懂 Vue 語法:請用圖片輪播的例子示範 Composition API?
系列文
不只懂 Vue 語法:Vue.js 觀念篇31

尚未有邦友留言

立即登入留言