[英]Typescript: Generic object mapping function
對於以下類似於[].map
函數,但用於對象
function mapObject(f, obj) {
return Object.keys(obj).reduce((ret, key) => {
ret[key] = f(obj[key])
return ret
}, {})
}
有沒有辦法輸入它以便以下工作?
interface InputType {
numberValue: number
stringValue: string
}
interface OutputType {
numberValue: string
stringValue: number
}
const input: InputType = {
numberValue: 5,
stringValue: "bob@gmail.com",
}
function applyChanges(input: number): string
function applyChanges(input: string): number
function applyChanges(input: number | string): number | string {
return typeof input === "number" ? input.toString() : input.length
}
const output: OutputType = mapObject(applyChanges, input) // <-- How to get the correct 'OutputType'
這有效,但非常特定於applyChanges
函數
type MapObject<T> = {
[K in keyof T]: T[K] extends number
? string
: T[K] extends string ? number : never
}
function mapObject<F extends FunctionType, T>(f: F, obj: T): MapObject<T>
有沒有更通用的解決方案?
typescript 2.1 發行說明中有一個簽名。
結合您的代碼,我最終得到:
function mapObject<K extends string, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U> {
return Object.keys(obj).reduce((ret, key) => {
const k = key as K;
ret[k] = f(obj[k]);
return ret
}, {} as Record<K, U>)
}
是的,您可以使用 lambda 類型來描述f
的輸入和輸出的類型,然后添加一個約束,即f
的輸入類型(這里稱為A
)必須是obj
類型的值的類型的一部分,有點模糊地稱為O[keyof O]
function mapObject<A extends O[keyof O], B, O>(f: (a: A) => B, obj: O) {
return Object.keys(obj).reduce((ret, key) => {
ret[key] = f(obj[key])
return ret
}, {})
}
正如此處所建議的,您可以在使用 keyof 時引入類型別名以提高可讀性:
type valueof<T> = T[keyof T]
您需要更高級的類型才能根據f
執行的類型正確描述mapObject
執行的類型轉換。 如果你使用我最喜歡的迷你庫來偽造更高級的類型,你可以這樣設置:
// Matt's mini "type functions" library
const INVARIANT_MARKER = Symbol();
type Invariant<T> = { [INVARIANT_MARKER](t: T): T };
interface TypeFuncs<C, X> {}
const FUN_MARKER = Symbol();
type Fun<K extends keyof TypeFuncs<{}, {}>, C> = Invariant<[typeof FUN_MARKER, K, C]>;
const BAD_APP_MARKER = Symbol();
type BadApp<F, X> = Invariant<[typeof BAD_APP_MARKER, F, X]>;
type App<F, X> = [F] extends [Fun<infer K, infer C>] ? TypeFuncs<C, X>[K] : BadApp<F, X>;
// Scenario
// https://github.com/Microsoft/TypeScript/issues/26242 will make this better.
function mapObject<F, B>() {
return function <O extends { [P in keyof O]: B }>
(f: <X extends B>(arg: X) => App<F, X>, obj: O): {[P in keyof O]: App<F, O[P]>} {
return Object.keys(obj).reduce((ret, key) => {
const key2 = <keyof O>key;
ret[key2] = f(obj[key2])
return ret
}, <{[P in keyof O]: App<F, O[P]>}>{})
};
}
const F_applyChanges = Symbol();
type F_applyChanges = Fun<typeof F_applyChanges, never>;
interface TypeFuncs<C, X> {
[F_applyChanges]: X extends number ? string : X extends string ? number : never;
}
// Take advantage of the lax checking of overload signatures. With
// https://github.com/Microsoft/TypeScript/issues/24085, we may be able
// to type check the body of applyChanges based on the first signature.
function applyChanges<X extends number | string>(input: X): App<F_applyChanges, X>
function applyChanges(input: number | string): number | string {
return typeof input === "number" ? input.toString() : input.length;
}
interface InputType {
numberValue: number
stringValue: string
}
interface OutputType {
numberValue: string
stringValue: number
}
const input: InputType = {
numberValue: 5,
stringValue: "bob@gmail.com",
}
const output: OutputType = mapObject<F_applyChanges, number | string>()
(applyChanges, input);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.