Dalam konsep aplikasi Vue, "fungsi boleh gabung" (Composables) ialah fungsi yang menggunakan API boleh gubah Vue untuk merangkum dan menggunakan semula logik stateful.
Apabila membina aplikasi bahagian hadapan, kita selalunya perlu menggunakan semula logik untuk tugasan biasa. Contohnya, untuk memformat masa di tempat yang berbeza, kami mungkin mengekstrak fungsi pemformatan tarikh boleh guna semula. Fungsi ini merangkum logik tanpa kewarganegaraan: ia mengambil sedikit input dan mengembalikan output yang dikehendaki serta-merta. Terdapat banyak perpustakaan yang menggunakan semula logik tanpa kewarganegaraan, seperti lodash atau date-fns yang mungkin anda gunakan.
Sebaliknya, logik stateful mengurus keadaan yang berubah dari semasa ke semasa. Contoh mudah ialah menjejaki kedudukan tetikus semasa pada halaman. Dalam aplikasi sebenar, ia juga boleh menjadi logik yang lebih kompleks seperti gerak isyarat sentuh atau status sambungan ke pangkalan data.
Jika kita melaksanakan fungsi penjejakan tetikus secara langsung dalam komponen menggunakan API yang digubah, ia akan kelihatan seperti ini:
<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>
Walau bagaimanapun, jika kita mahu Bagaimana pula dengan menggunakan semula logik yang sama dalam berbilang komponen? Kita boleh mengekstrak logik ini ke dalam fail luaran dalam bentuk fungsi yang terdiri:
// 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 } }
Begini cara ia akan digunakan dalam komponen:
<script setup> import { useMouse } from './mouse.js' const { x, y } = useMouse() </script> <template>Mouse position is at: {{ x }}, {{ y }}</template>
Seperti yang anda lihat, logik teras adalah sama, apa yang kita lakukan ialah mengalihkannya ke fungsi luaran dan mengembalikan keadaan yang perlu didedahkan. Sama seperti dalam komponen, anda boleh menggunakan semua API gubahan dalam fungsi gubahan. Kini, fungsi useMouse()
boleh digunakan semula dengan mudah dalam mana-mana komponen.
Apa yang lebih menarik ialah anda juga boleh menyusun berbilang fungsi gabungan: fungsi gabungan boleh memanggil satu atau lebih fungsi gabungan lain. Ini membolehkan kami menggabungkan berbilang unit yang lebih kecil dan bebas secara logik untuk membentuk logik yang kompleks, sama seperti berbilang komponen digabungkan untuk membentuk keseluruhan aplikasi. Malah, itulah sebabnya kami memutuskan untuk memanggil koleksi API yang melaksanakan corak reka bentuk ini API Boleh Gubah.
Sebagai contoh, kita boleh merangkum logik untuk menambah dan mengosongkan pendengar acara DOM ke dalam fungsi gabungan:
// 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)) }
Dengannya, fungsi gabungan useMouse()
sebelumnya Boleh dipermudahkan kepada:
// 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 } }
PETUA
Setiap tika komponen yang memanggil useMouse()
akan mencipta salinan unik keadaan x
dan y
, jadi mereka tidak akan menjejaskan satu sama lain.
useMouse()
Fungsi gabungan tidak menerima sebarang parameter, jadi mari kita lihat contoh lain fungsi gabungan yang perlu menerima satu parameter. Apabila membuat permintaan data tak segerak, kami selalunya perlu mengendalikan keadaan yang berbeza: pemuatan, kejayaan pemuatan dan kegagalan pemuatan.
<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 }}
Adalah terlalu menyusahkan untuk mengulangi corak ini dalam setiap komponen yang perlu mendapatkan data. Mari kita ekstrak ini ke dalam fungsi tersusun:
// 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 } }
Kini semua yang kita perlukan dalam komponen ialah:
<script setup> import { useFetch } from './fetch.js' const { data, error } = useFetch('...') </script>
useFetch()
Menerima rentetan URL statik sebagai input, jadi ia hanya melaksanakan Satu permintaan dan anda sudah selesai. Tetapi bagaimana jika kita mahu ia meminta semula setiap kali URL berubah? Kemudian kita boleh membenarkannya menerima rujukan sebagai parameter:
// 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 } }
Versi useFetch()
ini kini boleh menerima kedua-dua rentetan URL statik dan rujukan rentetan URL. Apabila ia mengesan bahawa URL ialah rujukan dinamik melalui isRef(), ia akan menggunakan watchEffect() untuk memulakan kesan reaktif. Kesannya akan dilaksanakan serta-merta, dan dalam proses itu, rujukan URL akan dijejaki sebagai pergantungan. Apabila ref URL berubah, data ditetapkan semula dan permintaan dibuat semula.
Konvensyen fungsi gabungan ialah menggunakan penamaan kotak unta dan bermula dengan "penggunaan".
Walaupun tindak balasnya tidak bergantung pada ref, fungsi yang digubah masih boleh menerima parameter ref. Jika anda menulis fungsi tersusun yang akan digunakan oleh pembangun lain, lebih baik anda mengendalikan parameter input dengan keserasian ref dan bukannya nilai mentah sahaja. Fungsi utiliti unref() boleh sangat membantu untuk ini:
import { unref } from 'vue' function useFeature(maybeRef) { // 若 maybeRef 确实是一个 ref,它的 .value 会被返回 // 否则,maybeRef 会被原样返回 const value = unref(maybeRef) }
Jika fungsi yang digubah anda akan menghasilkan kesan reaktif apabila menerima ref sebagai parameter, sila pastikan anda menggunakan watch()
untuk mendengar ref ini secara eksplisit. Atau hubungi watchEffect()
dalam unref()
untuk penjejakan yang betul.
Anda mungkin perasan bahawa kami telah menggunakan ref()
dan bukannya reactive()
dalam fungsi gabungan. Konvensyen kami yang disyorkan ialah fungsi yang digubah sentiasa mengembalikan objek biasa dan tidak reaktif yang mengandungi berbilang rujukan, supaya objek itu kekal responsif selepas dimusnahkan menjadi rujukan dalam komponen:
js
// x 和 y 是两个 ref const { x, y } = useMouse()
Mengembalikan objek reaktif daripada fungsi tersusun menyebabkan sambungan reaktif kepada keadaan dalam fungsi tersusun hilang semasa pemusnahan objek. Sebaliknya, ref mengekalkan sambungan responsif ini.
如果你更希望以对象属性的形式来使用组合式函数中返回的状态,你可以将返回的对象用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 的执行模型有本质上的不同。
Atas ialah kandungan terperinci Apakah kaedah pengaturcaraan fungsi gabungan dalam Vue3. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!