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

模块/组件分层设计

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

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

相关推荐
harrain38 分钟前
什么!vue3.4开始,v-model不能用在prop上
前端·javascript·vue.js
fanruitian6 小时前
uniapp android开发 测试板本与发行版本
前端·javascript·uni-app
rayufo6 小时前
【工具】列出指定文件夹下所有的目录和文件
开发语言·前端·python
RANCE_atttackkk6 小时前
[Java]实现使用邮箱找回密码的功能
java·开发语言·前端·spring boot·intellij-idea·idea
2501_944525548 小时前
Flutter for OpenHarmony 个人理财管理App实战 - 支出分析页面
android·开发语言·前端·javascript·flutter
李白你好8 小时前
Burp Suite插件用于自动检测Web应用程序中的未授权访问漏洞
前端
刘一说9 小时前
Vue 组件不必要的重新渲染问题解析:为什么子组件总在“无故”刷新?
前端·javascript·vue.js
徐同保10 小时前
React useRef 完全指南:在异步回调中访问最新的 props/state引言
前端·javascript·react.js
刘一说11 小时前
Vue 导航守卫未生效问题解析:为什么路由守卫不执行或逻辑失效?
前端·javascript·vue.js
一周七喜h11 小时前
在Vue3和TypeScripts中使用pinia
前端·javascript·vue.js