欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入探討Vue?3中的組合式函數(shù)編程方式

 更新時間:2023年05月05日 09:35:38   作者:自xing且樂觀~  
Vue?3中引入了組合式函數(shù)編程方式,可以更好地實現(xiàn)代碼的復(fù)用和可維護性。通過定義可組合的函數(shù),可以將組件的邏輯和狀態(tài)進行拆分和組合,實現(xiàn)更靈活的代碼組織方式。同時,組合式函數(shù)也支持響應(yīng)式數(shù)據(jù)和生命周期鉤子函數(shù),更加貼近Vue開發(fā)的實際場景

什么是組合式函數(shù)?

在 Vue 應(yīng)用的概念中,“組合式函數(shù)”(Composables) 是一個利用 Vue 的組合式 API 來封裝和復(fù)用有狀態(tài)邏輯的函數(shù)。

當(dāng)構(gòu)建前端應(yīng)用時,我們常常需要復(fù)用公共任務(wù)的邏輯。例如為了在不同地方格式化時間,我們可能會抽取一個可復(fù)用的日期格式化函數(shù)。這個函數(shù)封裝了無狀態(tài)的邏輯:它在接收一些輸入后立刻返回所期望的輸出。復(fù)用無狀態(tài)邏輯的庫有很多,比如你可能已經(jīng)用過的lodash或是date-fns

相比之下,有狀態(tài)邏輯負(fù)責(zé)管理會隨時間而變化的狀態(tài)。一個簡單的例子是跟蹤當(dāng)前鼠標(biāo)在頁面中的位置。在實際應(yīng)用中,也可能是像觸摸手勢或與數(shù)據(jù)庫的連接狀態(tài)這樣的更復(fù)雜的邏輯。

鼠標(biāo)跟蹤器示例?

如果我們要直接在組件中使用組合式 API 實現(xiàn)鼠標(biāo)跟蹤功能,它會是這樣的:

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const x = ref(0)
const y = ref(0)
function update(event) {
  x.value = event.pageX
  y.value = event.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
</script>
<template>Mouse position is at: {{ x }}, {{ y }}</template>

但是,如果我們想在多個組件中復(fù)用這個相同的邏輯呢?我們可以把這個邏輯以一個組合式函數(shù)的形式提取到外部文件中:

// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'
// 按照慣例,組合式函數(shù)名以“use”開頭
export function useMouse() {
  // 被組合式函數(shù)封裝和管理的狀態(tài)
  const x = ref(0)
  const y = ref(0)
  // 組合式函數(shù)可以隨時更改其狀態(tài)。
  function update(event) {
    x.value = event.pageX
    y.value = event.pageY
  }
  // 一個組合式函數(shù)也可以掛靠在所屬組件的生命周期上
  // 來啟動和卸載副作用
  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))
  // 通過返回值暴露所管理的狀態(tài)
  return { x, y }
}

下面是它在組件中使用的方式:

<script setup>
import { useMouse } from './mouse.js'
const { x, y } = useMouse()
</script>
<template>Mouse position is at: {{ x }}, {{ y }}</template>

如你所見,核心邏輯完全一致,我們做的只是把它移到一個外部函數(shù)中去,并返回需要暴露的狀態(tài)。和在組件中一樣,你也可以在組合式函數(shù)中使用所有的組合式 API?,F(xiàn)在,useMouse()的功能可以在任何組件中輕易復(fù)用了。

更酷的是,你還可以嵌套多個組合式函數(shù):一個組合式函數(shù)可以調(diào)用一個或多個其他的組合式函數(shù)。這使得我們可以像使用多個組件組合成整個應(yīng)用一樣,用多個較小且邏輯獨立的單元來組合形成復(fù)雜的邏輯。實際上,這正是為什么我們決定將實現(xiàn)了這一設(shè)計模式的 API 集合命名為組合式 API。

舉例來說,我們可以將添加和清除 DOM 事件監(jiān)聽器的邏輯也封裝進一個組合式函數(shù)中:

// event.js
import { onMounted, onUnmounted } from 'vue'
export function useEventListener(target, event, callback) {
  // 如果你想的話,
  // 也可以用字符串形式的 CSS 選擇器來尋找目標(biāo) DOM 元素
  onMounted(() => target.addEventListener(event, callback))
  onUnmounted(() => target.removeEventListener(event, callback))
}

有了它,之前的useMouse()組合式函數(shù)可以被簡化為:

// mouse.js
import { ref } from 'vue'
import { useEventListener } from './event'
export function useMouse() {
  const x = ref(0)
  const y = ref(0)
  useEventListener(window, 'mousemove', (event) => {
    x.value = event.pageX
    y.value = event.pageY
  })
  return { x, y }
}

TIP

每一個調(diào)用useMouse()的組件實例會創(chuàng)建其獨有的x、y狀態(tài)拷貝,因此他們不會互相影響。

異步狀態(tài)示例?

useMouse()組合式函數(shù)沒有接收任何參數(shù),因此讓我們再來看一個需要接收一個參數(shù)的組合式函數(shù)示例。在做異步數(shù)據(jù)請求時,我們常常需要處理不同的狀態(tài):加載中、加載成功和加載失敗。

<script setup>
import { ref } from 'vue'
const data = ref(null)
const error = ref(null)
fetch('...')
  .then((res) => res.json())
  .then((json) => (data.value = json))
  .catch((err) => (error.value = err))
</script>
<template>
  <div v-if="error">Oops! Error encountered: {{ error.message }}</div>
  <div v-else-if="data">
    Data loaded:
    <pre>{{ data }}</pre>
  </div>
  <div v-else>Loading...</div>
</template>

如果在每個需要獲取數(shù)據(jù)的組件中都要重復(fù)這種模式,那就太繁瑣了。讓我們把它抽取成一個組合式函數(shù):

// fetch.js
import { ref } from 'vue'
export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)
  fetch(url)
    .then((res) => res.json())
    .then((json) => (data.value = json))
    .catch((err) => (error.value = err))
  return { data, error }
}

現(xiàn)在我們在組件里只需要:

<script setup>
import { useFetch } from './fetch.js'
const { data, error } = useFetch('...')
</script>

useFetch()接收一個靜態(tài)的 URL 字符串作為輸入,所以它只執(zhí)行一次請求,然后就完成了。但如果我們想讓它在每次 URL 變化時都重新請求呢?那我們可以讓它同時允許接收 ref 作為參數(shù):

// fetch.js
import { ref, isRef, unref, watchEffect } from 'vue'
export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)
  function doFetch() {
    // 在請求之前重設(shè)狀態(tài)...
    data.value = null
    error.value = null
    // unref() 解包可能為 ref 的值
    fetch(unref(url))
      .then((res) => res.json())
      .then((json) => (data.value = json))
      .catch((err) => (error.value = err))
  }
  if (isRef(url)) {
    // 若輸入的 URL 是一個 ref,那么啟動一個響應(yīng)式的請求
    watchEffect(doFetch)
  } else {
    // 否則只請求一次
    // 避免監(jiān)聽器的額外開銷
    doFetch()
  }
  return { data, error }
}

這個版本的useFetch()現(xiàn)在同時可以接收靜態(tài)的 URL 字符串和 URL 字符串的 ref。當(dāng)通過isRef()檢測到 URL 是一個動態(tài) ref 時,它會使用watchEffect()啟動一個響應(yīng)式的 effect。該 effect 會立刻執(zhí)行一次,并在此過程中將 URL 的 ref 作為依賴進行跟蹤。當(dāng) URL 的 ref 發(fā)生改變時,數(shù)據(jù)就會被重置,并重新請求。

約定和最佳實踐

? 命名?

組合式函數(shù)約定用駝峰命名法命名,并以“use”作為開頭。

輸入?yún)?shù)?

