Vue3 核心设计模式实战:5 种模式 + 可复用代码,覆盖 80% 开发场景

Vue3 核心设计模式实战:5 种模式 + 可复用代码,覆盖 80% 开发场景

在 Vue 项目开发中,设计模式是解决重复业务场景、提升代码可维护性的核心手段。本文聚焦 Vue3 + Composition API,结合真实开发场景,拆解 5 种高频设计模式的落地实现,所有代码可直接复制复用。

一、前言:为什么 Vue 项目需要设计模式?

  • 组件化开发中,解决跨组件通信、动态 UI 生成等共性问题;
  • 减少冗余代码,比如单例模式避免重复创建 DOM,工厂模式实现配置驱动 UI;
  • 提升扩展性,比如装饰器模式可动态增强组件功能,不侵入原逻辑;
  • Vue3 原生支持设计模式落地(如 Proxy 响应式、Composition API 函数封装)。

二、5 种核心设计模式实战(Vue3 版)

2.1 单例模式:全局唯一实例的优雅实现

模式解析

保证一个类 / 组件仅有一个实例,提供全局统一访问入口。Vue 中常用于全局状态、弹框组件等场景,避免资源浪费。

实战场景:全局 Toast 提示框(无重复 DOM)
1. 编写 Toast 组件(Toast.vue)
xml 复制代码
<template>
  <div class="toast" :class="{ show: visible }">
    {{ message }}
  </div>
</template>
<script setup>
import { ref } from 'vue'
const visible = ref(false)
const message = ref('')
// 暴露控制方法
export const toast = {
  show(msg, duration = 2000) {
    message.value = msg
    visible.value = true
    setTimeout(() => visible.value = false, duration)
  }
}
</script>
<style scoped>
.toast {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  padding: 10px 20px;
  background: rgba(0,0,0,0.7);
  color: #fff;
  border-radius: 4px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s;
  z-index: 9999;
}
.toast.show {
  opacity: 1;
}
</style>
2. 单例注册(toast.js)
javascript 复制代码
import { createApp } from 'vue'
import Toast from './Toast.vue'
import { toast } from './Toast.vue'
let instance = null // 确保实例唯一
export const useToast = () => {
  if (!instance) {
    const app = createApp(Toast)
    const div = document.createElement('div')
    document.body.appendChild(div)
    instance = app.mount(div)
  }
  return toast // 统一访问入口
}
3. 项目中使用
xml 复制代码
<script setup>
import { useToast } from '@/utils/toast.js'
const toast = useToast()
// 多次调用仅生成一个 DOM 节点
const handleClick = () => {
  toast.show('操作成功!')
}
</script>
使用注意
  • 单例模式适用于全局唯一资源(如 Pinia Store、工具类实例);
  • 避免滥用全局实例,防止命名冲突或状态污染。

2.2 工厂模式:配置驱动的动态组件生成

模式解析

通过统一工厂函数创建对象 / 组件,隐藏具体创建逻辑,支持通过配置动态生成 UI 元素(如表单、列表)。

实战场景:动态表单生成(输入框 / 下拉框一键切换)
1. 基础表单组件(Input.vue/ Select.vue)
xml 复制代码
<!-- Input.vue -->
<template>
  <input 
    v-model="modelValue" 
    class="form-input" 
    v-bind="$attrs"
  />
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>
<!-- Select.vue -->
<template>
  <select 
    v-model="modelValue" 
    class="form-select"
    @change="emit('update:modelValue', $event.target.value)"
  >
    <option v-for="opt in options" :key="opt.value" :value="opt.value">
      {{ opt.label }}
    </option>
  </select>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'
const props = defineProps(['modelValue', 'options'])
const emit = defineEmits(['update:modelValue'])
</script>
2. 表单工厂函数(formFactory.js)
typescript 复制代码
import Input from '@/components/Input.vue'
import Select from '@/components/Select.vue'
/**
 * 表单组件工厂:根据类型返回对应组件
 * @param {string} type - 组件类型(input/select)
 * @returns {Component} 对应的表单组件
 */
