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 class="brush:php;toolbar:false">{{ data }}
如果在每個需要取得資料的元件中都要重複這種模式,那就太繁瑣了。讓我們把它抽取成一個組合式函數:
// 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中使用组合式函数
如果你正在使用选项式 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) } // ...其他选项 }
与其他模式的比较
和Mixin的对比
Vue 2 的用户可能会对mixins选项比较熟悉。它也让我们能够把组件逻辑提取到可复用的单元里。然而 mixins 有三个主要的短板:
不清晰的数据来源:当使用了多个 mixin 时,实例上的数据属性来自哪个 mixin 变得不清晰,这使追溯实现和理解组件行为变得困难。这也是我们推荐在组合式函数中使用 ref + 解构模式的理由:让属性的来源在消费组件时一目了然。
命名空间冲突:多个来自不同作者的 mixin 可能会注册相同的属性名,造成命名冲突。若使用组合式函数,你可以通过在解构变量时对变量进行重命名来避免相同的键名。
隐式的跨 mixin 交流:多个 mixin 需要依赖共享的属性名来进行相互作用,这使得它们隐性地耦合在一起。而一个组合式函数的返回值可以作为另一个组合式函数的参数被传入,像普通函数那样。
基于上述理由,我们不再推荐在 Vue 3 中继续使用 mixin。保留该功能只是为了项目迁移的需求和照顾熟悉它的用户。
和无渲染组件的对比
在组件插槽一章中,我们讨论过了基于作用域插槽的无渲染组件。我们甚至用它实现了一样的鼠标追踪器示例。
组合式函数相对于无渲染组件的主要优势是:组合式函数不会产生额外的组件实例开销。当在整个应用中使用时,由无渲染组件产生的额外组件实例会带来无法忽视的性能开销。
我们推荐在纯逻辑复用时使用组合式函数,在需要同时复用逻辑和视图布局时使用无渲染组件。
和React Hooks的对比
如果你有 React 的开发经验,你可能注意到组合式函数和自定义 React hooks 非常相似。组合式 API 的一部分灵感正来自于 React hooks,Vue 的组合式函数也的确在逻辑组合能力上与 React hooks 相近。然而,Vue 的组合式函数是基于 Vue 细粒度的响应性系统,这和 React hooks 的执行模型有本质上的不同。
以上是Vue3中的組合式函數程式設計方法是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

想要實現頁面的局部刷新,我們只需要實現局部元件(dom)的重新渲染。在Vue中,想要實現這效果最簡單的方式方法就是使用v-if指令。在Vue2中我們除了使用v-if指令讓局部dom的重新渲染,也可以新建一個空白元件,需要刷新局部頁面時跳轉至這個空白元件頁面,然後在空白元件內的beforeRouteEnter守衛中又跳轉回原來的頁面。如下圖所示,如何在Vue3.X中實現點擊刷新按鈕實現紅框範圍內的dom重新加載,並展示對應的加載狀態。由於Vue3.X中scriptsetup語法中組件內守衛只有o

tinymce是一個功能齊全的富文本編輯器插件,但在vue中引入tinymce並不像別的Vue富文本插件一樣那麼順利,tinymce本身並不適配Vue,還需要引入@tinymce/tinymce-vue,並且它是國外的富文本插件,沒有透過中文版本,需要在其官網下載翻譯包(可能需要翻牆)。 1.安裝相關依賴npminstalltinymce-Snpminstall@tinymce/tinymce-vue-S2、下載中文包3.引入皮膚和漢化包在項目public資料夾下新建tinymce資料夾,將下載的

vue3+vite:src使用require動態導入圖片報錯和解決方法vue3+vite動態的導入多張圖片vue3如果使用的是typescript開發,就會出現require引入圖片報錯,requireisnotdefined不能像使用vue2這樣imgUrl:require(' …/assets/test.png')導入,是因為typescript不支援require所以用import導入,下面介紹如何解決:使用awaitimport

Vue實作部落格前端,需要實作markdown的解析,如果有程式碼則需要實作程式碼的高亮。 Vue的markdown解析函式庫很多,如markdown-it、vue-markdown-loader、marked、vue-markdown等。這些庫都大同小異。這裡選用的是marked,程式碼高亮的函式庫選用的是highlight.js。具體實現步驟如下:一、安裝依賴庫在vue專案下開啟命令窗口,並輸入以下命令npminstallmarked-save//marked用於將markdown轉換成htmlnpmins

前言無論是vue還是react,當我們遇到多處重複程式碼的時候,我們都會想著如何重複使用這些程式碼,而不是一個檔案裡充斥著一堆冗餘程式碼。實際上,vue和react都可以透過抽組件的方式來達到復用,但如果遇到一些很小的程式碼片段,你又不想抽到另外一個檔案的情況下,相比而言,react可以在相同文件裡面宣告對應的小元件,或透過renderfunction來實現,如:constDemo:FC=({msg})=>{returndemomsgis{msg}}constApp:FC=()=>{return(

最終效果安裝VueCropper組件yarnaddvue-cropper@next上面的安裝值針對Vue3的,如果時Vue2或想使用其他的方式引用,請訪問它的npm官方地址:官方教程。在元件中引用使用時也很簡單,只需要引入對應的元件和它的樣式文件,我這裡沒有在全域引用,只在我的元件檔案中引入import{userInfoByRequest}from'../js/api' import{VueCropper}from'vue-cropper&

vue3+ts+axios+pinia實作無感刷新1.先在專案中下載aiXos和pinianpmipinia--savenpminstallaxios--save2.封裝axios請求-----下載js-cookienpmiJS-cookie-s//引入aixosimporttype{AxiosRequestConfigig ,AxiosResponse}from"axios";importaxiosfrom'axios';import{ElMess

vue3專案打包發佈到伺服器後存取頁面顯示空白1、處理vue.config.js檔案中的publicPath處理如下:const{defineConfig}=require('@vue/cli-service')module.exports=defineConfig({publicPath :process.env.NODE_ENV==='production'?'./':'/&
