四種全域性狀態資料
在實際開發當中,會遇到四種全域性狀態資料:非同步資料(一般來自服務端)
、同步資料
。同步資料又分為三種:localstorage
、cookie
、記憶體
。在傳統的 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;
同步資料: cookie
在服務端會自動使用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