hot*_*ell 5 typescript typescript-generics
我无法正确实现泛型isEmpty(value),将提供的值的类型限制缩小到空的对应项。
使用案例:
function getCountryNameById(countries: LookupItem[] = [], countryId?: number): string | undefined {
if (isEmpty(countries) || !isNumber(countryId)) {
// within this branch I would like to get countries argument to be narrowed to empty array type.
// Same would apply for other function which can have argument type of object or string. Why ? -> to prevent someone to do some mad code hacks like accessing non existent value from empty array ( which would happen on runtime ofc ) on compile time
// $ExpectType []
console.log(countries)
return
}
// continue with code logic ...
// implementation ...
}
Run Code Online (Sandbox Code Playgroud)
约束对象的类似情况:
function doSomethingWithObject( data: { foo: string; bar: number } | object ){
if(isEmpty(data)){
// $ExpectType {}
data
// following should throw compile error, as data is empty object
data.foo.toUpercase()
return
}
// here we are sure that data is not empty on both runtime and compile time
}
Run Code Online (Sandbox Code Playgroud)
isEmpty 类型守卫实现:
export const isEmpty = <T extends AllowedEmptyCheckTypes>(
value: T | AllowedEmptyCheckTypes
): value is Empty<T> => {
if (isBlank(value)) {
return true
}
if (isString(value) || isArray(value)) {
return value.length === 0
}
if (isObject(value)) {
return Object.keys(value).length === 0
}
throw new Error(
`checked value must be type of string | array | object. You provided ${typeof value}`
)
}
Run Code Online (Sandbox Code Playgroud)
具有定义的类型:
type EmptyArray = Array<never>
type Blank = null | undefined | void
/**
* // object collects {} and Array<any> so adding both {} and Array<any> is not needed
* @private
*/
export type AllowedEmptyCheckTypes = Blank | string | object
/**
* Empty mapped type that will cast any AllowedEmptyCheckTypes to empty equivalent
* @private
*/
export type Empty<T extends AllowedEmptyCheckTypes> = T extends string
? ''
: T extends any[]
? EmptyArray
: T extends object ? {} : T extends Blank ? T : never
Run Code Online (Sandbox Code Playgroud)
这有点奇怪,因为它从类型角度正确缩小,但不在 if/else 分支内:
代码可以在这里看到:https://github.com/Hotell/rex-tils/pull/13/files#diff-a3cdcb321a05315fcfc3309031eab1d8R177
相关问题:空对象的类型保护
处理此问题的一种方法是将空值检查 ( undefined, null) 与空值检查 ( '', [] {}) 分开。我倾向于对 \xe2\x80\x94isDefined和isEmpty.
第一个可能看起来像这样。请注意typeof检查 \xe2\x80\x94 这使得它也可以处理未声明的变量。
function isDefined<T>(value: T | undefined | null): value is T {\n return (typeof value !== 'undefined') && (value !== null);\n}\nRun Code Online (Sandbox Code Playgroud)\n\n对于空值,可以使用以下模型。
\n\nnamespace Empty {\n export type String = '';\n export type Object = Record<string, never>;\n export type Array = never[];\n}\n\ntype Empty =\n | Empty.Array\n | Empty.Object\n | Empty.String;\n\nfunction isEmpty<T extends string | any[] | object>(subject: T | Empty): subject is Bottom<T> {\n switch (typeof subject) {\n case 'object':\n return (Object.keys(subject).length === 0);\n case 'string':\n return (subject === '');\n default:\n return false;\n }\n}\n\ntype Bottom<T> =\n T extends string\n ? Empty.String\n : T extends any[]\n ? Empty.Array\n : T extends object\n ? Empty.Object\n : never;\nRun Code Online (Sandbox Code Playgroud)\n\n底部值被正确推断。
\n\ndeclare const foo: 'hello' | Empty.String;\ndeclare const bar: [number, number] | Empty.Array;\ndeclare const baz: Window | Empty.Object;\n\nif (isEmpty(foo) && isEmpty(bar) && isEmpty(baz)) {\n console.log(foo, bar, baz);\n}\nRun Code Online (Sandbox Code Playgroud)\n\n编辑:T按照建议添加限制。