Циркулярная справка при обеспечении безопасности системы государственного управления - PullRequest
1 голос
/ 30 января 2020

Моя система управления состоянием в старом проекте использует 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>

Поскольку хранилищам необходим доступ к менеджеру, типы являются очень сложными и на самом деле не работают, потому что есть циклическая ссылка. В идеальной ситуации это будет работать так:

  • Менеджер может быть доступен из любого хранилища
  • Менеджер не является глобальным объектом, импортированным из файла (поэтому он может быть создается на лету и полностью инкапсулируется)
  • Хранилища полностью безопасны при обращении из менеджера

1 Ответ

1 голос
/ 01 февраля 2020

Как правило, компилятор должен вывести следующую цепочку для типа Stores:

type Stores = typeof stores > createStoreFactory > Manager > StoreManager<Stores> > Stores 
//        ^                          circular                                      ↩

Указанная выше циклическая ссылка не может быть разрешена. Если вы наведите курсор на const storeA initializer, вы получите:

'storeA' неявно имеет тип 'any', потому что у него нет аннотации типа, и на него прямо или косвенно ссылаются в его собственном инициализаторе.

Это заявление об ошибке довольно неплохо объясняет работу: мы можем аннотировать одну из const переменных с явным типом, чтобы завершить круговое разрешение типа ( sample ):

type StoreFactory<T extends InitializableStore> = (manager: Manager) => T

const storeA: StoreFactory<StoreA> = createStoreFactory(StoreA)
const storeB: StoreFactory<StoreB> = createStoreFactory(StoreB)

Если это слишком повторяется в каждом магазине, вместо этого вы можете сначала определить Stores в нисходящем подходе ( sample ):

export type Stores = {
  storeA: StoreA;
  storeB: StoreB;
}
export type StoreFactories = { [K in keyof Stores]: (manager: Manager) => Stores[K] }

const storeA = createStoreFactory(StoreA)
const storeB = createStoreFactory(StoreB)
const stores: StoreFactories = { storeA, storeB }
...