了解 Vue 的 Compsition API


在這篇文章中,我將講講 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 中),則會有非常友好的智能提示。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM