您好,登錄后才能下訂單哦!
這篇“Vue3中的組合式函數編程方法是什么”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Vue3中的組合式函數編程方法是什么”文章吧。
在 Vue 應用的概念中,“組合式函數”(Composables) 是一個利用 Vue 的組合式 API 來封裝和復用有狀態邏輯的函數。
當構建前端應用時,我們常常需要復用公共任務的邏輯。例如為了在不同地方格式化時間,我們可能會抽取一個可復用的日期格式化函數。這個函數封裝了無狀態的邏輯:它在接收一些輸入后立刻返回所期望的輸出。復用無狀態邏輯的庫有很多,比如你可能已經用過的lodash或是date-fns。
相比之下,有狀態邏輯負責管理會隨時間而變化的狀態。一個簡單的例子是跟蹤當前鼠標在頁面中的位置。在實際應用中,也可能是像觸摸手勢或與數據庫的連接狀態這樣的更復雜的邏輯。
如果我們要直接在組件中使用組合式 API 實現鼠標跟蹤功能,它會是這樣的:
<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>
但是,如果我們想在多個組件中復用這個相同的邏輯呢?我們可以把這個邏輯以一個組合式函數的形式提取到外部文件中:
// mouse.js import { ref, onMounted, onUnmounted } from 'vue' // 按照慣例,組合式函數名以“use”開頭 export function useMouse() { // 被組合式函數封裝和管理的狀態 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)) // 通過返回值暴露所管理的狀態 return { x, y } }
下面是它在組件中使用的方式:
<script setup> import { useMouse } from './mouse.js' const { x, y } = useMouse() </script> <template>Mouse position is at: {{ x }}, {{ y }}</template>
如你所見,核心邏輯完全一致,我們做的只是把它移到一個外部函數中去,并返回需要暴露的狀態。和在組件中一樣,你也可以在組合式函數中使用所有的組合式 API。現在,useMouse()
的功能可以在任何組件中輕易復用了。
更酷的是,你還可以嵌套多個組合式函數:一個組合式函數可以調用一個或多個其他的組合式函數。這使得我們可以像使用多個組件組合成整個應用一樣,用多個較小且邏輯獨立的單元來組合形成復雜的邏輯。實際上,這正是為什么我們決定將實現了這一設計模式的 API 集合命名為組合式 API。
舉例來說,我們可以將添加和清除 DOM 事件監聽器的邏輯也封裝進一個組合式函數中:
// event.js import { onMounted, onUnmounted } from 'vue' export function useEventListener(target, event, callback) { // 如果你想的話, // 也可以用字符串形式的 CSS 選擇器來尋找目標 DOM 元素 onMounted(() => target.addEventListener(event, callback)) onUnmounted(() => target.removeEventListener(event, callback)) }
有了它,之前的useMouse()
組合式函數可以被簡化為:
// 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
每一個調用useMouse()
的組件實例會創建其獨有的x
、y
狀態拷貝,因此他們不會互相影響。
useMouse()
組合式函數沒有接收任何參數,因此讓我們再來看一個需要接收一個參數的組合式函數示例。在做異步數據請求時,我們常常需要處理不同的狀態:加載中、加載成功和加載失敗。
<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>
如果在每個需要獲取數據的組件中都要重復這種模式,那就太繁瑣了。讓我們把它抽取成一個組合式函數:
// 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 } }
現在我們在組件里只需要:
<script setup> import { useFetch } from './fetch.js' const { data, error } = useFetch('...') </script>
useFetch()
接收一個靜態的 URL 字符串作為輸入,所以它只執行一次請求,然后就完成了。但如果我們想讓它在每次 URL 變化時都重新請求呢?那我們可以讓它同時允許接收 ref 作為參數:
// fetch.js import { ref, isRef, unref, watchEffect } from 'vue' export function useFetch(url) { const data = ref(null) const error = ref(null) function doFetch() { // 在請求之前重設狀態... 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,那么啟動一個響應式的請求 watchEffect(doFetch) } else { // 否則只請求一次 // 避免監聽器的額外開銷 doFetch() } return { data, error } }
這個版本的useFetch()
現在同時可以接收靜態的 URL 字符串和 URL 字符串的 ref。當通過isRef()檢測到 URL 是一個動態 ref 時,它會使用watchEffect()啟動一個響應式的 effect。該 effect 會立刻執行一次,并在此過程中將 URL 的 ref 作為依賴進行跟蹤。當 URL 的 ref 發生改變時,數據就會被重置,并重新請求。
組合式函數約定用駝峰命名法命名,并以“use”作為開頭。
盡管其響應性不依賴 ref,組合式函數仍可接收 ref 參數。如果編寫的組合式函數會被其他開發者使用,你最好在處理輸入參數時兼容 ref 而不只是原始的值。unref()工具函數會對此非常有幫助:
import { unref } from 'vue' function useFeature(maybeRef) { // 若 maybeRef 確實是一個 ref,它的 .value 會被返回 // 否則,maybeRef 會被原樣返回 const value = unref(maybeRef) }
如果你的組合式函數在接收 ref 為參數時會產生響應式 effect,請確保使用watch()
顯式地監聽此 ref,或者在watchEffect()
中調用unref()
來進行正確的追蹤。
你可能已經注意到了,我們一直在組合式函數中使用ref()
而不是reactive()
。我們推薦的約定是組合式函數始終返回一個包含多個 ref 的普通的非響應式對象,這樣該對象在組件中被解構為 ref 之后仍可以保持響應性:
js
// x 和 y 是兩個 ref const { x, y } = useMouse()
從組合式函數返回一個響應式對象會導致在對象解構過程中丟失與組合式函數內狀態的響應性連接。與之相反,ref 則可以維持這一響應性連接。
如果你更希望以對象屬性的形式來使用組合式函數中返回的狀態,你可以將返回的對象用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 }}
在組合式函數中的確可以執行副作用 (例如:添加 DOM 事件監聽器或者請求數據),但請注意以下規則:
如果你的應用用到了服務端渲染(SSR),請確保在組件掛載后才調用的生命周期鉤子中執行 DOM 相關的副作用,例如:onMounted()
。這些鉤子僅會在瀏覽器中被調用,因此可以確保能訪問到 DOM。
確保在onUnmounted()
時清理副作用。舉例來說,如果一個組合式函數設置了一個事件監聽器,它就應該在onUnmounted()
中被移除 (就像我們在useMouse()
示例中看到的一樣)。當然也可以像之前的useEventListener()
示例那樣,使用一個組合式函數來自動幫你做這些事。
組合式函數在<script setup>
或setup()
鉤子中,應始終被同步地調用。在某些場景下,你也可以在像onMounted()
這樣的生命周期鉤子中使用他們。
這個限制是為了讓 Vue 能夠確定當前正在被執行的到底是哪個組件實例,只有能確認當前組件實例,才能夠:
將生命周期鉤子注冊到該組件實例上
將計算屬性和監聽器注冊到該組件實例上,以便在該組件被卸載時停止監聽,避免內存泄漏。
TIP
<script setup>
是唯一在調用await
之后仍可調用組合式函數的地方。編譯器會在異步操作之后自動為你恢復當前的組件實例。
抽取組合式函數不僅是為了復用,也是為了代碼組織。隨著組件復雜度的增高,你可能會最終發現組件多得難以查詢和理解。組合式 API 會給予你足夠的靈活性,讓你可以基于邏輯問題將組件代碼拆分成更小的函數:
<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>
在某種程度上,你可以將這些提取出的組合式函數看作是可以相互通信的組件范圍內的服務。
如果你正在使用選項式 API,組合式函數必須在setup()
中調用。且其返回的綁定必須在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) } // ...其他選項 }
Vue 2 的用戶可能會對mixins選項比較熟悉。它也讓我們能夠把組件邏輯提取到可復用的單元里。然而 mixins 有三個主要的短板:
不清晰的數據來源:當使用了多個 mixin 時,實例上的數據屬性來自哪個 mixin 變得不清晰,這使追溯實現和理解組件行為變得困難。這也是我們推薦在組合式函數中使用 ref + 解構模式的理由:讓屬性的來源在消費組件時一目了然。
命名空間沖突:多個來自不同作者的 mixin 可能會注冊相同的屬性名,造成命名沖突。若使用組合式函數,你可以通過在解構變量時對變量進行重命名來避免相同的鍵名。
隱式的跨 mixin 交流:多個 mixin 需要依賴共享的屬性名來進行相互作用,這使得它們隱性地耦合在一起。而一個組合式函數的返回值可以作為另一個組合式函數的參數被傳入,像普通函數那樣。
基于上述理由,我們不再推薦在 Vue 3 中繼續使用 mixin。保留該功能只是為了項目遷移的需求和照顧熟悉它的用戶。
在組件插槽一章中,我們討論過了基于作用域插槽的無渲染組件。我們甚至用它實現了一樣的鼠標追蹤器示例。
組合式函數相對于無渲染組件的主要優勢是:組合式函數不會產生額外的組件實例開銷。當在整個應用中使用時,由無渲染組件產生的額外組件實例會帶來無法忽視的性能開銷。
我們推薦在純邏輯復用時使用組合式函數,在需要同時復用邏輯和視圖布局時使用無渲染組件。
如果你有 React 的開發經驗,你可能注意到組合式函數和自定義 React hooks 非常相似。組合式 API 的一部分靈感正來自于 React hooks,Vue 的組合式函數也的確在邏輯組合能力上與 React hooks 相近。然而,Vue 的組合式函數是基于 Vue 細粒度的響應性系統,這和 React hooks 的執行模型有本質上的不同。
以上就是關于“Vue3中的組合式函數編程方法是什么”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。