最近在学习 Nuxt.js,基于 vue 的全栈框架。可以把前后端写在一个仓库里,一起部署。
随之而来一些新的概念,和一些老的问题。 本文记录学习心得,讨论 Nuxt.js 全栈场景下:
- 数据类型如何定义?最好是 一处声明,多处使用。不要多处声明,比如:声明数据库schema & 声明后端ts类型 & 声明前端ts类型;
- 全栈App的“状态” 分几种?他们分别应该如何管理?
1. 新的概念
这些是我以前没接触过的概念,熟悉的朋友可以跳过;
- ORM: Object-Relational Mapping,对象关系映射,简单讲就是用 你熟悉的编程语言 + 面向对象 来描述 数据库表结构和操作。我选的是
Prisam来做ORM- migration: 当你修改表结构后(即你描述表的ORM代码),ORM工具帮你生成更新表的
*.sql脚本,是一种 ORM -> SQL 的映射,然后执行sql脚本,数据库的结构就真的被ORM修改了;
- migration: 当你修改表结构后(即你描述表的ORM代码),ORM工具帮你生成更新表的
- GraphQL:一种API查询语言,针对传统 REST API 的「过度请求(返回多余字段)、请求不足(需多次调用)、接口定制化差」的痛点。
- TanstackQuery:代表一类工具,通常描述为 the missing data-fetching library for web applications,帮我们处理前后端状态交互,比如 缓存结果、乐观更新等;我选的是
pinia-colada,因为对 pinia 比较熟悉,且 TanstackQuery 与 Nuxt4 的兼容程度存疑;
2. 老的问题
对几个做纯前端开发时考虑过的问题,在Nuxt.js的全栈场景下,有了新的思考;
2.1 类型一致性
以前做纯前端,对接的后端是微服务架构,使用 protobuf 描述接口,因此我选择订阅 *.proto 文件来生成前后端类型定义;
在全栈场景下,我们可以从状态来源的最上层(数据库)开始定义结构,自然的,也希望项目中所有的类型衍生自此,以保证数据类型的一致性。
整体的流程是:
Prisma定义数据库 Schema,作为所有类型的源头;prisma-zod-generator根据 Schema 生成其他类型,用zod来描述;- 应用的其他部分使用的类型,应该尽量用第2步生成的类型,或衍生自它;
2.2 有哪些“状态“
简单来讲,有三种状态:
- 前后端状态:储存在数据库中的状态,以及所有其衍生状态,主要包括前端从后端接口处请求的数据;
- 前端全局状态:希望在整个客户端各处都访问的状态,比如:darkmode, theme等,它们与数据库无关(你可能觉得1中也有全局状态,但后文中我们会使用不同的工具来管理它们);
- 页面内状态:仅在单一页面中使用的状态,主要处理用户交互,比如页面是否在loading等;
对于1 前后端状态,本质上是数据库状态的映射,而对于“映射”,我们就希望:
- 一处存储,多处使用:比如 “文章列表页” 和 “文章详情页” 都依赖 “文章数据表”,这两个页面 最好依赖同一份数据,而不是依赖各自的数据副本。这样我们在 “文章详情页” 更改摘要的时候,就不必手动再去同步另一份数据副本的状态;
- 保持数据一致性:为了让应用更“丝滑”,我们一般会在内存维护一份数据副本,映射到“数据库数据”。而前端就是用户交互的入口,经常要对数据进行增删查改。因此最好有一个方便的办法,让我们维护本地数据副本与数据库的一致性;
我的最佳实践是这样:
- 前后端状态交给
pinia-colada处理:它会在内存维护一份数据库的数据映射,允许我们手动标记哪些数据已经“过期”,并在下次使用数据时重新同步数据库; - 前端全局状态交给
pinia,简单的状态管理库,使用非常方便; - 页面内状态可以用响应式变量处理,甚至是基于上面两个的映射;