Im Konzept von Vue-Anwendungen ist „Composables“ eine Funktion, die die zusammensetzbare API von Vue verwendet, um zustandsbehaftete Logik zu kapseln und wiederzuverwenden.
Beim Erstellen von Front-End-Anwendungen müssen wir häufig die Logik allgemeiner Aufgaben wiederverwenden. Um beispielsweise die Zeit an verschiedenen Orten zu formatieren, könnten wir eine wiederverwendbare Datumsformatierungsfunktion extrahieren. Diese Funktion kapselt zustandslose Logik: Sie nimmt einige Eingaben entgegen und gibt sofort die gewünschte Ausgabe zurück. Es gibt viele Bibliotheken, die zustandslose Logik wiederverwenden, z. B. lodash oder date-fns, die Sie möglicherweise verwendet haben.
Im Gegensatz dazu ist die Zustandslogik für die Verwaltung von Zuständen verantwortlich, die sich im Laufe der Zeit ändern. Ein einfaches Beispiel ist das Verfolgen der aktuellen Mausposition auf der Seite. In einer realen Anwendung könnte es sich auch um komplexere Logik wie Berührungsgesten oder den Verbindungsstatus zu einer Datenbank handeln.
Wenn wir die Kompositions-API direkt in der Komponente verwenden würden, um die Maus-Tracking-Funktion zu implementieren, würde das so aussehen:
<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>
Aber was wäre, wenn wir dieselbe Logik in mehreren Komponenten wiederverwenden möchten? Wir können diese Logik in Form einer kombinierten Funktion in eine externe Datei extrahieren:
// 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 } }
So wird sie in der Komponente verwendet:
<script setup> import { useMouse } from './mouse.js' const { x, y } = useMouse() </script> <template>Mouse position is at: {{ x }}, {{ y }}</template>
Wie Sie sehen können, ist die Kernlogik genau die gleiche wie wir Ich habe es einfach in eine externe Funktion verschoben und den Status zurückgegeben, der verfügbar gemacht werden muss. Genau wie in Komponenten können Sie in Kompositionsfunktionen alle Kompositions-APIs verwenden. Jetzt kann die Funktionalität von useMouse()
problemlos in jeder Komponente wiederverwendet werden. 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()
// x 和 y 是两个 ref const { x, y } = useMouse()
useMouse()
vereinfacht werden zu: 🎜const mouse = reactive(useMouse()) // mouse.x 链接到了原来的 x ref console.log(mouse.x)
useMouse()
aufruft, erstellt ihre eigenen eindeutigen x
-, y
-Zustandskopien, sodass sie sich nicht gegenseitig beeinflussen. 🎜🎜Beispiel für einen asynchronen Zustand🎜🎜useMouse()
Die kombinierte Funktion empfängt keine Parameter. Schauen wir uns also ein weiteres Beispiel einer kombinierten Funktion an, die einen Parameter empfangen muss. Bei asynchronen Datenanforderungen müssen wir häufig verschiedene Zustände verarbeiten: Laden, Ladeerfolg und Ladefehler. 🎜Mouse position is at: {<!--{C}%3C!%2D%2D%20%2D%2D%3E-->{ mouse.x }}, {<!--{C}%3C!%2D%2D%20%2D%2D%3E-->{ mouse.y }}
<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>
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) } // ...其他选项 }
useFetch()
akzeptiert eine statische URL-Zeichenfolge als Eingabe, sodass nur eine Anfrage ausgeführt wird, und zwar Erledigt. Aber was ist, wenn wir möchten, dass bei jeder URL-Änderung eine erneute Anfrage erfolgt? Dann können wir dafür sorgen, dass gleichzeitig der Empfang von Referenzen als Parameter möglich ist: 🎜rrreee🎜Diese Version von useFetch()
kann jetzt sowohl statische URL-Strings als auch URL-String-Referenzen empfangen. Wenn über isRef() erkannt wird, dass es sich bei der URL um eine dynamische Referenz handelt, wird watchEffect() verwendet, um einen reaktiven Effekt zu starten. Der Effekt wird sofort ausgeführt und dabei wird die Referenz der URL als Abhängigkeit verfolgt. Wenn sich die Referenz der URL ändert, werden die Daten zurückgesetzt und die Anfrage wird erneut gestellt. 🎜🎜Konventionen und Best Practices🎜watch()
verwenden, um dies explizit zu überwachen ref, oder rufen Sie unref()
in watchEffect()
für eine korrekte Nachverfolgung auf. 🎜ref()
anstelle von reactive()
verwendet haben. Unsere empfohlene Konvention besteht darin, dass zusammengesetzte Funktionen immer ein einfaches, nicht reaktives Objekt mit mehreren Referenzen zurückgeben, sodass das Objekt nach der Destrukturierung in eine Referenz in der Komponente weiterhin reagiert: 🎜🎜js🎜rrreee🎜Von zusammengesetzten Funktionen Die Rückgabe eines reaktiven Objekts verursacht das Die reaktive Verbindung zum Zustand innerhalb der zusammengesetzten Funktion geht während der Objektdestrukturierung verloren. Im Gegensatz dazu hält ref diese reaktionsfähige Verbindung aufrecht. 🎜如果你更希望以对象属性的形式来使用组合式函数中返回的状态,你可以将返回的对象用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 的执行模型有本质上的不同。
Das obige ist der detaillierte Inhalt vonWas ist die kombinierte Funktionsprogrammiermethode in Vue3?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!