瞭解 Vue 的 Compsition API

前端小蜜蜂發表於2021-03-31

在這篇文章中,我將講講 Vue 的 Composition API 為什麼比之前的 Options API 要好,以及它是如何工作的。

Options API 有什麼問題

首先,這裡不是要大家放棄 Options API,如果你覺得 Options API 還不錯,已經習慣了,就繼續使用它。但我希望你能明白為什麼 Composition API 是一種更好的選擇。

當我剛開始接觸 Vue 時,我喜歡它用匿名物件來構建元件的方式。簡單易學,也很自然。但隨著使用的時間越長,就會遇到一些很奇怪的問題。比如下面這段程式碼:

export default {
  mounted: async function () {
    this.load().then(function () {
      this.name = 'Foo'
    })
  },
}

這裡的第二個this的問題我想很多人都遇到過。在不除錯的情況下,很難知道兩個this為什麼不一樣。我們只知道要解決這個問題,反正得用箭頭函式,例如:

mounted: async function () {
  this.load().then(() => {
    this.name = 'Foo';
  });
}

此外,從data函式返回成員後,Vue 會包裝它們(通常使用Proxy),以便能夠監聽物件的修改。但如果你嘗試直接替換物件(比如陣列),就可能監聽不到物件修改了。比如:

export default {
  data: () => {
    return {
      items: [],
    }
  },
  mounted: async function () {
    this.load().then((result) => {
      this.items = result.data
    })
  },
  methods: {
    doSomeChange() {
      this.items[0].foo = 'foo'
    },
  },
}

這是 Vue 最常見的問題之一。儘管可以很簡單地解決這些問題,但畢竟給學習 Vue 工作原理造成了障礙。

最後,Options API 很難實現元件間的功能共享,為了解決這個問題,Vue 使用了mixins的概念。例如,你可以建立一個 mixin 來擴充套件元件,如下所示:

export default {
  data: function () {
    return { items: [] };
  },

  methods: {
    ...
  }
}

mixin 看起來很像 Options API。它只是對物件進行合併,以允許你新增到特定元件上:

