Работа с SSR
@vue-modeler/model не имеет отдельного хранилища состояния. Разработчик модели отвечает за процесс сериализации и десериализации состояния.
Реализация зависит от инфраструктуры, поэтому каждый может сделать это по-своему. Общая идея такая:
- создаем сервис, который
- в контексте сервера принимает функцию сериализации
- в контексте клиента возвращает данные по ключу
- Модель
- принимает сервис как зависимость
- В контексте сервера регистрирует в нем функцию сериализации
- В контексте клиента извлекает данные по ключу и инициализирует состояние
Модель извлекает из сервиса ровно то, что сама туда положила через функцию сериализации. Разработчик имеет полный контроль над форматом и содержанием передаваемых данных.
Модель не знает, как сервис сериализации передает данные с сервера на клиент.
Пример реализации
Здесь представлена описанная выше идея в виде кода.
Вместо реализации сервиса описан его возможный интерфейс. Детали реализации зависят от инфраструктуры.
Определяем изоморфную модель
type Serializer = () => {
extractionKey: string,
value: JsonObject,
}
interface SsrStateService {
extractState: <T unknown>(key: string ) => T
addSerializer: (f: Serializer) => void
injectState: (originState: JsonObject) => JsonObject
}
class MyIsomorphicModel {
protected _state: ShallowRef<Record<string, unknown>>;
constructor(
private api: Api,
private ssrStateService: SsrStateService
) {
// Восстанавливаем состояние с сервера
const stateFromServer = ssrStateService.extractState<Record<string, unknown>>('myModelState');
this._state = shallowRef(stateFromServer || {});
// Регистрируем сериализатор для передачи на клиент
this.ssrStateService.addSerializer(() => ({
extractionKey: 'myModelState',
value: this._state.value,
}));
this.init();
}
get state(): Readonly<Record<string, unknown>> {
return this._state.value;
}
@action async init(): Promise<void> {
if (this._state.value) {
// состояние уже инициализировано
return
}
this._state.value = await this.api.fetchState();
}
}Использование в компоненте
<template>
<div>{{ model.state }}</div>
</template>
<script setup lang="ts">
import { useMyIsomorphicModel } from '@/providers/myIsomorphicModel';
const model = useMyIsomorphicModel();
// получаем состояние на стороне сервера.
// init запуститься в конструкторе
// здесь просто ждем, когда выполнится
onServerPrefetch(async () => model.init.promise)
// onMounted не нужен.
// На клиенте оно будет сразу в модели, потому что инициализация будет в конструкторе.
</script>Гидрация состояния на сервере
Реализация гидрации зависит от инфраструктуры.
Допустим мы используем @vue-modeler/dc, поэтому это может выглядеть так.
// На сервере
import { useSsrState } from '@vue-modeler/dc';
function ssrHydration(ctx: Context): void {
// извлекает сервис из контейнера
const ssrStateService = app.$vueModelerDc.get(useSsrState.asKey).instance;
// вставляем состояние в контекст
ctx.state = ssrStateService.injectState(ctx.state);
}Дальше это состояние будет записано в какую-то глобальную переменную, которую можно прочитать на клиенте.
