使状态管理系统类型安全时的循环引用

时间:2020-01-30 03:15:33

标签: typescript circular-reference state-management

我在一个旧项目中的状态管理系统使用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 个答案:

答案 0 :(得分:1)

基本上,编译器必须为import {share} from 'rxjs/operators'; let observer = null const notificationArrayStream = new Observable(obs => { observer = obs; }).pipe(share()); function trigger(something) { observer.next(something) } notificationArrayStream.subscribe((x) => console.log('a: ' + x)) notificationArrayStream.subscribe((x) => console.log('b: ' + x)) trigger('TEST') 类型推断以下链:

Stores

以上循环参考无法解决。如果将鼠标悬停在type Stores = typeof stores > createStoreFactory > Manager > StoreManager<Stores> > Stores // ^ circular ↩ 初始化程序上,则会得到:

“ storeA”隐式具有类型“ any”,因为它没有类型注释,并且在其自己的初始化程序中直接或间接引用。

此错误语句可以很好地解释工作:我们可以使用显式类型注释const storeA变量之一,以结束循环类型解析(sample):

const

如果这对每个商店来说都是重复的,则可以改用自上而下的方法(sample)首先定义type StoreFactory<T extends InitializableStore> = (manager: Manager) => T const storeA: StoreFactory<StoreA> = createStoreFactory(StoreA) const storeB: StoreFactory<StoreB> = createStoreFactory(StoreB)

Stores