盡管其響應(yīng)性不依賴 ref,組合式函數(shù)仍可接收 ref 參數(shù)。如果編寫的組合式函數(shù)會被其他開發(fā)者使用,你最好在處理輸入?yún)?shù)時兼容 ref 而不只是原始的值。unref()工具函數(shù)會對此非常有幫助:

import { unref } from 'vue'
function useFeature(maybeRef) {
  // 若 maybeRef 確實是一個 ref,它的 .value 會被返回
  // 否則,maybeRef 會被原樣返回
  const value = unref(maybeRef)
}

如果你的組合式函數(shù)在接收 ref 為參數(shù)時會產(chǎn)生響應(yīng)式 effect,請確保使用watch()顯式地監(jiān)聽此 ref,或者在watchEffect()中調(diào)用unref()來進行正確的追蹤。

返回值?

你可能已經(jīng)注意到了,我們一直在組合式函數(shù)中使用ref()而不是reactive()。我們推薦的約定是組合式函數(shù)始終返回一個包含多個 ref 的普通的非響應(yīng)式對象,這樣該對象在組件中被解構(gòu)為 ref 之后仍可以保持響應(yīng)性:

js

// x 和 y 是兩個 ref
const { x, y } = useMouse()

從組合式函數(shù)返回一個響應(yīng)式對象會導(dǎo)致在對象解構(gòu)過程中丟失與組合式函數(shù)內(nèi)狀態(tài)的響應(yīng)性連接。與之相反,ref 則可以維持這一響應(yīng)性連接。

如果你更希望以對象屬性的形式來使用組合式函數(shù)中返回的狀態(tài),你可以將返回的對象用reactive()包裝一次,這樣其中的 ref 會被自動解包,例如:

const mouse = reactive(useMouse())
// mouse.x 鏈接到了原來的 x ref
console.log(mouse.x)
Mouse position is at: {<!--{C}%3C!%2D%2D%20%2D%2D%3E-->{ mouse.x }}, {<!--{C}%3C!%2D%2D%20%2D%2D%3E-->{ mouse.y }}

副作用?

在組合式函數(shù)中的確可以執(zhí)行副作用 (例如:添加 DOM 事件監(jiān)聽器或者請求數(shù)據(jù)),但請注意以下規(guī)則:

  • 如果你的應(yīng)用用到了服務(wù)端渲染(SSR),請確保在組件掛載后才調(diào)用的生命周期鉤子中執(zhí)行 DOM 相關(guān)的副作用,例如:onMounted()。這些鉤子僅會在瀏覽器中被調(diào)用,因此可以確保能訪問到 DOM。
  • 確保在onUnmounted()時清理副作用。舉例來說,如果一個組合式函數(shù)設(shè)置了一個事件監(jiān)聽器,它就應(yīng)該在onUnmounted()中被移除 (就像我們在useMouse()示例中看到的一樣)。當(dāng)然也可以像之前的useEventListener()示例那樣,使用一個組合式函數(shù)來自動幫你做這些事。

使用限制?

組合式函數(shù)在<script setup>setup()鉤子中,應(yīng)始終被同步地調(diào)用。在某些場景下,你也可以在像onMounted()這樣的生命周期鉤子中使用他們。

這個限制是為了讓 Vue 能夠確定當(dāng)前正在被執(zhí)行的到底是哪個組件實例,只有能確認(rèn)當(dāng)前組件實例,才能夠:

  • 將生命周期鉤子注冊到該組件實例上
  • 將計算屬性和監(jiān)聽器注冊到該組件實例上,以便在該組件被卸載時停止監(jiān)聽,避免內(nèi)存泄漏。

TIP

<script setup>是唯一在調(diào)用await之后仍可調(diào)用組合式函數(shù)的地方。編譯器會在異步操作之后自動為你恢復(fù)當(dāng)前的組件實例。

通過抽取組合式函數(shù)改善代碼結(jié)構(gòu)

