DEV Community

Cover image for How to achieve unified management of four types of global state data in Vue3?
Uncle Pushui
Uncle Pushui

Posted on

How to achieve unified management of four types of global state data in Vue3?

Four types of global state data

In actual development, you will encounter four types of global state data: asynchronous data (usually from the server) and synchronous data, while synchronous data is divided into three types: localstorage, cookie, and memory. In the traditional Vue3, different mechanisms are used to handle these state data, while only a unified Model mechanism is needed in Zova

Global State Data Traditional Vue3 Zova
asynchronous data Pinia Model
localstorage Pinia + Localstorage Model
cookie Pinia + Cookie Model
memory Pinia Model

By using the Model mechanism to uniformly manage these global state data, some common system capabilities can be provided, including Memory Optimization, Persistence and SSR Support, etc., thereby standardizing data usage, simplifying code structure, and improving code maintainability

Feature 1. Support for async data and sync data

The base of Zova Model is TanStack Query. TanStack Query provides powerful data acquisition, caching and update capabilities. If you have not used similar data management mechanisms like TanStack Query, it is strongly recommended that you learn about it, and you will definitely be impressed by the thought

The core of TanStack Query is to manage asynchronous data (usually from the server). Zova provides some extension capabilities based on TanStack Query, so as to support the management of synchronous data. In other words, all the features and capabilities described below apply to both asynchronous data and synchronous data

Feature 2. Automatic caching

Locally cache the acquired asynchronous data to avoid repeated acquisition. For synchronous data, read and write operations will be automatically performed on localstorage or cookie

Feature 3. Automatic update

Provide data expiration strategy and automatically update at the right time

Feature 4. Reduce duplicate requests

When accessing data in multiple places in the program at the same time, the server API will only be called once. If it is synchronous data, only one operation will be called for localstorage or cookie

Feature 5. Memory optimization

Although the data managed by Zova Model is in a global state, it does not always occupy memory, but provides a mechanism for memory release and recycling. Specifically, it is to create cache data according to business needs when creating a Vue component instance, release the reference to the cache data when the Vue component instance is unmounted, and if no reference and expiration time reached, the garbage collection mechanism (GC) will be triggered to complete the release of memory, thereby saving memory usage. This has significant benefits for large projects and scenarios where users need to interact with the interface for a long time

Feature 6. Persistence

Local cache can be persisted and can be automatically restored when the page is refreshed to avoid server requests. If it is asynchronous data, it will be automatically persisted to IndexDB to meet the storage needs of large amounts of data. If it is synchronous data, it will be automatically persisted to localstorage or cookie

Memory optimization and persistence work together, and the effect is more obvious for large projects. For example, the data obtained from the server will generate a local cache and automatically persist. When no longer used and expires, the local cache will be automatically destroyed to release memory. When the data is accessed again, the local cache data will be automatically restored from persistence instead of requesting the data from the server again

Feature 7. SSR support

Different types of state data will also have different implementation mechanisms in SSR mode. Zova Model smoothes out the differences in these state data and uses a unified mechanism to hydrate them, making the implementation of SSR more natural and intuitive, significantly reducing the mental burden

Feature 8. Automatic namespace isolation

Zova manages data through Model Bean. The Bean itself has a unique identifier and can be used as a namespace for data, thereby automatically ensuring the uniqueness of the state data naming inside the Bean and avoiding data conflicts

How to create a model bean

Zova provides a VSCode extension that allows you to easily create a model bean through the context menu

Context Menu - [Module Path]: Zova Create/Bean: Model

Enter the name of model bean according to the prompt, such as todo. The VSCode extension will automatically create the code skeleton of model bean

For example, create a Model Bean todo in the demo-todo module

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

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

@Model()
export class ModelTodo extends BeanModelBase {}
Enter fullscreen mode Exit fullscreen mode
  • Use @Model decorator
  • Inherited from base class BeanModelBase

Async Data

The core of TanStack Query is to manage server-side data. For simplicity, only the definition and use of the select method are shown here:

  • For complete code examples, please see: demo-todo

How to define

@Model()
export class ModelTodo {
  select() {
    return this.$useQueryExisting({
      queryKey: ['select'],
      queryFn: async () => {
        return this.scope.service.todo.select();
      },
    });
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Invoke $useQueryExisting to create a Query object
    • Why not use the $useQuery method? Because asynchronous data is generally loaded asynchronously when needed. Therefore, we need to ensure that the same Query object is always returned when the select method is invoked multiple times, so the $useQueryExisting method must be used
  • Pass in queryKey to ensure the uniqueness of the local cache
  • Pass in queryFn and call this function at the appropriate time to obtain server data

How to use

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

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

export class ControllerPageTodo {
  @Use()
  $$modelTodo: ModelTodo;
}
Enter fullscreen mode Exit fullscreen mode
  • Inject Model Bean instance: $$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>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Invoke select method to obtain the Query object
    • The render method will be executed multiple times, and repeated calls to the select method return the same Query object
  • Directly use the state and data of the Query object

How to support SSR

In SSR mode, we need to use asynchronous data like this: load the state data on the server, and then render it into an HTML string through the render method. The state data and HTML string will be sent to the client at the same time, and the client will still use the same state data when hydrating so as to maintain state consistency

To implement the above logic, only one step is required in 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;
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Just invoke suspense in the __init__ method to wait for asynchronous data loading to complete

Sync Data: localstorage

Since the server does not support window.localStorage, the localstorage state data does not participate in the SSR hydration process

The following demonstrates storing user information in localstorage, and the state will be retained when the page is refreshed

How to define

export class ModelUser extends BeanModelBase {
  user?: ServiceUserEntity;

  protected async __init__() {
    this.user = this.$useQueryLocal({
      queryKey: ['user'],
    });
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Unlike async data definition, sync data is defined directly in the initialization method __init__
  • Invoke $useQueryLocal to create a Query object
  • Pass in queryKey to ensure the uniqueness of the local cache

How to use

Read and set data directly like regular variables

const user = this.user;
this.user = newUser;
Enter fullscreen mode Exit fullscreen mode

Sync Data: cookie

Cookies in Request Header are automatically used on the server side, and document.cookie is automatically used on the client side, thus automatically ensuring the consistency of cookie state data during SSR hydration

The following demonstrates storing the user Token in a cookie, and the state will be retained when the page is refreshed. Thus, in SSR mode, both the client and the server can use the same jwt token to access the backend API services

How to define

export class ModelUser extends BeanModelBase {
  token?: string;

  protected async __init__() {
    this.token = this.$useQueryCookie({
      queryKey: ['token'],
    });
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Unlike async data definition, sync data is defined directly in the initialization method __init__
  • Invoke $useQueryCookie to create a Query object
  • Pass in queryKey to ensure the uniqueness of the local cache

How to use

Read and set data directly like regular variables

const token = this.token;
this.token = newToken;
Enter fullscreen mode Exit fullscreen mode

Sync Data: memory

In SSR mode, the global state data defined by the server will be synchronized to the client and automatically complete the hydration

The following demonstrates the memory-based global state data

How to define

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'],
    });
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Unlike async data definition, sync data is defined directly in the initialization method __init__
  • Invoke $useQueryMem to create a Query object
  • Pass in queryKey to ensure the uniqueness of the local cache

How to use

Read and set data directly like regular variables

const cBrand = this.cBrand;
this.cBrand = newValue;
Enter fullscreen mode Exit fullscreen mode

Conclusion

Zova is a vue3 framework with ioc container. It combines the advantages of Vue/React/Angular in code style, while avoiding their shortcomings to make our development experience more elegant and reduce the mental burden. Zova has built-in a lot of interesting features, and the Model mechanism is just one of them.

Zova has been open sourced, welcome to follow and participate: https://github.com/cabloy/zova. You can contact me on twitter: https://twitter.com/zhennann2024

Top comments (0)