【问题标题】:Is it possible to design a type guard for an array being empty?是否可以为空数组设计类型保护?
【发布时间】:2022-08-18 15:08:20
【问题描述】:

有许多示例说明如何为非空数组设计类型保护。例如this method 在使用noUncheckedIndexedAccess 时效果很好:

type Indices<L extends number, T extends number[] = []> = T[\"length\"] extends L
  ? T[number]
  : Indices<L, [T[\"length\"], ...T]>;

export type LengthAtLeast<T extends readonly any[], L extends number> = Pick<
  Required<T>,
  Indices<L>
>;

// Borrowed from: https://stackoverflow.com/a/69370003/521097
export function hasLengthAtLeast<T extends readonly any[], L extends number>(
  arr: T,
  len: L
): arr is T & LengthAtLeast<T, L> {
  return arr.length >= len;
}

export function isNotEmpty<T extends readonly any[]>(arr: T): arr is T & LengthAtLeast<T, 1> {
  return hasLengthAtLeast(arr, 1);
}

然后:

let foo = [1, 2, 3];

if (isNotEmpty(foo)) 
  foo[0].toString() // does not error
else 
 foo[0].toString() // does error

然而,要检查这个的逆必须反转布尔检查:

let foo = [1, 2, 3];

if (!isNotEmpty(foo)) 
  foo[0].toString(); // now errors
else 
  foo[0].toString(); // now does not error

问题是我认为if (!isNotEmpty(foo)) 读起来有点难看,因为它是双重否定的。

那么问题来了,如何定义一个isEmpty 类型守卫,这样就可以做到if (isEmpty(foo)) 并且仍然得到与上面显示的代码sn-p 相同的结果?这似乎是一个微不足道的问题,但到目前为止我的所有尝试都遭到了挫败。

我认为主要问题是你不能断言类型保护的逆,你不能说某事不是别的东西。

编辑:我被要求提供更多示例。

所以这是我想做的一个例子:

function logFirstDataElement(data: number[]) {
  // Dont do anything if no data
  if (isEmpty(data)) return;

  // this should not error because data should have been narrowed to 
  // [T, ...T] 
  // that is, it should have at least one element
  console.log(data[0].toString()) 
}

可以通过执行以下操作来实现

function logFirstDataElement(data: number[]) {
  // Dont do anything if no data
  if (!isNotEmpty(data)) return;
  console.log(data[0].toString()) 
}

但如上所述,我宁愿避免!isNotEmpty(data) 的“双重否定”

  • 回复:您的update:向编译器断言数组不为空并不会断言它在索引0 处具有值。这些是不同的断言,需要不同的类型保护。 (例如,数组不能为空,但前 200 个元素是 undefined,第一个未定义的元素仅出现在索引 200 处。)
  • 也许值得使用const isFilled=(arr:any[]): arr is [any, ...any[]]=&gt; your code。只需使用两个 typeguard

标签: typescript types


【解决方案1】:

您可以使用将数组设置为空元组[] 的谓词:

TS Playground

操场(和下面的代码)使用编译器选项noUncheckedIndexedAccess(就像您的问题一样),因此即使在false 分支中,您也必须验证每个索引元素以获得不可为空的值。

function isEmpty (array: readonly any[]): array is [] {
  return array.length === 0;
}

const array = [1, 2, 3];

if (isEmpty(array)) {
  const value = array[0]; /*
                      ~
  Tuple type '[]' of length '0' has no element at index '0'.(2493) */
}
else {
  const value = array[0];
      //^? const value: number | undefined
}

【讨论】:

  • 不,这是不正确的,因为非空 case 数组值不应该是 number | undefined 它应该是 number 因为我们刚刚断言它不是空的
  • ^ @mikeysee 不,noUncheckedIndexedAccess 不是这样工作的。如果您想要您描述的行为,则必须禁用该编译器选项。这是禁用该选项的同一个游乐场的链接:tsplay.dev/Nr4v0W
  • 好吧,正如我在我的 OP 中提到的那样,您可以做相反的事情。 isEmpty 检查后剩余的类型应该是一个至少有一个元素 [T, ...T] 的元组
  • @mikeysee 如果输入数组是这种类型(例如const array: [number, ...number[]] = [1, 2, 3];)。
  • 因此,如果不在“数组”变量上声明那个丑陋的元组类型,就不可能做到这一点?似乎应该可以简单地将!isNotEmpty() 变成类型保护,但我猜不是?
猜你喜欢
  • 2019-12-18
  • 1970-01-01
  • 2011-08-05
  • 1970-01-01
  • 2019-09-24
  • 1970-01-01
  • 2018-01-03
  • 2021-05-14
相关资源
最近更新 更多