抽取組合式函數(shù)不僅是為了復(fù)用,也是為了代碼組織。隨著組件復(fù)雜度的增高,你可能會最終發(fā)現(xiàn)組件多得難以查詢和理解。組合式 API 會給予你足夠的靈活性,讓你可以基于邏輯問題將組件代碼拆分成更小的函數(shù):

<script setup>
import { useFeatureA } from './featureA.js'
import { useFeatureB } from './featureB.js'
import { useFeatureC } from './featureC.js'
const { foo, bar } = useFeatureA()
const { baz } = useFeatureB(foo)
const { qux } = useFeatureC(baz)
</script>

在某種程度上,你可以將這些提取出的組合式函數(shù)看作是可以相互通信的組件范圍內(nèi)的服務(wù)。

選項式API中使用組合式函數(shù)?

如果你正在使用選項式 API,組合式函數(shù)必須在setup()中調(diào)用。且其返回的綁定必須在setup()中返回,以便暴露給this及其模板:

import { useMouse } from './mouse.js'
import { useFetch } from './fetch.js'
export default {
  setup() {
    const { x, y } = useMouse()
    const { data, error } = useFetch('...')
    return { x, y, data, error }
  },
  mounted() {
    // setup() 暴露的屬性可以在通過 `this` 訪問到
    console.log(this.x)
  }
  // ...其他選項
}

與其他模式的比較

和Mixin的對比?

Vue 2 的用戶可能會對mixins選項比較熟悉。它也讓我們能夠把組件邏輯提取到可復(fù)用的單元里。然而 mixins 有三個主要的短板:

  • 不清晰的數(shù)據(jù)來源:當(dāng)使用了多個 mixin 時,實例上的數(shù)據(jù)屬性來自哪個 mixin 變得不清晰,這使追溯實現(xiàn)和理解組件行為變得困難。這也是我們推薦在組合式函數(shù)中使用 ref + 解構(gòu)模式的理由:讓屬性的來源在消費組件時一目了然。
  • 命名空間沖突:多個來自不同作者的 mixin 可能會注冊相同的屬性名,造成命名沖突。若使用組合式函數(shù),你可以通過在解構(gòu)變量時對變量進行重命名來避免相同的鍵名。
  • 隱式的跨 mixin 交流:多個 mixin 需要依賴共享的屬性名來進行相互作用,這使得它們隱性地耦合在一起。而一個組合式函數(shù)的返回值可以作為另一個組合式函數(shù)的參數(shù)被傳入,像普通函數(shù)那樣。

基于上述理由,我們不再推薦在 Vue 3 中繼續(xù)使用 mixin。保留該功能只是為了項目遷移的需求和照顧熟悉它的用戶。

和無渲染組件的對比?

在組件插槽一章中,我們討論過了基于作用域插槽的無渲染組件。我們甚至用它實現(xiàn)了一樣的鼠標(biāo)追蹤器示例。

組合式函數(shù)相對于無渲染組件的主要優(yōu)勢是:組合式函數(shù)不會產(chǎn)生額外的組件實例開銷。當(dāng)在整個應(yīng)用中使用時,由無渲染組件產(chǎn)生的額外組件實例會帶來無法忽視的性能開銷。

我們推薦在純邏輯復(fù)用時使用組合式函數(shù),在需要同時復(fù)用邏輯和視圖布局時使用無渲染組件。

和React Hooks的對比?

如果你有 React 的開發(fā)經(jīng)驗,你可能注意到組合式函數(shù)和自定義 React hooks 非常相似。組合式 API 的一部分靈感正來自于 React hooks,Vue 的組合式函數(shù)也的確在邏輯組合能力上與 React hooks 相近。然而,Vue 的組合式函數(shù)是基于 Vue 細粒度的響應(yīng)性系統(tǒng),這和 React hooks 的執(zhí)行模型有本質(zhì)上的不同。

到此這篇關(guān)于深入探討Vue 3中的組合式函數(shù)編程方式的文章就介紹到這了,更多相關(guān)Vue組合式函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論