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 模式。

相关推荐
小马_xiaoen2 小时前
Vue 3 + TS 实战:手写 v-no-emoji 自定义指令,彻底禁止输入框表情符号!
前端·javascript·vue.js
Highcharts.js2 小时前
Highcharts Gantt 实战:从框架集成到高级功能应用-打造现代化、交互式项目进度管理图表
前端·javascript·vue.js·信息可视化·免费
终端鹿2 小时前
setup 语法糖从 0 到 1 实战教程
前端·javascript·vue.js
英俊潇洒美少年3 小时前
Vue3 中使用 Proxy 的 8 个注意事项
vue.js
炒毛豆3 小时前
Vue 3 公共组件从封装到全局注册的极简指南
前端·javascript·vue.js
踩着两条虫3 小时前
VTJ.PRO 在线应用开发平台前端架构
前端·vue.js·ai编程
踩着两条虫3 小时前
VTJ.PRO 在线应用开发平台部署与运维
前端·vue.js·人工智能
_院长大人_3 小时前
构建一个 Vue 基于el-input的磨损区间选择器组件 —— WearRangeSelector
前端·javascript·vue.js
遗憾随她而去.3 小时前
前端 Vue 虚拟列表(Virtual List),从原理到实战
前端·javascript·vue.js