Vue3 发布之后,整个开发模式发生了非常大的变化。
最明显的就是:
Composition API。
过去我们更多写的是:
- data
- methods
- computed
- watch
而现在:
更多写的是:
- ref
- reactive
- computed
- composables
整个项目开始朝着”函数化”发展。
例如以前:
export default {
data(){},
methods:{}
}
现在更多写成:
const count = ref(0)
function increment(){
count.value++
}
状态与逻辑可以自由组合。
Composable 之间也可以相互复用。
如果 Vuex 仍然保持原来的设计:
Action
↓
Mutation
↓
State
整个开发体验就会显得十分割裂。
Vue3 希望的是:
开发者面对状态时,尽量保持 JavaScript 本身的思维方式。
而不是额外学习一套复杂的数据流。
Pinia 正是在这样的背景下重新设计的。
Pinia 的设计理念
如果只用一句话概括 Pinia:
Store 本身就是一个普通的模块。
例如:
以前 Vuex:
修改状态:
dispatch()
↓
Action
↓
commit()
↓
Mutation
Pinia:
直接:
store.user = data
没有:
Mutation。
没有:
Commit。
没有:
Dispatch。
很多第一次接触 Pinia 的开发者都会觉得:
是不是太简单了?
事实上。
这正是 Pinia 最大的设计理念。
减少框架本身的概念。
Vue3 已经有:
- ref
- reactive
- computed
状态管理应该尽量保持一致。
而不是重新创造一套新的 API。
为什么取消 Mutation?
这是很多 Vue2 开发者最关心的问题。
Vuex 当初为什么一定要求:
Mutation
原因其实有两个。
第一:
Vue2 的响应式限制。
第二:
方便 DevTools 记录状态变化。
但是到了 Vue3:
Proxy 已经能够完整追踪状态。
开发工具同样能够知道:
什么时候修改了数据。
因此:
Mutation 存在的价值已经越来越低。
反而增加了:
大量重复代码。
例如:
修改用户名。
Vuex:
定义 type
↓
Mutation
↓
Action
↓
commit
↓
dispatch
Pinia:
store.user.name="Tom"
整个过程更加符合 JavaScript 本身的开发习惯。
为什么 Pinia 更适合 TypeScript?
这是很多企业最终迁移的重要原因。
Vuex 出现的时候。
TypeScript 还没有今天这么流行。
因此:
Vuex 很多 API 都不是围绕类型推导设计的。
例如:
Getter。
Mutation。
Action。
都需要写大量声明。
IDE 很难自动推导。
很多时候需要:
手写泛型。
而 Pinia 从第一天开始。
就是围绕 TypeScript 设计。
例如:
const userStore = useUserStore()
userStore.user
IDE 可以自动推导:
user 的完整类型。
甚至:
Action 的参数。
返回值。
Getter。
全部能够自动提示。
这意味着:
开发效率明显提高。
错误更容易提前发现。
对于大型团队来说。
这是非常大的优势。
Pinia 为什么代码更容易维护?
真正的大型项目。
Store 通常不会只有:
User
还会有:
Order
Product
Permission
Dashboard
System
Message
Setting
如果仍然按照 Vuex 的方式。
每个模块:
Action。
Mutation。
Getter。
都会越来越多。
几年之后。
整个 Store 会变得非常庞大。
而 Pinia 的设计更接近普通 JavaScript 模块。
例如:
stores
├── user.ts
├── order.ts
├── product.ts
每个 Store 都是独立文件。
维护成本明显降低。
Pinia 与 Composition API 的关系
很多开发者没有注意到。
Pinia 和 Composition API 的思想其实完全一致。
例如:
Composition API:
const count = ref(0)
Pinia:
const userStore = useUserStore()
都是:
普通变量。
普通函数。
没有额外概念。
因此:
学习成本非常低。
对于刚学习 Vue3 的开发者来说。
几乎没有额外负担。
大型项目如何组织 Store?
很多教程都会这样写:
src
stores
user.ts
order.ts
product.ts
项目小时没有问题。
但是:
如果一个项目:
有:
三十多个业务模块。
整个 stores 目录很快会变得混乱。
更推荐结合上一篇介绍的:
Feature First。
例如:
src
features
user
store.ts
api.ts
types.ts
order
store.ts
api.ts
components
product
这样:
业务。
接口。
Store。
类型。
全部放在一起。
整个模块更加完整。
以后迁移。
删除。
拆分。
都会更加方便。
Store 应该负责什么?
很多团队:
把 Store 当数据库。
什么数据都放进去。
这是非常典型的错误。
真正适合放 Store 的通常只有:
- 当前登录用户
- Token
- 权限
- 主题
- 国际化
- 系统配置
而:
例如:
搜索框内容。
弹窗是否打开。
分页页码。
这些:
完全属于页面状态。
推荐直接:
ref()
reactive()
即可。
Store 应该保存:
跨页面共享的数据。
而不是:
整个页面所有状态。
Pinia 常见误区
第一:Store 过于庞大
很多项目:
最终只有:
AppStore
几千行代码。
所有状态。
所有方法。
全部放进去。
维护非常困难。
正确做法:
按业务拆分。
第二:Store 负责请求接口
例如:
login()
↓
axios
↓
Toast
↓
Router
↓
Store
所有逻辑混在一起。
建议:
Component
↓
Service
↓
API
↓
Store
Store 只负责:
状态。
不要负责:
页面。
网络。
UI。
第三:所有数据永久缓存
很多开发者:
接口请求一次。
全部放 Store。
几年之后:
Store:
几十 MB。
真正需要长期保存的数据其实很少。
例如:
订单详情。
搜索结果。
一般离开页面即可释放。
不要为了缓存而缓存。
企业项目最佳实践
结合目前越来越多团队的经验。
推荐采用下面这种结构:
src
├── app
├── assets
├── features
│ ├── auth
│ ├── user
│ ├── order
│ ├── product
│ └── dashboard
├── shared
│ ├── components
│ ├── composables
│ ├── utils
│ └── types
├── router
├── services
├── stores
└── main.ts
其中:
- Feature 保存业务。
- Shared 保存公共资源。
- Store 保存全局状态。
- Service 负责业务逻辑。
- API 专门负责请求。
整个架构职责更加清晰。
对于多人协作和 AI 编程工具都更加友好。
X记录空间