Code前端首页关于Code前端联系我们

Vue3 里玩面向对象编程(OOP)这些关键问题得搞懂!

terry 16小时前 阅读数 118 #SEO

前端圈里,Vue3 的组合式 API 让逻辑拆分更灵活,但代码写多了容易“散”;而面向对象编程(OOP)强调封装、复用,刚好能补上结构化的缺口,但 Vue3 和 OOP 咋结合?哪些场景适合用?踩坑点在哪?这篇用问答式聊聊核心问题。

Vue3 为啥要和 OOP 结合?

先想清楚:Vue 本身和 OOP 本就有“血缘”—— 选项式 API 里的 data 像类的属性,methods 像类的方法,天然有封装感,但组合式 API 流行后,逻辑被拆到 setupcomposable 里,虽灵活却容易“东一榔头西一棒槌”。

OOP 能解决的痛点:

  • 逻辑内聚:把“用户登录登出”“购物车加减商品”这类业务逻辑,封装成独立类,避免代码散在多个文件。
  • 复用性拉满:通过“继承”减少重复代码,比如所有表单都要校验,写个基类封装校验逻辑,子类直接继承。
  • 团队协作顺:OOP 有成熟的设计模式(单例、工厂、观察者),团队按“类的职责”分工,代码结构更清晰。

举个例子:做电商项目时,购物车模块要处理“加商品、改数量、算总价、同步缓存”,把这些逻辑包成 CartService 类,组件里只需要 new CartService().addItem(),既清爽又好维护。

OOP 三大特性在 Vue3 咋落地?

OOP 核心是封装、继承、多态,但 Vue 是框架,得结合响应式、组件化玩出新花样。

封装:把逻辑“包”成可复用单元

封装的关键是“隐藏内部细节,暴露必要接口”,比如做权限管理,写个 Auth 类:

class Auth {
  #token = ref('') // 私有状态,外部不能直接改
  get token() { return this.#token.value } // 暴露读取接口
  setToken(newToken) { this.#token.value = newToken } // 暴露修改接口
  checkPermission(route) { // 暴露业务方法
    return route.meta.roles.includes(this.#getRole())
  }
  #getRole() { /* 私有方法,解析用户角色,外部无需关心 */ }
}

组件里用的时候,new Auth() 后只调 checkPermission,内部咋存 token、咋解析角色,完全不用管 —— 这就是封装的魅力。

继承:少写重复代码的关键

Vue 选项式 API 支持 extends 继承组件,但组合式 API 更推荐自定义类继承,比如做表单组件,写个基类 BaseForm

class BaseForm {
  form = reactive({}) // 表单数据
  rules = {} // 校验规则
  validate() { /* 通用校验逻辑,比如遍历rules判空 */ }
}
// 登录表单继承基类,扩展自己的逻辑
class LoginForm extends BaseForm {
  constructor() {
    super() // 继承父类属性
    this.form = reactive({ username: '', password: '' }) // 覆盖表单数据
    this.rules = { 
      username: [{ required: true, message: '用户名必填' }],
      password: [{ required: true, message: '密码必填' }]
    } // 覆盖校验规则
  }
  login() { /* 登录逻辑,调this.validate()先校验 */ }
}

组件里 new LoginForm() 后,既能用 validate 做通用校验,又能调 login 做业务逻辑 —— 重复代码全被父类“扛了”。

多态:同一行为,不同表现

多态的精髓是“父类定义行为,子类实现细节”,比如做弹窗组件,定义抽象基类 Modal,子类 ConfirmModalAlertModal 实现不同渲染:

class Modal {
  show() { /* 显示弹窗的通用逻辑,比如加遮罩层 */ }
  hide() { /* 隐藏弹窗的通用逻辑,比如删遮罩层 */ }
  render() { throw new Error('子类必须实现render方法!') } // 强制子类实现
}
class ConfirmModal extends Modal {
  render() { return h('div', '确认弹窗内容 + 确定/取消按钮') }
}
class AlertModal extends Modal {
  render() { return h('div', '提示弹窗内容 + 确定按钮') }
}

父组件调 modal.show() 时,不管是 ConfirmModal 还是 AlertModal,都会执行自己的 render —— 这就是多态:同一行为(显示弹窗),不同表现(内容/按钮不同)。

组合式 API 和 OOP 怎么“打好配合”?

组合式 API 强调“逻辑拆分到 setupcomposable”,OOP 强调“逻辑封装到类”,两者结合要注意这三点:

响应式与类的结合

Vue 的响应式依赖 ref/reactive,但类的属性默认不是响应式的!所以类里的状态必须用 ref/reactive 包起来

class Counter {
  count = ref(0) // 用ref包,修改时count.value++
  increment() { this.count.value++ }
}
// 组件中
const counter = new Counter()
console.log(counter.count.value) // 响应式数据,修改后视图会更新

composable 转类方法

很多人用 composable 抽逻辑,其实可以封装成类的方法,更“面向对象”,比如把 useRequest 改成 Request 类:

class Request {
  constructor(url) {
    this.data = ref(null)
    this.loading = ref(false)
    this.url = url
  }
  async fetch() {
    this.loading.value = true
    this.data.value = await axios.get(this.url)
    this.loading.value = false
  }
}
// 组件中
const userRequest = new Request('/api/user')
userRequest.fetch() // 调用和composable一样,但结构更像“服务类”

setup 里的类实例管理

setup 里实例化类后,还能通过 provide/inject 实现单例共享(比如全局的 AuthService 只需要一个实例):

// 父组件
const authService = new Auth()
provide('authService', authService)
// 子组件
const auth = inject('authService')
auth.checkPermission(route) // 全项目用同一个Auth实例

Vue3 组件设计里 OOP 有啥实用技巧?

除了封装、继承、多态,这些场景用 OOP 更顺手:

抽象基类封装通用逻辑

所有页面都需要“加载状态、错误处理、初始化数据”,写个 PageBase 类:

class PageBase {
  loading = ref(true)
  error = ref(null)
  async init() {
    try {
      await this.fetchData() // 子类必须实现的方法
    } catch (e) {
      this.error.value = e
    } finally {
      this.loading.value = false
    }
  }
  fetchData() { throw new Error('子类必须实现fetchData!') }
}
// 首页继承基类
class HomePage extends PageBase {
  async fetchData() {
    this.data = await getHomeData() // 获取首页数据
  }
}
// 组件setup中
const homePage = new HomePage()
homePage.init() // 自动处理loading和error

自定义指令的类封装

指令逻辑复杂时(比如拖拽、懒加载),用类管理生命周期更清晰:

class DragDirective {
  bind(el) {
    // 绑定鼠标按下事件,记录初始位置
  }
  update(el, binding) {
    // 根据binding值更新拖拽配置
  }
  unbind(el) {
    // 解绑事件,防止内存泄漏
  }
}
// 注册指令
app.directive('drag', new DragDirective())

插件开发用 OOP 组织结构

写 Vue 插件时,用类封装插件逻辑和配置,扩展性更强:

class ToastPlugin {
  constructor(app, options) {
    this.app = app
    this.options = options || { duration: 2000 } // 默认配置
    this.init()
  }
  init() {
    // 给全局属性挂$toast方法
    this.app.config.globalProperties.$toast = (msg) => {
      // 显示Toast,时长取this.options.duration
    }
  }
}
// 使用插件
app.use(new ToastPlugin(app, { duration: 3000 }))

用 OOP 写 Vue3 容易踩哪些坑?咋避?

OOP 好归好,但乱用也会踩雷,这三个坑要警惕:

过度抽象,把简单逻辑搞复杂

比如一个只显示用户名的组件,没必要写个 User 类+继承链。避坑: 先写基础逻辑,等复用性真的高了再抽象(遵循“YAGNI 原则”:You Ain’t Gonna Need It)。

响应式丢失,数据不更新

类里的属性如果不用 ref/reactive 包,修改后视图不会更新。避坑: 状态统一用 ref/reactive 声明,方法里操作 valueref)或属性(reactive 对象)。

继承时选项式与组合式冲突

用选项式 API 的 extends 继承组件时,和组合式 API 的 setup 可能有生命周期执行顺序问题(比如父类 mounted 和子类 setup 谁先执行)。避坑: 组合式 API 优先用自定义类继承,少用选项式的 extends;如果用 extends,要仔细测试生命周期钩子的执行顺序。

Vue3 + OOP 是银弹吗?

不是银弹,但能解决大型项目逻辑分散、复用难、维护差的痛点,核心是理解 OOP “封装、继承、多态”的设计思想,和 Vue3 “组件化、响应式、组合式” 的特性互补。

  • 小项目:直接写 composable 更快,没必要上 OOP。
  • 中大型项目:用 OOP 结构化代码,团队协作更顺,后期改需求也更稳。

说到底,技术是工具,得看场景选,但学会 Vue3 + OOP 的配合,相当于给代码加了层“结构化buff”,应对复杂需求更游刃有余~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门