import dataService from "./dataServiceMixin";
export default {
  mixins: [dataService],
  ...

mixin 最大問題是名稱衝突。

鑑於 Options API 這些不足,Composition API 誕生了。

瞭解 Composition API

現在來介紹一下 Composition API。Vue 2 也可以使用 Composition API。首先,你需要引入 Composition API 庫:

> npm i -s @vue/composition-api

要啟用 Composition API,你只需在main.js/ts中註冊一下:

import Vue from 'vue'
import VueCompositionAPI from '@vue/composition-api'
Vue.use(VueCompositionAPI)

讓我們來寫一個 Composition API 元件。首先,元件仍然是匿名物件:

export default {
  setup() {
    return {}
  },
}

Composition API 的關鍵就是這個setup方法,所有所需的資料都從setup中返回。這非常類似於 Options API 的data方法:

export default {
  setup() {
    const name = 'Foo'
    return { name }
  },
}

與其只在return中賦值,不如在setup裡面用一個區域性變數建立。為什麼要這樣做呢?因為 JavaScript 的閉包。讓我們擴充套件一下,再寫一個函式。

export default {
  setup() {
    const name = 'Foo'

    function save() {
      alert(`Name: ${name}`)
    }
    return { name, save }
  },
}

該函式可以訪問name,因為它們在同一個作用域內。這就是 Composition API 的神奇之處。沒有魔術物件,只有 JavaScript。這個模式可以支援更復雜的場景,更靈活,這一切的基礎只是閉包,僅此而已。

在這個例子中name是永遠不會改變的。為了讓 Vue 能夠處理繫結,它需要知道name的變化。在這個例子中,如果你在save()中修改了程式碼來改變名稱,它不會反映在 UI 中:

export default {
  setup() {
    const name = 'Foo'

    function save() {
      name = name + ' saved'
      alert(`Name: ${name}`)
    }
    return { name, save }
  },
}

響應式

要使物件具有響應式,Vue 提供了兩種方法:refreactive包裝器。

你可以將name包裝成一個ref物件:

import { ref } from '@vue/composition-api'
export default {
  setup() {
    const name = ref('Foo')

    function save() {
      name.value = name.value + ' saved'
      alert(`Name: ${name.value}`)
    }
    return { name, save }
  },
}

這是一個簡單的響應式,ref 物件的 value 屬性才是實際的值。

這與簡單物件可以很好地配合,但是具有自身狀態的物件(例如類或陣列),對值的更改是不夠的。你真正需要的是使用一個 proxy 函式以便監聽該物件內部發生的任何更改。

import { ref, reactive } from '@vue/composition-api'
export default {
  setup() {
    const name = ref('Foo')
    const items = reactive([])

    function save() {
      // Change Array
      items.splice(0, items.length)
      name.value = name.value + ' saved'
      alert(`Name: ${name.value}`)
    }
    return { name, save, items }
  },
}

在此示例中,使用了splice更改陣列,Vue 需要了解此更改。你可以通過使用另一個稱為reactive的包裝器包裝該物件(在本例中為陣列)來實現。

Composition API 中的 reactive 包裝器與 Vue2 的 Vue.observable 包裝器相同。

ref 和 react 之間的區別在於,reactive 用 proxy 包裝物件,而 ref 是簡單的值包裝器。

因此,你不需要將返回的 reactive 物件再包裝,不需要像 ref 物件一樣通過 value 屬性訪問其值。

一旦有了 ref 和 reactive 物件,就可以觀察更改。有兩種方法可以做到這一點:watchwatchEffect。watch 允許你監聽單個物件的更改:

 setup() {
    const name = ref("Shawn");
    watch(() => name, (before, after) => {
        console.log("name changes");
    });
    return { name };
},

watch函式有兩個引數:將資料返回到watch的回撥以及發生更改時要呼叫的回撥。它提供兩個引數分別是修改前的值before和修改後的值after

或者,你可以使用watchEffect 監聽 reactive 物件的任何更改。它只需要一個回撥:

watchEffect(() => {
  console.log('Ch..ch...ch...changes.')
})

組合元件

有時候你希望把一個已有元件的功能組合到另一個元件,以便程式碼重用和資料互動。當組合元件時,Composition API 只是使用作用域和閉包解決問題。例如,你可以建立一個簡單的 service 服務:

import axios from 'axios'

export let items = []

export async function load() {
  let result = await axios.get(API_URL)
  items.splice(0, items.length, ...result.data)
}

export function removeItem(item) {
  let index = items.indexOf(item)
  if (index > -1) {
    items.splice(index, 1)
  }
}

然後你可以按需將需要的功能(物件或函式) import 到你的元件中:

import { ref, reactive, onMounted } from '@vue/composition-api'

import { load, items, removeItem } from './dataService'

export default {
  setup() {
    const name = ref('Foo')

    function save() {
      alert(`Name: ${this.name}`)
    }

    return {
      load, // from import
      removeItem, // from import
      name,
      save,
      items, // from import
    }
  },
}

你可以使用 Composition API 更明顯式地組合你的元件。接下來我們來談談元件的使用。

使用 Props

在 Composition API 中定義 Props 的方式與 Options API 相同:

export default {
  name: 'WaitCursor',
  props: {
    message: {
      type: String,
      required: false,
    },
    isBusy: {
      type: Boolean,
      required: false,
    },
  },
  setup() {},
}

你可以通過向setup新增可選引數來訪問 Props:

setup(props) {
    watch(() => props.isBusy, (b,a) => {
        console.log(`isBusy Changed`);
    });
}

此外,你可以新增第二個引數,可以訪問emitslotsattrs物件,和 Options API 上 this 指標上的物件對應:

setup(props, context) {
    watch(() => props.isBusy, (b,a) => context.emit("busy-changed", a));
}

使用元件

在 Vue 中有兩種使用元件的方法。你可以全域性註冊元件(用作通用元件),以便可以在任何地方使用它們:

import WaitCursor from './components/WaitCursor'
Vue.component('wait-cursor', WaitCursor)

通常,你可以將元件新增到正在使用的特定元件中。在 Composition API 中,與 Options API 相同:

import WaitCursor from './components/waitCursor'
import store from './store'
import { computed } from '@vue/composition-api'

export default {
  components: {
    WaitCursor, // Use Component
  },
  setup() {
    const isBusy = computed(() => store.state.isBusy)
    return { isBusy }
  },
}

在 Composition API 中指定了元件後,就可以使用它:

<div>
  <WaitCursor message="Loading..." :isBusy="isBusy"></WaitCursor>
  <div class="row">
    <div class="col">
      <App></App>
    </div>
  </div>
</div>

使用元件的方式與 Options API 中的方式沒有什麼不同。

在 Vue 3 中使用 Composition API

如果你使用的是 Vue 3,則無需單獨引用 Composition API。Vue 3 已經整合好了。該@vue/composition-api庫僅用於 Vue 2 專案。Vue 3 的真正變化是,當你需要匯入 Composition API 時,只需要直接從 Vue 獲取它們:

import {
  ref,
  reactive,
  onMounted,
  watch,
  watchEffect,
  //from "@vue/composition-api";
} from 'vue'

其他一切都一樣。只需從“ vue”匯入即可。在 Vue 3 中,使用 Composition API 只是簡單一些,因為它是預設行為。

Vue 3 的主要目標之一是改善 TypeScript 體驗,所有內容都有型別庫。但是要增加型別安全性,你必須進行一些小的更改才能使用 TypeScript。在建立元件時,需使用defineComponent

import { defineComponent, reactive, onMounted, ref } from "vue";
import Customer from "@/models/Customer";

export default defineComponent({
    name: "App",
    setup() {
      const customers = reactive([] as Array<Customer>);
      const customer = ref(new Customer());
      const isBusy = ref(false);
      const message = ref("");
      return {
        customers, customer, isBusy, message
      }
    }
});

在這些示例中,變數被推斷為型別例項(例如,Customers物件是Reactive<Customer[]>型別的例項)。此外,使用強型別可以降低傳遞錯誤資料的機會。當然,如果你使用 TypeScript(尤其是在 Visual Studio 或 VS Code 中),則會有非常友好的智慧提示。

相關文章