[Nuxt.js最佳实践] 状态管理 & 类型

57 阅读4分钟

最近在学习 Nuxt.js,基于 vue 的全栈框架。可以把前后端写在一个仓库里,一起部署。

随之而来一些新的概念,和一些老的问题。 本文记录学习心得,讨论 Nuxt.js 全栈场景下:

  1. 数据类型如何定义?最好是 一处声明,多处使用。不要多处声明,比如:声明数据库schema & 声明后端ts类型 & 声明前端ts类型
  2. 全栈App的“状态” 分几种?他们分别应该如何管理?

1. 新的概念

这些是我以前没接触过的概念,熟悉的朋友可以跳过;

  • ORM: Object-Relational Mapping,对象关系映射,简单讲就是用 你熟悉的编程语言 + 面向对象 来描述 数据库表结构和操作。我选的是 Prisam来做ORM
    • migration: 当你修改表结构后(即你描述表的ORM代码),ORM工具帮你生成更新表的 *.sql 脚本,是一种 ORM -> SQL 的映射,然后执行sql脚本,数据库的结构就真的被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 文件来生成前后端类型定义;

在全栈场景下,我们可以从状态来源的最上层(数据库)开始定义结构,自然的,也希望项目中所有的类型衍生自此,以保证数据类型的一致性。

整体的流程是:

  1. Prisma 定义数据库 Schema,作为所有类型的源头;
  2. prisma-zod-generator 根据 Schema 生成其他类型,用 zod 来描述;
  3. 应用的其他部分使用的类型,应该尽量用第2步生成的类型,或衍生自它;

2.2 有哪些“状态“

简单来讲,有三种状态:

  1. 前后端状态:储存在数据库中的状态,以及所有其衍生状态,主要包括前端从后端接口处请求的数据;
  2. 前端全局状态:希望在整个客户端各处都访问的状态,比如:darkmode, theme等,它们与数据库无关(你可能觉得1中也有全局状态,但后文中我们会使用不同的工具来管理它们);
  3. 页面内状态:仅在单一页面中使用的状态,主要处理用户交互,比如页面是否在loading等;

对于1 前后端状态,本质上是数据库状态的映射,而对于“映射”,我们就希望:

  • 一处存储,多处使用:比如 “文章列表页” 和 “文章详情页” 都依赖 “文章数据表”,这两个页面 最好依赖同一份数据,而不是依赖各自的数据副本。这样我们在 “文章详情页” 更改摘要的时候,就不必手动再去同步另一份数据副本的状态;
  • 保持数据一致性:为了让应用更“丝滑”,我们一般会在内存维护一份数据副本,映射到“数据库数据”。而前端就是用户交互的入口,经常要对数据进行增删查改。因此最好有一个方便的办法,让我们维护本地数据副本与数据库的一致性

我的最佳实践是这样:

  1. 前后端状态交给 pinia-colada 处理:它会在内存维护一份数据库的数据映射,允许我们手动标记哪些数据已经“过期”,并在下次使用数据时重新同步数据库;
  2. 前端全局状态交给 pinia,简单的状态管理库,使用非常方便;
  3. 页面内状态可以用响应式变量处理,甚至是基于上面两个的映射;