Vue 3 工程化中如何落地 SOLID 面向对象设计原则?

来源:DB2教程作者:缅甸程序员头衔:程序员
导读:本期聚焦于缅甸程序员创作的《Vue 3 工程化中如何落地 SOLID 面向对象设计原则?》,敬请观看详情。把 SOLID 原则直接套进 Vue 3 组件常常会让脚本又臭又长。其实 SOLID 本质是对模块职责与依赖关系的约束,并不要求把所有逻辑写成类。以单一职责为例,把数据获取、状态转换、视图渲染拆成 composable 函数后,组件只负责装配,测试与复用都更轻松。接口隔离则体现在为 props 定义精确类型而非大而全的对象。本文从依赖倒置与里氏替换两个易错点切入,对比传统 Options API 与 Composition API 下的重构方案,说明如何用 TypeScript 与 inject/provide 降低耦合,避免父组件感知子实现细节。

在 Vue 3 的项目规模膨胀到数十个业务模块之后,很多团队会发现组件越来越难维护。面向对象设计里的 SOLID 原则提供了一套经过验证的职责划分与依赖管理思路,但在组合式 API 占据主流的工程里,它并不是简单地把逻辑写成 class。我们需要理解每条原则背后要解决的是什么耦合问题,再用 Vue 3 提供的 Composition API、TypeScript 与依赖注入机制去映射实现。

Vue 3 工程化中如何落地 SOLID 面向对象设计原则?

单一职责与接口隔离在 Composable 中的体现

单一职责原则要求一个模块只因一种原因发生变化。在 Vue 3 中,最自然的承载单元不是组件,而是 composable 函数。过去用 Options API 时,我们常把数据请求、表单校验、本地缓存读写全塞进同一个组件的 methods 与 data,导致组件文件超过八百行。现在可以把这些关注点拆成 useUserApiuseFormValidatoruseLocalCache 三个独立函数,组件本身只负责调用与模板绑定。

接口隔离原则提醒我们不要强迫调用方依赖它不需要的字段。定义组件 props 或 composable 返回值时,应给出精确类型而不是一个大而全的对象。比如一个用户卡片只需要 idname,那就用单独的 UserPreview 接口,而不是把包含密码哈希的 UserFull 直接传进去。这样当后端在 UserFull 中新增字段时,预览组件不会因为类型变更而需要跟着改。

下面示例展示如何把臃肿组件拆成两个 composable,并定义窄接口:

interface UserPreview {
  id: number;
  name: string;
}

export function useUserPreview(id: number) {
  const user = ref<UserPreview | null>(null);
  async function load() {
    const res = await fetch('/api/user/' + id);
    const data = await res.json();
    user.value = { id: data.id, name: data.name };
  }
  onMounted(load);
  return { user };
}

export function useUserNameFormatter(user: Ref<UserPreview | null>) {
  const displayName = computed(() => {
    return user.value ? user.value.name.toUpperCase() : '';
  });
  return { displayName };
}

依赖倒置与 inject/provide 解耦实践

依赖倒置原则强调高层模块不应依赖低层模块,二者都应依赖抽象。在 Vue 3 工程里,组件通常属于高层调度者,而具体的存储实现、网络客户端属于低层细节。如果直接在组件里写 new LocalStorageHelper(),就形成了对具体类的硬依赖。通过 app.provide 注入一个抽象接口,组件只管调用 inject('Storage') 拿到的对象,后续换成 IndexedDB 实现也不需要改组件。

这种写法也符合开闭原则的精神:对扩展开放、对修改封闭。我们定义一个 StoragePort 接口,包含 getset 方法,在应用入口处提供基于 localStorage 的实例。测试时可以用内存版 fake 注入,完全不碰浏览器 API。相比把存储逻辑写成组件内的方法,这种结构让单元测试的 mock 成本大幅下降。

示例代码如下,展示如何在入口提供抽象并在子组件消费:

// storage.ts
export interface StoragePort {
  get(key: string): string | null;
  set(key: string, val: string): void;
}

const localStorageImpl: StoragePort = {
  get: (k) => localStorage.getItem(k),
  set: (k, v) => localStorage.setItem(k, v)
};

// main.ts
const app = createApp(RootComponent);
app.provide('Storage', localStorageImpl);

// Child.vue
import { inject } from 'vue';
export default {
  setup() {
    const storage = inject<StoragePort>('Storage')!;
    storage.set('token', 'abc');
    return { token: storage.get('token') };
  }
};

里氏替换与组件继承重构为组合

里氏替换原则要求子类型必须能替换掉父类型且不破坏程序正确性。在 Vue 2 时代,我们常用 extends 来复用组件逻辑,但子类经常覆盖父类的 datamounted 导致隐式契约被破坏。Vue 3 推荐用组合替代继承,把可替换行为做成参数或插槽传入,从而保证任何实现了相同接口的 composable 都能嵌入原流程。

例如一个列表组件依赖 fetchList 函数获取数据,我们可以把该函数作为 prop 或参数传入,而不是让子类重写 methods.fetchList。这样无论是普通 REST 获取,还是带缓存的获取,只要返回结构一致,组件渲染结果就稳定。这也避免了父类内部对子类实现的反向依赖,符合依赖倒置与里氏替换的双重约束。

以下代码演示把获取逻辑作为参数注入,而不是用继承覆盖:

import { ref, onMounted } from 'vue';

export function useListLoader(fetchList: () => Promise<any[]>) {
  const items = ref<any[]>([]);
  const loading = ref(false);
  onMounted(async () => {
    loading.value = true;
    items.value = await fetchList();
    loading.value = false;
  });
  return { items, loading };
}

// 使用方传入不同实现,均遵守同一返回类型约定
const restImpl = () => fetch('/api/items').then(r => r.json());
const cacheImpl = () => Promise.resolve([{ id: 1 }]);

在工程化落地 SOLID 时,重点不是生搬硬套类与接口,而是识别耦合点并用 Vue 3 的组合式能力将其打散。当单向数据流、依赖注入与精确类型三者配合,原本纠缠的组件自然满足五大原则,后续迭代也不会陷入改一处崩一片的困境。

Vue3SOLIDengineering_design修改时间:2026-08-17 02:06:13

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。