[英]TypeScript: Can you define a return Type structure based on an argument of the function?
[英]typescript: validate function argument based on callback return type
我正在嘗試鍵入一個函數,該函數為每個數組項運行回調並使用新屬性擴展該項:
interface Row {
a: number
}
export function extender<
Data extends Row[],
T extends object,
Props extends keyof T
>(data: Data, extend: (row: Row) => T, newProp?: Props): (T & Row)[] {
return data.map(row => ({
...row,
...extend(row)
}))
}
當我打電話時,這份草稿似乎正在發揮作用
extender([{ a: 1 }, { a: 2 }], () => ({ once: 1, more: 2 }), 'once')
但是,一旦我嘗試在extend
回調中使用row
參數,我就會得到一個錯誤Argument of type '"once"' is not assignable to parameter of type 'undefined'.
:
// TS_ERROR: Argument of type '"once"' is not assignable to parameter of type 'undefined'.
extender([{ a: 1 }, { a: 2 }], (row) => ({ once: 1, more: row.a * 2 }), 'once')
我錯過了什么?
在您的示例中, T
受object
(實際上代表所有 non-primitives )的約束。 “任何普通對象”的類型可以表示為Record<PropertyKey, any>
(它使用類型實用程序Record<Keys, Type>
)。 PropertyKey
是string | number | symbol
string | number | symbol
string | number | symbol
,所以這種類型的意思是“任何具有字符串、數字和/或符號鍵的對象,這些屬性的值可以是任何類型”。
通過簡單地替換該約束,您的代碼應該可以在沒有診斷錯誤的情況下編譯。
如果輸入數組的成員不完全是Row
類型(相反,它們擴展了它),則函數的當前返回類型會丟失一些類型信息,並且它不承認T
可以覆蓋類型的概念在map操作中對象合並過程中的屬性a
。 映射類型可以幫助您保留此類類型信息,甚至可以保留諸如具有明確定義的元素數量(元組)的輸入數組的長度之類的內容。 考慮以下示例:
type PlainObject = Record<PropertyKey, any>;
type Mutable<T extends PlainObject> = { -readonly [K in keyof T]: T[K] };
type ExtendedTuple<A extends readonly PlainObject[], T extends PlainObject> = {
-readonly [Index in keyof A]: Mutable<Omit<A[Index], keyof T>> & Mutable<T>;
}
type Row = { a: number };
export function extender <
Data extends readonly Row[],
T extends PlainObject,
Prop extends keyof T,
>(data: Data, extend: (row: Data[number]) => T, newProp?: Prop): ExtendedTuple<Data, T> {
return data.map(row => ({ ...row, ...extend(row) })) as unknown as ExtendedTuple<Data, T>;
}
const extended1 = extender(
[{ a: 1 }, { a: 2 }],
() => ({ once: 1, more: 2 }),
'once',
); // Record<'a' | 'more' | 'once', number>[]
const extended2 = extender(
[{ a: 1 }, { a: 2 }],
(row) => ({ once: 1, more: row.a * 2 }),
'once',
); // Record<'a' | 'more' | 'once', number>[]
const [
e2o0, // Record<'a' | 'more' | 'once', number> | undefined
e2o1, // Record<'a' | 'more' | 'once', number> | undefined
e2o2, // Record<'a' | 'more' | 'once', number> | undefined
e2o3, // Record<'a' | 'more' | 'once', number> | undefined
e2o4, // Record<'a' | 'more' | 'once', number> | undefined
// etc...
] = extended2;
// Tuple correctness:
const extended3 = extender(
[{ a: 1, b: 1 }, { a: 2, b: 2 }, { a: 3, b: 3 }] as [Record<'a' | 'b', number>, Record<'a' | 'b', number>, Record<'a' | 'b', number>],
(row) => ({ a: 'hi', once: 1, more: row.a * 2 }),
'once',
); /* [
{ a: string } & Record<'b' | 'more' | 'once', number>,
{ a: string } & Record<'b' | 'more' | 'once', number>,
{ a: string } & Record<'b' | 'more' | 'once', number>,
] */
// Note the values at the tuple indexes are not potentially undefined:
const [
e3o0, // { a: string } & Record<'b' | 'more' | 'once', number>
e3o1, // { a: string } & Record<'b' | 'more' | 'once', number>
e3o2, // { a: string } & Record<'b' | 'more' | 'once', number>
e3o3, /* This error is expected
~~
Tuple type '[...]' of length '3' has no element at index '3'.(2493) */
] = extended3;
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.