export const formFactory = (type) => {
  switch (type) {
    case 'input':
      return Input
    case 'select':
      return Select
    default:
      throw new Error(`不支持的表单类型:${type}`)
  }
}
3. 页面中动态渲染表单
php 复制代码
<template>
  <div class="dynamic-form">
    <div v-for="item in formConfig" :key="item.key" class="form-item">
      <label>{{ item.label }}:</label>
      <component
        :is="formFactory(item.type)"
        v-model="formData[item.key]"
        v-bind="item.props"
      />
    </div>
  </div>
</template>
<script setup>
import { ref } from 'vue'
import { formFactory } from '@/utils/formFactory.js'
// 表单配置(可从后端接口获取)
const formConfig = ref([
  {
    key: 'username',
    label: '用户名',
    type: 'input',
    props: { placeholder: '请输入用户名', maxLength: 20 }
  },
  {
    key: 'gender',
    label: '性别',
    type: 'select',
    props: {
      options: [
        { label: '男', value: 'male' },
        { label: '女', value: 'female' }
      ]
    }
  }
])
// 表单数据
const formData = ref({
  username: '',
  gender: 'male'
})
</script>
核心价值
  • 配置驱动 UI,减少重复代码(如多表单页面);
  • 新增表单类型时,仅需扩展工厂函数,符合「开闭原则」。

2.3 观察者模式:跨组件通信的极简方案

模式解析

定义一对多的依赖关系,当一个对象状态变化时,所有依赖它的对象都会收到通知。Vue 中常用 EventBus 实现跨组件通信。

实战场景:购物车数量更新(非父子组件通信)
1. 实现 EventBus(eventBus.js)
javascript 复制代码
import mitt from 'mitt' // 需安装:npm i mitt
// 单例的发布-订阅中心(Vue3 已移除 $on/$emit,推荐用 mitt)
export const bus = mitt()
2. 发布者:加入购物车按钮(AddToCart.vue)
xml 复制代码
<template>
  <button @click="addToCart" class="add-cart-btn">
    加入购物车
  </button>
</template>
<script setup>
import { bus } from '@/utils/eventBus.js'
const addToCart = () => {
  // 发布事件:通知订阅者购物车变化
  bus.emit('cartChange', 1) // 传递参数(增加 1 件商品)
}
</script>
3. 订阅者:头部购物车数量(Header.vue)
xml 复制代码
<template>
  <div class="header">
    <div class="cart-count">
      购物车:{{ cartCount }} 件商品
    </div>
  </div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { bus } from '@/utils/eventBus.js'
const cartCount = ref(0)
let handleCartChange = null
onMounted(() => {
  // 订阅事件
  handleCartChange = (num) => {
    cartCount.value += num
  }
  bus.on('cartChange', handleCartChange)
})
onUnmounted(() => {
  // 组件卸载时取消订阅,避免内存泄漏
  bus.off('cartChange', handleCartChange)
})
</script>
扩展说明
  • Vue3 响应式系统的 watch/watchEffect 本质也是观察者模式;
  • 复杂项目建议用 Pinia 替代 EventBus,避免事件命名冲突。

2.4 代理模式:不修改原逻辑的功能增强

模式解析

通过代理对象间接访问目标对象,在访问过程中附加额外逻辑(如拦截、增强)。Vue3 响应式系统的核心就是 Proxy 代理。

实战场景:图片懒加载(优化性能)
1. 实现懒加载指令(lazyDirective.js)
javascript 复制代码
/**
 * 图片懒加载指令:进入视口后加载真实图片
 */
export const lazyDirective = {
  mounted(el, binding) {
    // 创建观察者:监听元素是否进入视口
    const observer = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting) {
        // 进入视口:加载真实图片
        el.src = binding.value
        observer.unobserve(el) // 停止观察,避免重复触发
      }
    })
    // 开始观察目标元素
    observer.observe(el)
  }
}
2. 注册并使用指令
xml 复制代码
<template>
  <!-- 占位图 + 懒加载真实图片 -->
  <img 
    v-lazy="realImgUrl" 
    src="@/assets/placeholder.png"
    class="lazy-img"
    alt="示例图片"
  />
</template>
<script setup>
import { createApp } from 'vue'
import App from './App.vue'
import { lazyDirective } from '@/utils/lazyDirective.js'
// 全局注册指令
const app = createApp(App)
app.directive('lazy', lazyDirective)
// 真实图片地址(可从后端获取)
const realImgUrl = ref('https://picsum.photos/800/600')
</script>
代理模式的核心优势
  • 不修改原元素逻辑,仅通过代理增强功能;
  • 可复用性强(如请求拦截、权限校验等场景)。

