Typescript 如何键入此合并函数?
我对类型脚本或其他类型语言中标准类型以外的任何东西都比较陌生,我正在尝试找出一种键入此函数的好方法。我想去掉参数和返回类型的任何类型 函数返回的第一个值不是null、未定义或NaNTypescript 如何键入此合并函数?,typescript,Typescript,我对类型脚本或其他类型语言中标准类型以外的任何东西都比较陌生,我正在尝试找出一种键入此函数的好方法。我想去掉参数和返回类型的任何类型 函数返回的第一个值不是null、未定义或NaN /** returns the first not-null value excluding NaN */ const coalesce = (...args: any): any => { for (let i = 0; i < args.length; i++) { // args[i]
/** returns the first not-null value excluding NaN */
const coalesce = (...args: any): any => {
for (let i = 0; i < args.length; i++) {
// args[i] === args[i] is to avoid NaN, because NaN !== NaN
if (args[i] != null && args[i] === args[i]) {
return args[i];
}
}
return null;
};
最简单的版本是捕获我们在一个类型中传递的参数类型的类型,并返回所有参数类型的并集 我们可以使用中的元组将所有参数的类型捕获为元组类型,并获得元组中所有类型的并集:
const coalesce = <T extends any[]>(...args: T): T[number] => {
for (let i = 0; i < args.length; i++) {
// args[i] === args[i] is to avoid NaN, because NaN !== NaN
if (args[i] != null && args[i] === args[i]) {
return args[i];
}
}
return null;
};
// o is string | number | null | undefined under strict null checks
// T is [null, undefined, number, string] so T[number] is string | number | null | undefined
let o = coalesce(null, undefined, NaN, 'maybe')
最简单的版本是捕获我们在一个类型中传递的参数类型的类型,并返回所有参数类型的并集 我们可以使用中的元组将所有参数的类型捕获为元组类型,并获得元组中所有类型的并集:
const coalesce = <T extends any[]>(...args: T): T[number] => {
for (let i = 0; i < args.length; i++) {
// args[i] === args[i] is to avoid NaN, because NaN !== NaN
if (args[i] != null && args[i] === args[i]) {
return args[i];
}
}
return null;
};
// o is string | number | null | undefined under strict null checks
// T is [null, undefined, number, string] so T[number] is string | number | null | undefined
let o = coalesce(null, undefined, NaN, 'maybe')
好,这实际上是一个相当复杂的类型函数
首先,
NaN
提出了一个问题。TypeScript没有对的表示。值NaN
的类型为number
。因此,当您看到类型为number
的值时,它可能是NaN
,编译器无法阻止这种情况。因此,当我看到一个number
参数时,为了coalesce()
的目的,我必须将其视为类似于number | null
。因此:
function hmm(n: number) { return coalesce(n, "oops"); }
应返回类型为number |“oops”
的值,而不仅仅是number
。我可以通过明确认识到数值文本不能是NaN
,对其进行一些改进,以便
coalesce(123, "oops");
应计算为123
而不是123 |“oops”
,因为123
是已知的非NaN
数字
因此,如果您将参数处理为
coalesce()
,那么您需要的是从左到右遍历元组,建立元组中类型的并集,去掉任何可能的null
或未定义的类型。如果您遇到的类型肯定不是null
、未定义的
、或编号
,则可以停止。否则,您将结束,将null
添加到您的联合,然后停止
这种类型自然是通过TypeScript进行递归的。有很多方法可以解决这个问题,但我确信唯一一种可以处理的方法是将递归类型展开到某个固定的深度,然后退出
让我们开始编写类型:
// Head<L>: return the first element of a tuple L
// Head<[string, number, boolean]> ~ string
type Head<L extends any[]> = ((...l: L) => void) extends
((h: infer H, ...t: infer T) => void) ? H : never;
// Tail<L>: return the tuple L with the first element removed
// Tail<[string, number, boolean]> ~ [number, boolean]
type Tail<L extends any[]> = ((...l: L) => void) extends
((h: infer H, ...t: infer T) => void) ? T : never;
// MightSkip<T>: return unknown if coalesce() might possibly skip a value
// of this type; return never if coalesce() will definitely stop at a value
// of this type.
// MightSkip<string | null> ~ unknown (might be null)
// MightSkip<string> ~ never
// MightSkip<number> ~ unknown (it might skip because NaN)
// MightSkip<123> ~ never (it knows 123 is not NaN)
type MightSkip<T> =
null extends T ? unknown :
undefined extends T ? unknown :
[T] extends [number] ? (number extends T ? unknown : never) :
number extends T ? unknown :
never;
请注意,每个FirstNonNull1
,FirstNonNull2
,etc类型都只引用下一个类型,因此它不再是循环的。在使用剩余类型的并集退出更长的参数列表之前,上述方法应该适用于长度为7左右的任何元组。如果你期望更长的参数列表,你可以很容易地扩展上面的方案
差不多了:为了捕获像123
这样的文本类型的参数,而不让它们自动扩展到像number
这样的非文本类型,我们需要使用像可缩小的
这样的类型
type Narrowable = string | number | boolean | symbol | object |
null | undefined | void | ((...args: any[]) => any) | {};
最后,让我们键入coalesce()
,看看编译器告诉我们什么:
/** returns the first not-null value excluding NaN */
const coalesce = <T extends Narrowable[]>(...args: T): FirstNonNull<T> => {
for (let i = 0; i < args.length; i++) {
// args[i] === args[i] is to avoid NaN, because NaN !== NaN
if (args[i] != null && args[i] === args[i]) {
return args[i] as any; // assert
}
}
return null as any; // assert
};
这看起来和我想象的一样好v0
只是“嘿”
,而不是“嘿”|“你”
,因为coalesce()
肯定会在“嘿”
处停止v1
是number |“maybe”
,因为编译器将NaN
视为number
,所以没有更好的了v2
是“嘿”|“你”| 123
,因为前两个参数中的任何一个可能为null,但123
肯定不是,所以它必须是这三个参数中的一个。而v3
是null
,因为它从参数列表的末尾掉了下来
好,这实际上是一个相当复杂的类型函数
首先,NaN
提出了一个问题。TypeScript没有对的表示。值NaN
的类型为number
。因此,当您看到类型为number
的值时,它可能是NaN
,编译器无法阻止这种情况。因此,当我看到一个number
参数时,为了coalesce()
的目的,我必须将其视为类似于number | null
。因此:
function hmm(n: number) { return coalesce(n, "oops"); }
应返回类型为number |“oops”
的值,而不仅仅是number
。我可以通过明确认识到数值文本不能是NaN
,对其进行一些改进,以便
coalesce(123, "oops");
应计算为123
而不是123 |“oops”
,因为123
是已知的非NaN
数字
因此,如果您将参数处理为coalesce()
,那么您需要的是从左到右遍历元组,建立元组中类型的并集,去掉任何可能的null
或未定义的类型。如果您遇到的类型肯定不是null
、未定义的
、或编号
,则可以停止。否则,您将结束,将null
添加到您的联合,然后停止
这种类型自然是通过TypeScript进行递归的。有很多方法可以解决这个问题,但我确信唯一一种可以处理的方法是将递归类型展开到某个固定的深度,然后退出
让我们开始编写类型:
// Head<L>: return the first element of a tuple L
// Head<[string, number, boolean]> ~ string
type Head<L extends any[]> = ((...l: L) => void) extends
((h: infer H, ...t: infer T) => void) ? H : never;
// Tail<L>: return the tuple L with the first element removed
// Tail<[string, number, boolean]> ~ [number, boolean]
type Tail<L extends any[]> = ((...l: L) => void) extends
((h: infer H, ...t: infer T) => void) ? T : never;
// MightSkip<T>: return unknown if coalesce() might possibly skip a value
// of this type; return never if coalesce() will definitely stop at a value
// of this type.
// MightSkip<string | null> ~ unknown (might be null)
// MightSkip<string> ~ never
// MightSkip<number> ~ unknown (it might skip because NaN)
// MightSkip<123> ~ never (it knows 123 is not NaN)
type MightSkip<T> =
null extends T ? unknown :
undefined extends T ? unknown :
[T] extends [number] ? (number extends T ? unknown : never) :
number extends T ? unknown :
never;
请注意,每个FirstNonNull1
,FirstNonNull2
,etc类型都只引用下一个类型,因此它不再是循环的。在使用剩余类型的并集退出更长的参数列表之前,上述方法应该适用于长度为7左右的任何元组。如果你期望更长的参数列表
const v0 = coalesce(null, undefined, "hey", "you");
// const v0: "hey"
// TypeScript doesn't have a NaN literal, so NaN is treated as number
const v1 = coalesce(null, undefined, NaN, 'maybe');
// const v1: number | "maybe"
const coinFlip = () => Math.random() < 0.5
const v2 = coalesce(coinFlip() ? "hey" : null, coinFlip() ? "you" : undefined, 123);
// const v2: "hey" | "you" | 123
const v3 = coalesce(undefined, undefined)
// const v3: null;