Vue3 举例说明如何编写一个自定义组合式函数(与 Mixins 相比的优势)

Vue3自定义组合式函数(Composable)通过函数封装实现逻辑复用,相比Vue2的Mixins具有显著优势。


文章展示了useMouse和useFetch两个典型示例,演示如何封装鼠标位置追踪和数据请求逻辑。


关键优势包括:

  1. 避免命名冲突,通过解构赋值可重命名;

  2. 来源清晰,每个状态和方法都有明确出处;

  3. 支持灵活组合,可条件使用;

  4. 提供更好的TypeScript支持。


Composable将相关逻辑集中管理,解决了Mixins的隐式依赖和代码分散问题,成为Vue3推荐的逻辑复用方案。


Vue3 举例说明如何编写一个自定义组合式函数 (Composable),例如 useMouse, useFetch,并理解其与 Mixins 相比的优势(命名冲突、来源清晰)


Vue3 自定义组合式函数 (Composable)

1. useMouse - 监听鼠标位置

html 复制代码
<!-- useMouse.js -->
import { ref, onMounted, onUnmounted } from 'vue'

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 }
}

2. useFetch - 数据请求

html 复制代码
<!-- useFetch.js -->
import { ref, watchEffect, toValue } from 'vue'

export function useFetch(url, options = {}) {
  const data = ref(null)
  const error = ref(null)
  const loading = ref(false)
  
  async function execute() {
    loading.value = true
    error.value = null
    
    try {
      const response = await fetch(toValue(url), toValue(options))
      if (!response.ok) throw new Error('请求失败')
      data.value = await response.json()
    } catch (err) {
      error.value = err.message
    } finally {
      loading.value = false
    }
  }
  
  // 立即执行
  watchEffect(() => {
    if (toValue(url)) execute()
  })
  
  return { data, error, loading, execute, refresh: execute }
}

3. 在组件中使用

html 复制代码
<!-- MouseTracker.vue -->
<template>
  <div>
    <h2>鼠标追踪器</h2>
    <p>鼠标位置: X={{ x }}, Y={{ y }}</p>
    
    <h2>用户数据</h2>
    <div v-if="loading">加载中...</div>
    <div v-else-if="error">错误: {{ error }}</div>
    <pre v-else>{{ data }}</pre>
    <button @click="refresh">刷新</button>
  </div>
</template>

<script setup>
import { useMouse } from './composables/useMouse'
import { useFetch } from './composables/useFetch'

// 使用鼠标追踪 - 完全独立的状态
const { x, y } = useMouse()

// 使用数据请求 - 完全独立的状态
const { data, error, loading, refresh } = useFetch(
  'https://jsonplaceholder.typicode.com/users/1'
)

// 可以使用多个 useFetch,互不干扰
const { data: posts } = useFetch(
  'https://jsonplaceholder.typicode.com/posts'
)
</script>

4. 组合多个 Composable

html 复制代码
<!-- AdvancedExample.vue -->
<script setup>
import { computed } from 'vue'
import { useMouse } from './composables/useMouse'
import { useFetch } from './composables/useFetch'
import { useLocalStorage } from './composables/useLocalStorage'

// 组合多个 composables
const { x, y } = useMouse()
const { data: weather } = useFetch(
  computed(() => `https://api.weather.com/${x.value}/${y.value}`)
)
const { value: settings } = useLocalStorage('app-settings', { theme: 'dark' })

// 基于多个来源创建新状态
const mouseInfo = computed(() => `鼠标位置: (${x.value}, ${y.value})`)
</script>

Composable vs Mixins 优势对比


Mixins 的问题示例

javascript 复制代码
// ❌ Mixins 的问题
// mixin1.js
export const mouseMixin = {
  data() {
    return {
      x: 0,
      y: 0
    }
  },
  mounted() {
    window.addEventListener('mousemove', this.update)
  },
  methods: {
    update(event) {
      this.x = event.pageX
      this.y = event.pageY
    }
  }
}

// mixin2.js
export const fetchMixin = {
  data() {
    return {
      data: null,
      loading: false,
      error: null,
      x: '冲突的数据' // 命名冲突!
    }
  }
}

// 组件中使用
export default {
  mixins: [mouseMixin, fetchMixin], // 冲突:x 被覆盖
  mounted() {
    console.log(this.x) // 哪个 x?不清楚!
    // 难以追踪数据来源
  }
}

Composable 的优势对比表

