Vue 在 Vue3 中如何实现四种全局状态数据的统一管理?

zhennann · 2024年10月10日 · 114 次阅读

四种全局状态数据

在实际开发当中,会遇到四种全局状态数据:异步数据(一般来自服务端)同步数据。同步数据又分为三种:localstoragecookie内存。在传统的 Vue3 当中,分别采用不同的机制来处理这些状态数据,而在 Zova 中只需要采用统一的Model机制

状态数据 传统的 Vue3 Zova
异步数据 Pinia Model
localstorage Pinia + Localstorage Model
cookie Pinia + Cookie Model
内存 Pinia Model

采用 Model 机制统一管理这些全局状态数据,就可以提供一些通用的系统能力,比如,内存优化持久化SSR支持等等,从而规范数据使用方式,简化代码结构,提升代码的可维护性

特性 1. 支持异步数据和同步数据

Zova Model 的基座是TanStack Query。TanStack Query 提供了强大的数据获取、缓存和更新能力。如果你没有使用过类似 TanStack Query 的数据管理机制,那么强烈建议了解一下,相信你一定会受到思想的洗礼 但是,TanStack Query 的核心是对异步数据(一般来自服务端)进行管理。Zova Model 在 TanStack Query 的基础上做了扩展,因此也支持同步数据的管理。换而言之,以下所述所有特性和能力同时适用于异步数据同步数据

特性 2. 自动缓存

对获取的异步数据进行本地缓存,避免重复获取。对于同步数据,会自动针对 localstorage 或者 cookie 进行读写操作

特性 3. 自动更新

提供数据过期策略,在合适的时机自动更新

特性 4. 减少重复请求

在程序的多个地方同时访问数据,将只调用一次服务端 api。如果是同步数据,也只针对 localstorage 或者 cookie 调用一次操作

特性 5. 内存优化

通过 Zova Model 管理的数据,虽然是全局范围的状态,但是并不总是占用内存,而是提供了内存释放与回收的机制。具体而言,就是在创建 Vue 组件实例时根据业务的需要创建缓存数据,当 Vue 组件实例卸载时释放对缓存数据的引用,到达约定的过期时间如果仍然没有其他 Vue 组件引用,就会触发回收机制 (GC),完成对内存的释放,从而节约内存占用。这对于大型项目,用户需要长时间进行界面交互的场景,具有显著的好处

特性 6. 持久化

本地缓存可以持久化,当页面刷新时可以自动恢复,避免服务端调用。如果是异步数据,就会自动持久化到 IndexDB 中,从而满足大数据量的存储需要。如果是同步数据,就会自动持久化到 localstorage 或者 cookie

内存优化持久化配合发挥作用,对于大型项目效果更佳明显。比如,第一次从服务端获取的数据,会生成本地缓存,并自动持久化。当页面不再使用并且过期时,会自动销毁本地缓存,从而释放内存。当再次访问该数据时,会自动从持久化中恢复本地缓存数据,而不是再次从服务端获取数据

特性 7. SSR 支持

不同类型的状态数据,在 SSR 模式下也会有不同的实现机制。Zova Model 把这些状态数据的差异进行抹平,并且采用统一的机制进行水合,从而让 SSR 的实现更加自然、直观,显著降低了心智负担

特性 8. 自动命名空间隔离

Zova 通过 Model Bean 来管理数据。而 Bean 本身有唯一的标识,可以作为数据的命名空间,从而自动保证了 Bean 内部状态数据命名的唯一性,避免数据冲突

如何创建一个 Model Bean

Zova 提供了 VS Code 插件,通过右键菜单可以非常便利的创建一个 Model Bean

右键菜单 - [模块路径]: Zova Create/Bean: Model

依据提示输入 model bean 的名称,比如todo,VSCode 插件会自动添加 model bean 的代码骨架

比如,在 demo-todo 模块中创建一个 Model Bean todo

demo-todo/src/bean/model.todo.ts

import { Model } from 'zova';
import { BeanModelBase } from 'zova-module-a-model';

@Model()
export class ModelTodo extends BeanModelBase {}
  • 使用@Model 装饰器
  • 继承自基类 BeanModelBase

异步数据

TanStack Query 的核心是对服务端数据进行管理。为简化起见,这里仅展示 select 方法的定义与使用:

如何定义

@Model()
export class ModelTodo {
  select() {
    return this.$useQueryExisting({
      queryKey: ['select'],
      queryFn: async () => {
        return this.scope.service.todo.select();
      },
    });
  }
}
  • 调用$useQueryExisting 创建 Query 对象
    • 为何不使用$useQuery方法?因为异步数据一般是在需要时才进行异步加载。因此我们需要确保在多次调用select方法时始终返回同一个 Query 对象,所以必须使用$useQueryExisting方法
  • 传入 queryKey,确保本地缓存的唯一性
  • 传入 queryFn,在合适的时机调用此函数获取服务端数据

