Typescript-isEmpty函数的泛型类型保护

Typescript-isEmpty函数的泛型类型保护,typescript,typescript-generics,Typescript,Typescript Generics,我无法正确地实现genericisEmpty(value),将提供的值的类型约束缩小到它的空对应项 用例: function getCountryNameById(countries: LookupItem[] = [], countryId?: number): string | undefined { if (isEmpty(countries) || !isNumber(countryId)) { // within this branch I would like to

我无法正确地实现generic
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 ...
}
约束对象的类似情况:

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
}
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}`
  )
}
const isEmpty = <T extends string | object | any[]>(
  value: T
): value is never => {
  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 ${
      // tslint:disable-next-line:strict-type-predicates
      value === null ? 'null' : typeof value
    }`
  )
}
export const isEmpty=(
值:T | AllowedEmptyCheckTypes
):值为空=>{
如果(为空(值)){
返回真值
}
if(isString(值)| | isArray(值)){
返回值.length==0
}
if(等值对象(值)){
返回对象。键(值)。长度==0
}
抛出新错误(
`选中的值必须是字符串|数组|对象的类型。您提供了${typeof value}`
)
}
使用已定义的类型:

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
键入EmptyArray=Array
类型Blank=null |未定义| void
/**
*//对象收集{}和数组

代码可以在这里看到:

相关问题:

这里

if (isEmpty(countries) || !isNumber(countryId)) {
您有两个条件,其中只有一个是
国家/地区的类型保护
,这就是为什么
国家/地区的类型在if中不会更改的原因


对于对象,
{}
并不表示空对象。除了
null
undefined
之外,任何内容都可以分配给
{}
类型的变量。您可能想使用
{[prop:string]:never}
{[prop:string]:undefined}
来代替。

因此,在几次twitter讨论和长时间的/Github搜索之后,我最终得到了以下解决方案:

  • 首先,在isEmpty中检查null/undefined没有多大意义(尽管lodash.isEmpty可以处理这一点,但它做的太多了,而且不是以非常明确的方式)
  • 由于
    {}
    |
    对象
    |
    任何[]
    之间基本上没有区别,因此类型保护变窄将永远不会像预期的那样工作
  • 最终的解决方案只接受有效值作为参数->js对象和字符串进行检查,guard返回
    never
    ,因此匹配值的类型为
    never
    ,因为无论如何,在
    if(isEmpty(value)){…}中执行任何进一步的登录都没有任何意义
    语句,而不是终止程序或抛出错误
以下是最终实施:

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}`
  )
}
const isEmpty = <T extends string | object | any[]>(
  value: T
): value is never => {
  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 ${
      // tslint:disable-next-line:strict-type-predicates
      value === null ? 'null' : typeof value
    }`
  )
}
const isEmpty=(
值:T
):值从不=>{
if(isString(值)| | isArray(值)){
返回值.length==0
}
if(等值对象(值)){
返回对象。键(值)。长度==0
}
抛出新错误(
`选中值必须是字符串|数组|对象的类型。您提供了${
//tslint:禁用下一行:严格类型谓词
值===null?'null':值的类型
}`
)
}

处理此问题的一种方法是将空检查(
未定义的
空的
)与空值检查(
'
[]
{}
)分开。我倾向于使用两种类型的保护-
isDefined
isEmpty

第一个可能是这样的。注意
typeof
check-这使得它也可以处理未声明的变量

function isDefined<T>(value: T | undefined | null): value is T {
  return (typeof value !== 'undefined') && (value !== null);
}

编辑:根据建议对
T
添加了约束。

我很难理解这一点。您是否可以将所有相关代码作为代码(而不是代码图片或代码链接)包含在此处,删除重现问题所不需要的任何内容,并明确指出您的问题所在(您期望的是什么,您得到的是什么)?提供答案对于获得有意义的答案大有帮助。这些都不管用。我已经试过了:{[prop:keyof T]:never}或{[prop:keyof T]:undefined}对,我接近于类似的一个,在对象的测试中唯一缺少的是显式类型强制转换对象到该对象或空对象的并集。我会继续用这个。非常感谢!我还将约束泛型,因此如果使用者get希望支持不受支持的isEmpty检查类型(它希望对象值上出现错误,但更好的是,我猜什么都没有:))export const isEmpty=(value:T | Empty):value是Bottom