TS4.1 之前的答案;
您无法自动执行此操作。最大的障碍是当前有 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 并将T 中的任何键替换为M 中存在的任何键与来自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 的优势
另一个更新:如果您希望它与可选属性一起使用,它会变得更加复杂:
type RequiredKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? never : K }[keyof T];
type OptionalKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? K : never }[keyof T];
type MapKeys<T, M extends Record<string, string>> =
KeyValueTupleToObject<ValueOf<{
[K in RequiredKeys<T>]-?: [K extends keyof M ? M[K] : K, T[K]]
}>> & Partial<KeyValueTupleToObject<ValueOf<{
[K in OptionalKeys<T>]-?: [K extends keyof M ? M[K] : K, T[K]]
}>>> extends infer O ? { [K in keyof O]: O[K] } : never;
如果您需要支持索引签名,它会变得更加复杂。
Playground link to code