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

模块/组件分层设计

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

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的封装):与后端接口通信,只负责怎么发请求,怎么响应。

相关推荐
Keepreal4962 小时前
React组件生命周期,各个生命周期可以进行什么操作以及如何使用useEffect模拟组件生命周期
前端·react.js
JiKun2 小时前
ECMA 2025(ES16) 新特性
前端·javascript
一路上__有你2 小时前
闲来无事,写一篇文章吧!
前端·javascript·vue.js
keep_di2 小时前
05-vue3+ts中axios的封装
前端·vue.js·ajax·typescript·前端框架·axios
JiKun2 小时前
ECMA 2024(ES15) 新特性
前端·javascript
百锦再3 小时前
从 .NET 到 Java 的转型指南:详细学习路线与实践建议
android·java·前端·数据库·学习·.net·数据库架构
i小杨3 小时前
前端埋点(打点)方案
前端·状态模式
前端加油站3 小时前
时间转换那些事
前端·javascript
风清云淡_A3 小时前
【VUECLI】node.js打造自己的前端cli脚手架工具
前端·node.js