键入安全方式以在对象中保存多个类型

时间:2017-10-03 14:28:43

标签: typescript

这感觉就像一个非常简单的问题,但我无法理解它或找到正确的例子。

让我们说我想创建一个ServiceProvider类,它在内部将类实例映射到字符串标识符或实例构造函数。使用时它看起来像这样:

let provider = new ServiceProvider();
provider.set('date', new Date());
provider.set('other', new SomeObject());
provider.get('date'); // I want TypeScript to know this is a date object
provider.get('other'); // I want TypeScript to know this is an instance of SomeObject

或者像这样:

let provider = new ServiceProvider();
provider.set(new Date());
provider.set(new SomeObject());
provider.get(Date); // I want TypeScript to type-safely return the Date I provided
provider.get(SomeObject); // I want TypeScript to type-safely return the instance of SomeObject I provided

实施

该解决方案与this article中给出的解释非常相似,但在示例中,存储对象与密钥一起提供,而不是存储在其他位置。我探讨了以下示例,但T extends typeof this.data无法解析。

export class ServiceProvider
{

    private data: {};

    public set(key: string, instance: {}): void
    {
        this.data[key] = instance;
    }

    public get<T extends typeof this.data, K extends keyof T>(key: K): T
    {
        return this.data[key];
    }

}

我不确定将data声明为什么类型;我不能强烈地键入它(private data: { [key: string]: Date }),因为我正在处理未知的实例类型,但如果我松散地键入它(private data: { [key: string]: object }),那么我可以分配任何对象,但我认为类型信息正在丢失。

任何想法都会有所帮助。

2 个答案:

答案 0 :(得分:2)

您的第二种方法(传递构造函数以获取该构造函数的唯一实例)很简单:

public get<T>({new(): T}): T

答案 1 :(得分:1)

我将重点关注string - 值键的部分,并忽略传递构造函数问题(@SLaks或多或少地正确键入)。

如果您事先知道哪些键与哪种类型相关,那么您应该只使用普通对象:

interface ServiceProvider {
  date: Date,
  other: SomeObject
}

如果您事先不知道但希望能够动态添加密钥,那么没有静态类型的对象可以为您工作。你可以做的是让setter返回一个新类型的对象。然后,TypeScript会跟踪哪些键与哪些值类型一致,但您必须链接您的setter。这是一个示例实现:

interface EmptyServiceProvider {
  set<K extends string, V>(k: K, v: V): ServiceProvider<K, V>;
}

interface ServiceProvider<K extends string, V> extends EmptyServiceProvider {
   set<K extends string, V>(k: K, v: V): this & ServiceProvider<K, V>;
   get(k: K): V;  
}


function emptyProvider(): EmptyServiceProvider {
  const data = {} as { [k: string]: any };
  const provider = {
    get(k: string) {
      return data[k];
    },
    set(k: string, v: any) {
      data[k] = v;
      return provider;
    }    
  };
  return provider;
} 

观看它的实际操作:

let provider = emptyProvider()
  .set('date', new Date()) //chaining
  .set('other', new SomeObject()); //chaining

const d = provider.get('date'); // inferred as Date
const o = provider.get('other'); // inferred as SomeObject
const x = provider.get('whoops'); // error, "whoops" doesn't work

这样可行,但提供者的监管链很重要。如果有人向您提供了来自未知来源的ServiceProvider,您可能无法让TypeScript了解从键到值类型的映射:

declare const providerFromSomewhereElse: ServiceProvider<string, any>
const hmm = providerFromSomewhere.get('date'); // any

所以我不确定这对你有多大用处,但是我能在TypeScript中找到最接近动态类型对象的东西。希望有所帮助;祝好运!