一篇文章彻底搞懂前端架构层面分层设计

模块/组件分层设计

上层模块依赖下层模块,下层模块不反向依赖上层模块。

Ui层

组件层(components): 页面展示,用户交互(按钮表单弹窗)

业务逻辑层

逻辑层(Composables/Stores/Servers):处理业务规则, 状态管理,API调用协调。

Composables - Vue3的组合式函数

  • 作用:封装可复用的响应式逻辑。
  • 场景:管理响应式状态/处理生命周期逻辑/跨组件共享状态/处理副作用(API 事件监听等)
  • 通常不依赖全局状态(无法单元测试,避免在组合式函数依赖全局store数据)通过局部或传参驱动

Stores - 全局状态管理

  • 作用:管理跨组件共享的状态(如用户登录信息,购物车,全局配置)
  • 工具:Vue3的Pinia,Vue2的Vuex
  • 通常支持Actions发异步请求,getters 计算器属性

Servers(服务层)- 业务协调与API封装

  • 作用:封装业务规则+协调多个API调用,避免组件直接调用API
  • 特点:通常为纯函数不依赖UI,易于单元测试,可包含复杂的业务逻辑。(无响应式数据)

为什么需要逻辑层?

无逻辑层的问题 逻辑层如何解决
组件代码臃肿(API+状态+逻辑混在一起) 逻辑抽离,单一职责,组件只负责渲染
相同逻辑重复写(如多个页面都需要搜索用户) Composable复用
状态在各个组件,传值繁琐 Store集中管理,跨多层组件传值
业务规则写在组件里,无法测试 Service是纯函数,可单元测试
修改一个业务组件要改多个文件 只需要改Composables或Service

项目最佳实践

  • 组件:调用Composable
  • Compsable:调用Service+更新局部状态
  • Store:管理全局状态,也可以调用Service
  • Service:只处理数据和业务,不碰UI和响应式

分层后的效果

  • Service只关心做什么(What)
  • Composables/Store关心 怎么做(How)+状态怎么变(State)
  • Component只关心怎么展示(View)

案例 - 用户搜索功能

1️⃣Service 层(无响应式)
js 复制代码
// services/userService.ts
export async function searchUsers(keyword: string): Promise<User[]> {
  if (!keyword.trim()) return []
  const res = await api.get('/users', { params: { q: keyword } })
  return res.data
}

✅ 只负责:调用 API + 返回数据 ,不碰 ref、不管理状态。

2️ Composable 层(定义响应式变量)
js 复制代码
// composables/useUserSearch.ts
import { ref, computed } from 'vue'
import { searchUsers } from '@/services/userService'

export function useUserSearch() {
  // 👇 响应式变量定义在这里!
  const keyword = ref('')
  const loading = ref(false)
  const results = ref<User[]>([])

  const isEmpty = computed(() => results.value.length === 0)

  const search = async () => {
    loading.value = true
    try {
      // 调用 service,拿到普通数据
      results.value = await searchUsers(keyword.value)
    } finally {
      loading.value = false
    }
  }

  return {
    keyword,      // ref<string>
    loading,      // ref<boolean>
    results,      // ref<User[]>
    isEmpty,      // ComputedRef<boolean>
    search        // function
  }
}

响应式状态(keyword/loading/results)定义在 Composable 中,因为它:

  • 与 UI 交互强相关(输入框、加载动画、列表渲染)
  • 可能被多个组件复用
  • 生命周期通常与组件绑定(组件销毁,状态自动清理)
3️⃣ Store 层(如果是全局状态
js 复制代码
// stores/userStore.ts
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  // 👇 响应式状态定义在 state 中
  state: () => ({
    profile: null as User | null,
    isLoggedIn: false
  }),

  actions: {
    async fetchProfile() {
      // 调用 service
      this.profile = await getUserProfileService()
      this.isLoggedIn = !!this.profile
    }
  }
})

✅ 全局共享的响应式状态放在 Store。

4️⃣ 组件层(尽量少定义)
js 复制代码
<script setup>
import { useUserSearch } from '@/composables/useUserSearch'

// 从 composable 拿到响应式变量,直接在模板中使用
const { keyword, loading, results, search } = useUserSearch()
</script>

<template>
  <input v-model="keyword" @input="search" />
  <div v-if="loading">搜索中...</div>
  <ul v-else>
    <li v-for="user in results" :key="user.id">{{ user.name }}</li>
  </ul>
</template>

✅ 组件不定义 keywordloading 等状态,只消费 Composable 提供的响应式数据。

数据访问层

API层/数据请求层(Axios/fetch的封装):与后端接口通信,只负责怎么发请求,怎么响应。

相关推荐
Full Stack Developme12 分钟前
Redis 持久化 备份 还原
前端·chrome
猪猪拆迁队35 分钟前
2025年终总结-都在喊前端已死,这一年我的焦虑、挣扎与重组:AI 时代如何摆正自己的位置
前端·后端·ai编程
❆VE❆42 分钟前
WebSocket与SSE深度对比:技术差异、场景选型及一些疑惑
前端·javascript·网络·websocket·网络协议·sse
ConardLi43 分钟前
SFT、RAG 调优效率翻倍!垂直领域大模型评估实战指南
前端·javascript·后端
rgeshfgreh1 小时前
Java高性能开发:Redis7持久化实战
前端·bootstrap·mybatis
李剑一1 小时前
uni-app使用html5+创建webview,可以控制窗口大小、显隐、与uni通信
前端·trae
Hooray2 小时前
2026年,站在职业生涯十字路口的我该何去何从?
前端·后端
小二·2 小时前
Python Web 开发进阶实战:安全加固实战 —— 基于 OWASP Top 10 的全栈防御体系
前端·python·安全
over6972 小时前
🌟 JavaScript 数组终极指南:从零基础到工程级实战
前端·javascript·前端框架
社恐的下水道蟑螂2 小时前
深入掌握 AI 全栈项目中的路由功能:从基础到进阶的全面解析
前端·react.js·全栈