方面 Mixins Composable
命名冲突 ❌ 所有属性和方法合并到同一个实例,容易冲突 ✅ 通过解构赋值,完全避免冲突
来源清晰 ❌ 不清楚属性来自哪个 mixin ✅ 明确知道每个状态的来源
逻辑复用 ⚠️ 只能合并到组件选项,难以组合 ✅ 函数式组合,灵活组合多个 composable
类型推导 ❌ TypeScript 支持差 ✅ 完美的类型推导
隐式依赖 ❌ 混入之间可能有隐式依赖 ✅ 显式传递参数,依赖清晰
代码组织 ❌ 逻辑分散在不同的选项区域 ✅ 相关逻辑集中在一起
Tree Shaking ❌ 无法进行 tree shaking ✅ 支持 tree shaking

实际对比示例

html 复制代码
<!-- ✅ 使用 Composable - 清晰、无冲突 -->
<script setup>
import { useMouse } from './composables/useMouse'
import { useFetch } from './composables/useFetch'
import { useCounter } from './composables/useCounter'

// 来源清晰,可以重命名避免冲突
const { x: mouseX, y: mouseY } = useMouse()
const { data: userData } = useFetch('/api/user')
const { data: productData } = useFetch('/api/products')
const { count, increment } = useCounter()

// 可以基于它们创建派生状态
const totalClicks = computed(() => count.value * 2)
</script>

<!-- ❌ 使用 Mixins - 混乱、易冲突 -->
<script>
import mouseMixin from './mixins/mouse'
import fetchMixin from './mixins/fetch'
import counterMixin from './mixins/counter'

export default {
  mixins: [mouseMixin, fetchMixin, counterMixin],
  computed: {
    // 这里的 x、data、count 来自哪里?不清楚!
    // 如果多个 mixin 有同名属性,会发生覆盖!
    totalClicks() {
      return this.count * 2 // count 来自哪个 mixin?
    }
  }
}
</script>

高级特性:Composable 的额外优势

javascript 复制代码
// ✅ Composable 支持响应式参数
export function useCustomFetch(url, options = {}) {
  const data = ref(null)
  
  // 可以响应式地重新请求
  watchEffect(() => {
    const currentUrl = toValue(url)
    if (currentUrl) {
      fetch(currentUrl).then(res => res.json()).then(data.value = res)
    }
  })
  
  return { data }
}

// ✅ Composable 可以条件性使用
const shouldTrack = ref(false)
const { x, y } = shouldTrack.value ? useMouse() : { x: ref(0), y: ref(0) }

// ✅ Composable 可以相互组合
export function useUserProfile(userId) {
  const { data: user } = useFetch(`/api/users/${userId}`)
  const { data: posts } = useFetch(`/api/users/${userId}/posts`)
  
  return { user, posts }
}

总结

Composable 的核心优势:

  1. 清晰的来源 - 每个状态和方法都有明确的来源函数

  2. 无命名冲突 - 通过解构赋值可以任意重命名

  3. 灵活组合 - 函数式组合比对象合并更灵活

  4. 更好的类型支持 - 完美的 TypeScript 支持

  5. 逻辑聚焦 - 相关代码组织在一起,更易维护


这些特性使 Composable 成为 Vue3 中逻辑复用的最佳实践,完全替代了 Vue2 中的 Mixins 模式。

相关推荐
小李子呢021110 小时前
前端八股Vue---Vue2和Vue3的区别,set up的用法
前端·javascript·vue.js
邂逅星河浪漫10 小时前
【银行内网开发-管理端】Vue管理端+Auth后台开发+Nginx配置+Linux部署(详细解析)
linux·javascript·css·vue.js·nginx·html·前后端联调
一 乐10 小时前
电影院|基于springboot + vue电影院购票管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·电影院购票管理管理系统
奔跑的呱呱牛10 小时前
@giszhc/vue-page-motion:Vue3 路由动画怎么做才“丝滑”?(附在线示例)
前端·javascript·vue.js
一 乐13 小时前
旅游|基于springboot + vue旅游信息推荐系统(源码+数据库+文档)
java·vue.js·spring boot·论文·旅游·毕设·旅游信息推荐系统
最逗前端小白鼠13 小时前
vue3 数据响应式遇到的问题
前端·vue.js
卤蛋fg614 小时前
vxe-table 自定义数字行主键,解决默认字符串主键与后端类型不匹配问题
vue.js
岁月宁静14 小时前
都知道AI大模型能生成文本内容,那你知道大模型是怎样生成文本的吗?
前端·vue.js·人工智能
|晴 天|15 小时前
我如何用Vue 3打造一个现代化个人博客系统(性能提升52%)
前端·javascript·vue.js
yuqifang16 小时前
vue3+typescript+vite封装自己的UI组件库并上传至npm
vue.js·arkui