在Vue3中如何實現四種全域性狀態資料的統一管理?

濮水大叔發表於2024-10-10

四種全域性狀態資料

在實際開發當中,會遇到四種全域性狀態資料:非同步資料(一般來自服務端)同步資料。同步資料又分為三種: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 內部狀態資料命名的唯一性,避免資料衝突

  • 參見: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方法的定義與使用:

  • 完整程式碼示例,請參見:demo-todo

如何定義

@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,在合適的時機呼叫此函式獲取服務端資料
    • service.todo.select:參見Api服務

如何使用

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 物件中的狀態和資料
    • 參見:TanStack Query: Queries

如何支援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

相關文章