如何使用

demo-todo/src/page/todo/controller.ts

import { ModelTodo } from '../../bean/model.todo.js';

export class ControllerPageTodo {
  @Use()
  $$modelTodo: ModelTodo;
}
  • 注入 Model Bean 实例:$$modelTodo

demo-todo/src/page/todo/render.tsx

export class RenderTodo {
  render() {
    const todos = this.$$modelTodo.select();
    return (
      <div>
        <div>isLoading: {todos.isLoading}</div>
        <div>
          {todos.data?.map(item => {
            return <div>{item.title}</div>;
          })}
        </div>
      </div>
    );
  }
}
  • 调用 select 方法获取 Query 对象
    • render 方法会多次执行,重复调用 select 方法返回的是同一个 Query 对象
  • 直接使用 Query 对象中的状态和数据

如何支持 SSR

在 SSR 模式下,我们需要这样使用异步数据:在服务端加载状态数据,然后通过 render 方法渲染成 html 字符串。状态数据和 html 字符串会同时发送到客户端,客户端在进行水合时仍然使用此相同的状态数据,从而保持状态的一致性

要实现以上逻辑,在 Zova Model 中只需要执行一个步骤:

demo-todo/src/page/todo/controller.ts

import { ModelTodo } from '../../bean/model.todo.js';

export class ControllerPageTodo {
  @Use()
  $$modelTodo: ModelTodo;

  protected async __init__() {
    const queryTodos = this.$$modelTodo.select();
    await queryTodos.suspense();
    if (queryTodos.error) throw queryTodos.error;
  }
}
  • 只需要在__init__方法中调用suspense等待异步数据加载完成

同步数据:localstorage

由于服务端不支持window.localStorage,因此 localstorage 状态数据不参与 SSR 的水合过程

下面演示把用户信息存入 localstorage,当页面刷新时也会保持状态

如何定义

export class ModelUser extends BeanModelBase {
  user?: ServiceUserEntity;

  protected async __init__() {
    this.user = this.$useQueryLocal({
      queryKey: ['user'],
    });
  }
}
  • 异步数据定义不同,同步数据直接在初始化方法__init__中定义
  • 调用$useQueryLocal 创建 Query 对象
  • 传入 queryKey,确保本地缓存的唯一性

如何使用

直接像常规变量一样读取和设置数据

const user = this.user;
this.user = newUser;

在服务端会自动使用Request Header中的 Cookies,在客户端会自动使用document.cookie,因此会自动保证 SSR 水合过程中 cookie 状态数据的一致性

下面演示把用户 Token 存入 cookie,当页面刷新时也会保持状态。这样,在 SSR 模式下,客户端和服务端都可以使用相同的jwt token访问后端 API 服务

如何定义

export class ModelUser extends BeanModelBase {
  token?: string;

  protected async __init__() {
    this.token = this.$useQueryCookie({
      queryKey: ['token'],
    });
  }
}
  • 异步数据定义不同,同步数据直接在初始化方法__init__中定义
  • 调用$useQueryCookie 创建 Query 对象
  • 传入 queryKey,确保本地缓存的唯一性

如何使用

直接像常规变量一样读取和设置数据

const token = this.token;
this.token = newToken;

同步数据:内存

在 SSR 模式下,服务端定义的全局状态数据会同步到客户端,并自动完成水合

下面演示基于内存的全局状态数据

如何定义

zova-ui-quasar/src/suite-vendor/a-quasar/modules/quasar-adapter/src/bean/model.theme.ts

export class ModelTheme extends BeanModelBase {
  cBrand: string;

  protected async __init__() {
    this.cBrand = this.$useQueryMem({
      queryKey: ['cBrand'],
    });
  }
}
  • 异步数据定义不同,同步数据直接在初始化方法__init__中定义
  • 调用$useQueryMem 创建 Query 对象
  • 传入 queryKey,确保本地缓存的唯一性

如何使用

直接像常规变量一样读取和设置数据

const cBrand = this.cBrand;
this.cBrand = newValue;

结语

Zova 是一款支持 IOC 容器的 Vue3 框架,在代码风格上结合了 Vue/React/Angular 的优点,同时规避他们的缺点,让我们的开发体验更加优雅,减轻心智负担。Zova 已经内置了大量实用、有趣的功能特性,Model 机制仅仅是其中一个

Zova 框架已经开源,欢迎关注,参与共建:https://github.com/cabloy/zova。可添加我的微信,入群交流:yangjian2025

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请 注册新账号