在Typescript映射类型中更改属性名称

时间:2017-06-02 07:43:22

标签: typescript

我有一组这样的Typescript对象:

SomeData {
  prop1: string;
  prop2: number;
}

我需要得到一些看起来像这样的对象:

type SomeMoreData= {
  prop1Change: string;
  prop2Change: number;
}

我知道如果我只是想改变类型,我可以这样做:

type SomeMoreData<T> = {
  [P in keyof T]: boolean;
}

哪会给我:

SomeMoreData<SomeData> {
  prop1: boolean;
  prop2: boolean;
}

有没有办法操纵[P in keyof T]位产生的名称?我尝试过像[(P in keyof T) + "Change"]这样的事情没有成功。

2 个答案:

答案 0 :(得分:11)

你不能自动完成。最大的阻碍是,当前no type operator允许您在类型级别追加字符串文字,因此您甚至无法描述您正在进行的转换:

// without Append<A extends string, B extends string>, you can't type this:

function appendChange<T extends string>(originalKey: T): Append<T,'Change'> {
  return originalKey+'Change';
}

此功能有suggestion,但是谁知道它是否会发生。

这意味着如果你想转换键,你需要实际硬编码你正在寻找的特定映射,从字符串文字到字符串文字:

type SomeMoreDataMapping = {
  prop1: "prop1Change"
  prop2: "prop2Change"
}

使用此映射,您可以定义以下内容:

type ValueOf<T> = T[keyof T]
type KeyValueTupleToObject<T extends [keyof any, any]> = {
  [K in T[0]]: Extract<T, [K, any]>[1]
}
type MapKeys<T, M extends Record<string, string>> =
  KeyValueTupleToObject<ValueOf<{
    [K in keyof T]: [K extends keyof M ? M[K] : K, T[K]]
  }>>

简短的问题:

  • ValueOf<T>只返回类型T的属性值类型的并集。
  • KeyValueTupleToObject采用这样的元组类型的联合:["a",string] | ["b",number]并将它们转换为如下对象类型:{a: string, b: number}
  • MapKeys<T, M>采用T类型和密钥映射M,并使用相应的密钥替换TM中的任何密钥来自M。如果T中没有M中的密钥,则密钥不会被转换。如果M中没有T中的密钥,则会忽略该密钥。

现在你可以(最后)这样做:

type SomeMoreData= MapKeys<SomeData, SomeMoreDataMapping>;

如果你检查SomeMoreData,你会发现它的类型正确:

var someMoreData: SomeMoreData = {
  prop1Change: 'Mystery Science Theater',
  prop2Change: 3000
} // type checks

这应该可以让你做一些有趣的事情:

function makeTheChange<T>(input: T): MapKeys<T, SomeMoreDataMapping> {
  var ret = {} as MapKeys<T, SomeMoreDataMapping>;
  for (var k in input) {
    // lots of any needed here; hard to convince the type system you're doing the right thing
    var nk: keyof typeof ret = <any>((k === 'prop1') ? 'prop1Change' : (k === 'prop2') ? 'prop2Change' : k);
    ret[nk] = <any>input[k];    
  }
  return ret;
}

var changed = makeTheChange({ prop1: 'Gypsy', prop2: 'Tom', prop3: 'Crow' });
console.log(changed.prop1Change.charAt(0)); //ok
console.log(changed.prop2Change.charAt(0)); //ok
console.log(changed.prop3.charAt(0)); //ok

希望有所帮助。祝你好运!

2018年9月更新,以利用TS 2.8引入的conditional types

答案 1 :(得分:4)

Template string types一经发布(类似于打字稿4.1),您将能够:

type SomeData = {
    prop1: string;
    prop2: number;
}

type WithChange<T> = { [P in keyof T & string as `${P}Change`]: T[P] };

type SomeMoreData = WithChange<SomeData>; // {  prop1Change: string, prop2Change: number }

Playground


在上面的示例中,我们使用了两个新功能:映射类型中的as子句 + 模板字符串类型。

模板字符串类型是与模板字符串表达式等效的类型空间。与模板字符串表达式相似,模板字符串类型包含在反引号分隔符中,并且可以包含格式为${T}的占位符,其中T是可分配给string,{{1}的类型},numberboolean。模板字符串类型提供了连接文字字符串,将非字符串原始类型的文字转换为其字符串表示形式以及更改字符串文字的大小写或大小写的功能。此外,通过类型推断,模板字符串类型提供了字符串模式匹配和分解的简单形式。

一些例子:

bigint

其他映射的类型支持可选的type EventName<T extends string> = `${T}Changed`; type Concat<S1 extends string, S2 extends string> = `${S1}${S2}`; type T0 = EventName<'foo'>; // 'fooChanged' type T1 = EventName<'foo' | 'bar' | 'baz'>; // 'fooChanged' | 'barChanged' | 'bazChanged' type T2 = Concat<'Hello', 'World'>; // 'HelloWorld' type T3 = `${'top' | 'bottom'}-${'left' | 'right'}`; // 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' 子句,通过该子句可以指定生成的属性名称的转换:

as

其中{ [P in K as N]: X } 必须是可分配给N的类型。

更多详细信息和示例here