我无法正确实现通用 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}`
  )
}

使用定义的类型:
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

这有点奇怪,因为它从类型角度正确缩小,但不在 if/else 分支内:

isEmpty for string values

isEmpty for array values

isEmpty for object values

代码可以在这里看到:https://github.com/Hotell/rex-tils/pull/13/files#diff-a3cdcb321a05315fcfc3309031eab1d8R177

相关问题:Type Guard for empty object

最佳答案

处理此问题的一种方法是将空检查( undefinednull )与空值检查( ''[] {} )分开。我倾向于为此使用两种类型的保护—— isDefinedisEmpty

第一个可能看起来像这样。请注意 typeof 检查——这使它也适用于未声明的变量。

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

对于空值,可以使用以下模型。
namespace Empty {
  export type String = '';
  export type Object = Record<string, never>;
  export type Array = never[];
}

type Empty =
  | Empty.Array
  | Empty.Object
  | Empty.String;

function isEmpty<T extends string | any[] | object>(subject: T | Empty): subject is Bottom<T> {
  switch (typeof subject) {
    case 'object':
      return (Object.keys(subject).length === 0);
    case 'string':
      return (subject === '');
    default:
      return false;
  }
}

type Bottom<T> =
  T extends string
    ? Empty.String
    : T extends any[]
        ? Empty.Array
        : T extends object
            ? Empty.Object
            : never;

正确推断底部值。
declare const foo: 'hello' | Empty.String;
declare const bar: [number, number] | Empty.Array;
declare const baz: Window | Empty.Object;

if (isEmpty(foo) && isEmpty(bar) && isEmpty(baz)) {
  console.log(foo, bar, baz);
}

编辑: 按照建议添加了对 T 的约束。

关于Typescript - isEmpty 函数的通用类型保护,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/52188387/

10-11 23:44