Моя система управления состоянием в старом проекте использует MobX. Недавно я хотел заставить его работать с SSR (поскольку я имел успех с этим в новых проектах).
Идея состояла в том, чтобы иметь менеджера магазина, который управляет всеми магазинами, к которым магазины также могут иметь доступ. возможность читать и изменять другие магазины. Это прекрасно работает с JavaScript, но TypeScript создает проблему.
Мне удалось выделить проблему в воспроизводимый пример. Вы можете запустить это на игровой площадке TypeScript, чтобы увидеть проблему.
/**
* The manager holds all the stores in the application
*/
class StoreManager<T extends Record<string, InitializableStore>> {
public stores: T = {} as any
constructor(
public instantiators: { [K in keyof T]: (manager: any) => T[K] },
) {
for (const [name, creator] of Object.entries(instantiators)) {
this.stores[name as keyof T] = creator(this)
}
}
public async init() {
console.info("Initializing stores")
await Promise.all(Object.values(this.stores).map((x) => x.init()))
}
}
export type Manager = StoreManager<Stores>
/**
* This class represents a store which should have access to the manager
*/
class InitializableStore {
constructor(protected manager: Manager) {}
public init(): void | Promise<void> {}
}
/**
* Helper function for creating a store factory
*/
const createStoreFactory = <S extends InitializableStore>(
storeClass: new (manager: Manager) => S,
) => (manager: Manager) => new storeClass(manager)
/**
* Example store set up
*/
class StoreA extends InitializableStore {
public init() {}
public meow() {
console.log("Meow")
}
}
class StoreB extends InitializableStore {
public init() {
const { storeA } = this.manager.stores
storeA.meow()
}
public woof() {
console.log("Woof!")
}
}
const storeA = createStoreFactory(StoreA)
const storeB = createStoreFactory(StoreB)
/**
* Defining the stores for the manager here
* */
const stores = { storeA, storeB }
export type StoreMapReturn<
T extends Record<string, (manager: Manager) => InitializableStore>
> = {
[K in keyof T]: ReturnType<T[K]>
}
/**
* This errors, because there's a circular reference
*/
export type Stores = StoreMapReturn<typeof stores>
Поскольку хранилищам необходим доступ к менеджеру, типы являются очень сложными и на самом деле не работают, потому что есть циклическая ссылка. В идеальной ситуации это будет работать так:
- Менеджер может быть доступен из любого хранилища
- Менеджер не является глобальным объектом, импортированным из файла (поэтому он может быть создается на лету и полностью инкапсулируется)
- Хранилища полностью безопасны при обращении из менеджера