2.5 装饰器模式:组件功能的动态增强

模式解析

动态给对象 / 组件添加额外功能,不改变原对象结构。Vue3 中常用高阶组件(HOC)实现。

实战场景:按钮权限控制(仅管理员可见)
1. 实现权限装饰器(withPermission.js)
javascript 复制代码
import { h } from 'vue'
import { useUserStore } from '@/stores/user' // 假设用 Pinia 存储用户信息
/**
 * 权限装饰器:根据角色控制组件是否渲染
 * @param {Component} Component - 被装饰的组件
 * @param {string} requiredRole - 所需角色(如 admin/user)
 * @returns {Component} 装饰后的组件
 */
export const withPermission = (Component, requiredRole) => {
  return (props, { slots }) => {
    const userStore = useUserStore()
    // 校验权限
    if (userStore.role === requiredRole) {
      // 有权限:渲染原组件
      return h(Component, props, slots)
    } else {
      // 无权限:渲染提示或空节点
      return h('div', { class: 'no-permission' }, '无权限操作')
    }
  }
}
2. 装饰组件并使用
xml 复制代码
<!-- 基础按钮组件(EditButton.vue) -->
<template>
  <button @click="handleClick" class="edit-btn">
    <slot></slot>
  </button>
</template>
<script setup>
const emit = defineEmits(['click'])
const handleClick = () => emit('click')
</script>
<!-- 页面中使用装饰后的组件 -->
<template>
  <!-- 仅管理员可见的编辑按钮 -->
  <DecoratedEditButton @click="handleEdit">
    编辑内容
  </DecoratedEditButton>
</template>
<script setup>
import EditButton from '@/components/EditButton.vue'
import { withPermission } from '@/utils/withPermission.js'
// 用装饰器包装组件:仅 admin 角色可见
const DecoratedEditButton = withPermission(EditButton, 'admin')
const handleEdit = () => {
  // 编辑逻辑
}
</script>
适用场景
  • 权限控制、日志打印、loading 状态等通用功能;
  • 避免组件内冗余逻辑,保持组件职责单一。

三、总结:Vue3 设计模式的核心价值

模式 核心解决问题 Vue3 适配技巧
单例模式 全局唯一实例(弹框 / 状态) 结合 Composition API 封装 hooks
工厂模式 配置驱动 UI(表单 / 列表) 利用 component 动态组件渲染
观察者模式 跨组件通信 用 mitt 替代 Vue2 的 EventBus
代理模式 功能增强(懒加载 / 拦截) 基于指令或 Proxy 实现
装饰器模式 组件动态扩展(权限 / 日志) 用高阶组件包装组件

扩展思考

  • 除了以上 5 种,Vue3 中还常用「适配器模式」(兼容旧接口)、「策略模式」(表单校验)等;
  • 设计模式不是银弹,需结合场景选择(如简单组件无需过度设计);
  • 复杂项目建议结合 TypeScript 增强类型约束,提升代码健壮性。
相关推荐
sosojie1 小时前
and+design的table前端本地分页处理
前端·vue.js
执携2 小时前
Vue Router (导航守卫)
前端·javascript·vue.js
San30.2 小时前
Vue 3 + DeepSeek 实现 AI 流式对话的完整指南
前端·vue.js·人工智能
VX:Fegn08953 小时前
计算机毕业设计|基于springboot + vue图书商城系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
一个小小开发3 小时前
在大型项目中为什么更推荐Composition API?它解决了哪些工程化问题?
vue.js
醒了接着睡4 小时前
Vue中的watch
vue.js
狗哥哥4 小时前
我是如何治理一个混乱的 Pinia 状态管理系统的
前端·vue.js·架构
一 乐4 小时前
物业管理|基于SprinBoot+vue的智慧物业管理系统(源码+数据库+文档)
前端·javascript·数据库·vue.js·spring boot
zlpzlpzyd4 小时前
vue.js 2和vue.js 3的生命周期与对应的钩子函数区别
前端·javascript·vue.js