从对象和键列表推断函数签名
Infer function signature from an object and a list of keys
我有一组表示为对象的指标:
type Indicators = {
UNIT_PRICE: number;
QUANTITY: number;
CURRENCY: "$" | "€";
TOTAL: string;
};
我想描述要对这组指标进行的计算:
type Computation<R extends { [key: string]: any }> = {
output: keyof R;
inputs: Array<keyof R>;
// I Would like the arguments and the returned value to be type checked
compute: (...inputs: any[]) => any;
};
我没有设法表达 inputs
(指标名称)和 compute
函数的参数之间的关系。 output
和 compute
的 return 类型也是如此。我想在某些时候我必须使用映射类型,但我不知道如何使用。
我想要的是能够编写那种代码,如果 compute
的签名与从 inputs
和 output
推断的类型不匹配,打字稿会抱怨。
const computation: Computation<Indicators> = {
output: "TOTAL",
inputs: ["UNIT_PRICE", "QUANTITY", "CURRENCY"],
compute: (unitPrice, quantity, currency) =>
(unitPrice * quantity).toFixed(2) + currency.toUpperCase(),
};
这个答案是由一个叫@webstrand 的人在 gitter 上提供的。
首先,映射类型可以表达 output
、inputs
和 compute
签名之间的关系:
type Computation<
R extends { [key: string]: any },
// readonly is important to make typescript infer tuples instead of arrays later on
I extends readonly (keyof R)[],
O extends keyof R,
> = {
output: O;
inputs: I;
compute: (...inputs: { [P in keyof I]: I[P] extends keyof R ? R[I[P]] : never }) => R[O];
};
此版本要求用户明确类型。
然后是“双重调用”hack,因为打字稿仅在函数调用站点推断类型:
function createComputation<R extends { [key: string]: any }>(): <I extends readonly (keyof R)[], O extends keyof R>(o: Computation<R, I, O>) => Computation<R, I, O> {
return (x) => x;
}
现在,我们可以使用 createComputation
来键入检查所有内容:
const validComputation = createComputation<Indicators>()({
output: "TOTAL",
inputs: ["UNIT_PRICE", "QUANTITY", "CURRENCY"] as const,
compute: (unitPrice, quantity, currency) =>
(unitPrice * quantity).toFixed(2) + currency.toUpperCase(),
});
为了让它工作,你需要 Computation
不仅在对象类型中而且在 tuple of input keys and the output key. (Well, technically you could try to reprepresent "every possible tuple of input keys and output key" as a big union 中都是通用的,但这很烦人并且不能很好地扩展,所以我忽略了这种可能性。)例如:
type Computation<T extends Record<keyof T, any>,
IK extends (keyof T)[], OK extends keyof T> = {
output: OK;
inputs: readonly [...IK];
compute: (...inputs: {
[I in keyof IK]: T[Extract<IK[I], keyof T>]
}) => T[OK];
};
对象类型是T
(我是从R
改过来的),输入键是IK
,输出键是OK
。 compute()
方法的输入参数列表 maps the tuple 键值类型元组。
但是现在,为了注释任何值的类型,它将变得冗长和多余:
const validComputation: Computation<
Indicators,
["UNIT_PRICE", "QUANTITY", "CURRENCY"],
"TOTAL"
> = {
output: "TOTAL",
inputs: ["UNIT_PRICE", "QUANTITY", "CURRENCY"],
compute: (unitPrice, quantity, currency) =>
(unitPrice * quantity).toFixed(2) + currency.toUpperCase(),
};
理想情况下,您希望编译器为您推断 IK
和OK
,同时让您指定T
。但目前 TypeScript 不允许您部分指定类型;您要么必须如上所述指定整个内容,要么让编译器为您推断整个类型。您可以创建一个辅助函数来让编译器为您推断 IK
和 OK
,但是同样,任何特定调用都只会推断所有 T
、IK
和OK
,或 none 个。整个“部分推理”的事情在 TypeScript 中是一个悬而未决的问题;参见 microsoft/TypeScript#26242 进行讨论。
我们可以用当前语言做的最好的事情是编写类似 curried 泛型函数的东西,您在初始函数上指定 T
,然后让编译器推断 IK
和 OK
对返回函数的调用:
const asComputation = <T,>() =>
<IK extends (keyof T)[], OK extends keyof T>(c: Computation<T, IK, OK>) => c;
它是这样工作的:
const asIndicatorsComputation = asComputation<Indicators>();
const validComputation = asIndicatorsComputation({
output: "TOTAL",
inputs: ["UNIT_PRICE", "QUANTITY", "CURRENCY"],
compute: (unitPrice, quantity, currency) =>
(unitPrice * quantity).toFixed(2) + currency.toUpperCase(),
});
const wrongComputation = asIndicatorsComputation({
output: "TOTAL",
inputs: ["UNIT_PRICE"],
compute: (unitPrice) => unitPrice.toUpperCase() // error!
});
您调用 asComputation<Indicators>()
来获得一个新的辅助函数,该函数接受编译器推断的某些 IK
和 OK
的 Computation<Indicators, IK, OK>
值。您可以看到,在 compute()
方法参数是上下文类型的情况下,您获得了所需的行为,如果您做错了什么,您将收到错误消息。
我有一组表示为对象的指标:
type Indicators = {
UNIT_PRICE: number;
QUANTITY: number;
CURRENCY: "$" | "€";
TOTAL: string;
};
我想描述要对这组指标进行的计算:
type Computation<R extends { [key: string]: any }> = {
output: keyof R;
inputs: Array<keyof R>;
// I Would like the arguments and the returned value to be type checked
compute: (...inputs: any[]) => any;
};
我没有设法表达 inputs
(指标名称)和 compute
函数的参数之间的关系。 output
和 compute
的 return 类型也是如此。我想在某些时候我必须使用映射类型,但我不知道如何使用。
我想要的是能够编写那种代码,如果 compute
的签名与从 inputs
和 output
推断的类型不匹配,打字稿会抱怨。
const computation: Computation<Indicators> = {
output: "TOTAL",
inputs: ["UNIT_PRICE", "QUANTITY", "CURRENCY"],
compute: (unitPrice, quantity, currency) =>
(unitPrice * quantity).toFixed(2) + currency.toUpperCase(),
};
这个答案是由一个叫@webstrand 的人在 gitter 上提供的。
首先,映射类型可以表达 output
、inputs
和 compute
签名之间的关系:
type Computation<
R extends { [key: string]: any },
// readonly is important to make typescript infer tuples instead of arrays later on
I extends readonly (keyof R)[],
O extends keyof R,
> = {
output: O;
inputs: I;
compute: (...inputs: { [P in keyof I]: I[P] extends keyof R ? R[I[P]] : never }) => R[O];
};
此版本要求用户明确类型。
然后是“双重调用”hack,因为打字稿仅在函数调用站点推断类型:
function createComputation<R extends { [key: string]: any }>(): <I extends readonly (keyof R)[], O extends keyof R>(o: Computation<R, I, O>) => Computation<R, I, O> {
return (x) => x;
}
现在,我们可以使用 createComputation
来键入检查所有内容:
const validComputation = createComputation<Indicators>()({
output: "TOTAL",
inputs: ["UNIT_PRICE", "QUANTITY", "CURRENCY"] as const,
compute: (unitPrice, quantity, currency) =>
(unitPrice * quantity).toFixed(2) + currency.toUpperCase(),
});
为了让它工作,你需要 Computation
不仅在对象类型中而且在 tuple of input keys and the output key. (Well, technically you could try to reprepresent "every possible tuple of input keys and output key" as a big union 中都是通用的,但这很烦人并且不能很好地扩展,所以我忽略了这种可能性。)例如:
type Computation<T extends Record<keyof T, any>,
IK extends (keyof T)[], OK extends keyof T> = {
output: OK;
inputs: readonly [...IK];
compute: (...inputs: {
[I in keyof IK]: T[Extract<IK[I], keyof T>]
}) => T[OK];
};
对象类型是T
(我是从R
改过来的),输入键是IK
,输出键是OK
。 compute()
方法的输入参数列表 maps the tuple 键值类型元组。
但是现在,为了注释任何值的类型,它将变得冗长和多余:
const validComputation: Computation<
Indicators,
["UNIT_PRICE", "QUANTITY", "CURRENCY"],
"TOTAL"
> = {
output: "TOTAL",
inputs: ["UNIT_PRICE", "QUANTITY", "CURRENCY"],
compute: (unitPrice, quantity, currency) =>
(unitPrice * quantity).toFixed(2) + currency.toUpperCase(),
};
理想情况下,您希望编译器为您推断 IK
和OK
,同时让您指定T
。但目前 TypeScript 不允许您部分指定类型;您要么必须如上所述指定整个内容,要么让编译器为您推断整个类型。您可以创建一个辅助函数来让编译器为您推断 IK
和 OK
,但是同样,任何特定调用都只会推断所有 T
、IK
和OK
,或 none 个。整个“部分推理”的事情在 TypeScript 中是一个悬而未决的问题;参见 microsoft/TypeScript#26242 进行讨论。
我们可以用当前语言做的最好的事情是编写类似 curried 泛型函数的东西,您在初始函数上指定 T
,然后让编译器推断 IK
和 OK
对返回函数的调用:
const asComputation = <T,>() =>
<IK extends (keyof T)[], OK extends keyof T>(c: Computation<T, IK, OK>) => c;
它是这样工作的:
const asIndicatorsComputation = asComputation<Indicators>();
const validComputation = asIndicatorsComputation({
output: "TOTAL",
inputs: ["UNIT_PRICE", "QUANTITY", "CURRENCY"],
compute: (unitPrice, quantity, currency) =>
(unitPrice * quantity).toFixed(2) + currency.toUpperCase(),
});
const wrongComputation = asIndicatorsComputation({
output: "TOTAL",
inputs: ["UNIT_PRICE"],
compute: (unitPrice) => unitPrice.toUpperCase() // error!
});
您调用 asComputation<Indicators>()
来获得一个新的辅助函数,该函数接受编译器推断的某些 IK
和 OK
的 Computation<Indicators, IK, OK>
值。您可以看到,在 compute()
方法参数是上下文类型的情况下,您获得了所需的行为,如果您做错了什么,您将收到错误消息。