Реестр
Гайд по использованию реестра и передаче типизированных сущностей из проектов в
shared
Для чего нужен?
Некоторые компоненты shared-front требуют зависимости, которые зависят от проекта, к примеру api вызовы или ссылки на страницы. Решением этой проблемы служит Реестр.
Принцип работы
Для работы с реестром необходимы несколько шагов
Шаг 0 (Установка в проект)
Nuxt(2/3)
// nuxt.config.js
export default {
modules: [
'shared-front/nuxt/registry',
],
};
Vue(2/3)
// main.ts
import { Plugin } from 'shared-front/lib/modules/registry';
appOrVue.use(Plugin);
Шаг 1 (Описание зависимости)
На данном шаге происходит описание типа зависимости и создание уникального ключа для реестра
Описание зависимости происходит на уровне shared-front в папке src/registry
// src/registry/example.ts
import type { RegistryKey, Registry } from '@/modules/registry';
import { useRegistryWrapper, useRegistryItem } from '@/modules/registry';
export interface Example {
foo: string;
bar: number;
}
export const exampleRegistryKey = Symbol('example registry-key') as RegistryKey<Example>;
export default useRegistryWrapper(exampleRegistryKey);
// Идентично
export default (registry?: Registry) =>
useRegistryItem(exampleRegistryKey, registry);
Шаг 1.2 (Классовое описание)
Если зависимость имеет схожую структуру в разных проектах, имеет смысл создать
js classи использовать далее его же или наследовать и модифицировать
// src/registry/example.ts
import type { RegistryKey } from '@/modules/registry';
export class Example {
foo = 'foo';
bar = 123;
}
export const exampleRegistryKey = Symbol('example registry-key') as RegistryKey<Example>;
export default useRegistryWrapper(exampleRegistryKey);
Из-за бага vite#9251 использовать символы невозможно и приходится использовать строки
export const exampleRegistryKey = 'example registry-key' as unknown as RegistryKey<Example>;
Шаг 1.5 (Описание pinia store)
Для работы с pinia добавлены более удобные хелперы с рассчетом на расширение
// src/registry/stores/example.ts
import {
type ExtractId,
type ExtractState,
type ExtractGetters,
type ExtractActions,
type StoreRegistryKey,
defineDefaultStore,
useStoreWrapper,
setActiveRegistry,
} from '@/modules/registry';
// Хелпер сугубо для правильной типизации
export const defaultStore = defineDefaultStore({
id: 'example',
state: () => ({
foo: 'foo',
bar: 123,
}),
getters: {
fooBar(state) {
return state.foo + state.bar;
},
},
});
// Шаблонный код для дальнейшего расширения типов
export type Id = ExtractId<typeof defaultStore>;
export interface State extends ExtractState<typeof defaultStore> {}
export interface Getters extends ExtractGetters<typeof defaultStore> {}
export interface Actions extends ExtractActions<typeof defaultStore> {}
// StoreRegistryKey легкий алиас для RegistryKey<Store<...>>
// "${defaultStore.id}" вместо "example" сугубо для более простого копирования в разные файлы
export const injectionKey = `store:${defaultStore.id}` as unknown as StoreRegistryKey<Id, State, Getters, Actions>;
export default useStoreWrapper<Id, State, Getters, Actions>(injectionKey, defaultStore);
// Идентично
const useStore = defineStore(defaultStore);
export default (registry?: Registry) =>
useRegistryItem(injectionKey, registry, ({ $pinia }, registry) => {
setActiveRegistry(registry);
return useStore($pinia);
});
Указание типов для useStoreWrapper<> важно, иначе не будут работать новые типы после расширения
Шаг 2 (Реализация зависимости)
Данный шаг подразумевает конкретную реализацию зависимости
Реализация происходит на уровне проектов в папках registry
// registry/example.ts
import { defineRegistryItem } from 'shared-front/lib/modules/registry';
import { exampleRegistryKey } from 'shared-front/lib/registry/example';
export default defineRegistryItem(exampleRegistryKey, (context) => ({
foo: 'foo',
bar: 123,
}));
Шаг 2.2 (Классовая реализация)
Дополнение к предыдущему классовому шагу
// registry/example.ts
import { defineRegistryItem } from 'shared-front/lib/modules/registry';
import { Example, exampleRegistryKey } from 'shared-front/lib/registry/example';
class MyExample extends Example {
foo = 'baz';
}
export default defineRegistryItem(exampleRegistryKey, (context) => new MyExample());
Шаг 2.5 (Реализация pinia store)
Более подробно про работу с pinia stores
// registry/stores/example.ts
import { injectionKey, defaultStore } from '@/registry/stores/foo';
import {
defineRegistryStore,
extendStore,
setActiveRegistry,
type ExtractState,
type ExtractGetters,
type ExtractActions,
} from '@/modules/registry';
// Расширение defaultStore
const extendedStore = extendStore(defaultStore, {
state: () => ({
baz: 'baz',
}),
getters: {
barBaz(state) {
return state.bar + state.baz;
},
},
actions: {
someActionInAnotherStore() {
const anotherStore = useAnotherRegistryStore(this.$registry);
},
},
});
// Очередной шаблонный код
declare module 'shared-front/lib/registry/stores/example' {
interface State extends ExtractState<typeof extendedStore> {}
interface Getters extends ExtractGetters<typeof extendedStore> {}
interface Actions extends ExtractActions<typeof extendedStore> {}
}
export default defineRegistryStore(injectionKey, extendStore);
// Идентично
const useStore = defineStore(extendedStore);
export default defineRegistryItem(injectionKey, ({ $pinia }, registry) => {
setActiveRegistry(registry);
return useStore($pinia);
});
Шаг 3 (Provide зависимости)
Перед использованием зависимости, необходимо поместить её в реестр
Provide зависимости происходит на уровне проектов, в компоненте родителе компонента, в котором эта зависимость нужна
Если у зависимости есть дефолтное значение и оно не изменяется в проекте, этот шаг можно пропустить
// pages/example/index.ts setup
import useExample from '@/registry/example';
const example = useExample();
Глобально сделать provide зависимости в предыдущем шаге нельзя при использовании ssr, т.к. нужен конкретный инстанс реестра
Для доступа к элементам реестра из nuxt route middleware, provide необходимо проводить или в предыдущем middleware или в плагине
Шаг 3.3 (Упрощенный provide)
Если зависимость не нужно переиспользовать или она нужна лишь для теста, можно совместить 2-ой и 3-ий шаги
// pages/example/index.ts setup
import { provideRegistryItem } from 'shared-front/lib/modules/registry';
import { exampleRegistryKey } from 'shared-front/lib/registry/example';
provideRegistryItem(exampleRegistryKey, {
foo: 'foo',
bar: 123,
});
Шаг 3.4 (Provide вне setup)
// pages/example/index.ts
import useExample from '@/registry/example';
export default defineComponent({
created() {
const example = useExample(this.$registry);
},
});
Шаг 4 (Использование зависимости)
Финальный шаг при использовании реестра
Использование может происходить как на shared-front так и на проектах, актуальнее всего конечно в shared-front
// src/components/example/index.ts setup
import { useRegistryItem } from '@/modules/registry';
import useExample, { exampleRegistryKey } from '@/registry/example';
const example = useRegistryItem(exampleRegistryKey);
// Идентично, но в случае использование загрузчиков, приоритетно
const example = useExample();
Шаг 4.4 (Использование вне setup)
// src/components/example/index.ts
import { useRegistryItem } from '@/modules/registry';
import { exampleRegistryKey } from '@/registry/example';
export default defineComponent({
computed: {
example() {
return useRegistryItem(exampleRegistryKey, this.$registry);
},
},
});
Контексты использования
Контексты использования реестра можно разбить на категории
Ssr + Spa + SetupSsr + SpaSpa + SetupSpa
Для всех кейсов кроме второго, все будет штатно работать без доп аргументов, для случая 2, необходимо добавлять registry к вызовам
Argument registry
Т.к. в среде Ssr единый инстанс реестра расшаривается на все запросы, какая-то приватная информация юзеров могла бы утечь. Чтобы подобное было невозможно, при использовании на сервере и вне setup, необходимо явно задавать реестр с которым производится манипуляция (аналогично pinia).
На странице api можно более подробно ознакомиться с аргументами каждой функции