[英]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.