在TypeScript中,我们可以使用“运行时”键来定义新类型吗?

时间:2018-09-11 18:24:16

标签: typescript

更好地举例说明:

class ModuleOptions {
  key1?: string;
  key2?: string;

  keyA?: string;
  keyB?: string;
}

class Module {
  static options: ModuleOptions = {
    key1: 'key1',
    key2: 'key2',

    keyA: 'keyA',
    keyB: 'keyB'
  };

  static create(options: ModuleOptions) {
    Object.assign(Module.options, options);
  }
}


const myModule = Module.create({
  key1: 'foo'
});

// Now Module.options static field has "foo" as key1...
// Could we constrait fooArgs to have a property named "foo" of type string?
function foo(fooArgs: NewType) {
  // Do stuff
}

是否可以使fooArgs(即NewType)仅接受'foo'作为键,并为其定义相同的类型(string)?

这不起作用(甚至很简单):

class NewType {
  [k in keyof [ModuleOptions.options]]: string;
}
  

计算出的属性名称必须为'string','number','symbol'或'any'类型。

1 个答案:

答案 0 :(得分:2)

您可能会得到想要的东西,但是肯定有痛点。第一个主要问题是TypeScript不允许您随意更改现有值的类型。因此,如果名为foo的变量具有类型string,则以后不能将其更改为number。在您的情况下,如果编译器将Module.options.key1称为string-literal type "key1",那么以后就无法将其类型更改为字符串文字类型"foo"。即使您调用"key1",编译器也希望它永远是Module.create()。有几种解决方法。一种是使用type guards来缩小值的类型(值的类型可以更具体,但不能任意更改),另一种是使用新变量来表示新类型的值。

这是使用后一种方法的可能解决方案...让create返回具有不同类型的Module的新版本:

module Module {

  type Module = typeof Module; // handy type alias

  const defaultOptions = {
    key1: 'key1' as 'key1',
    key2: 'key2' as 'key2',
    keyA: 'keyA' as 'keyA',
    keyB: 'keyB' as 'keyB',
  }; // use type assertions to narrow to string literals

  type DefaultOptions = typeof defaultOptions; // handy type alias

  export const options: Record<keyof DefaultOptions, string> = defaultOptions;

  // represent overwriting properties from T with properties from U
  type Assign<T, U> = { 
    [K in keyof T | keyof U]: K extends keyof U ? U[K] : K extends keyof T ? T[K] : never 
  };

  export function create<T>(
    o: T
  ): { [K in keyof Module]: 'options' extends K ? Assign<DefaultOptions, T> : Module[K] } {
    Object.assign(options, o);
    return Module as any; // can't represent type mutation, use type assertion here
  }

}

使用conditional types表示当您致电optionscreate会发生什么。让我们看看它是如何工作的:

const almostMyModule = Module.create({
  key1: 'foo'
});
almostMyModule.options.key2; // "key2"
almostMyModule.options.key1; // string? oops

糟糕,传递给create()的参数类型被推断为{key1: string},而不是{key1: "foo"}。这是另一个痛苦点。有多种方法可以使推论正确,但是现在我仅使用类型断言来缩小其范围:

const myModule = Module.create({
  key1: 'foo' as 'foo'
});
myModule.options.key1; // 'foo', that's better
myModule.options.key2; // 'key2'

现在编译器将myModule.options.key1称为"foo",我们可以使您想要的NewType

type ValueOf<T> = T[keyof T];
type NewType = { [K in ValueOf<typeof myModule.options>]: string };

检查NewType给我们:

// type NewType = {
//   foo: string;
//   key2: string;
//   keyA: string;
//   keyB: string;
// }

您可以使用它:

function foo(fooArgs: NewType) {
  fooArgs.foo // okay
}

希望有帮助。祝你好运!