用于验证数组的每一项是否已定义的类型保护

Type guard for verifying that each item of an array is defined

我想知道是否可以创建一个类型保护来检查是否定义了数组的每一项。我已经有一个用于检查单个值的类型保护,但是有一个解决方案可以对整个数组执行此操作(会很棒。

为了将其放入代码中,我正在寻找可以键入以下内容的东西:

// Input array type:
[ Something | undefined, SomethingDifferent | undefined ]

// Type guard resolving to:
[ Something, SomethingDifferent ]

目前我有以下类型保护来检查单个项目:

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

类型保护的逻辑实现非常简单(尤其是当为单个值重用保护时):

function isEachItemDefined<T extends Array<unknown>>(value: T): ?? {
    return value.every(isDefined);
}

我的用例是在使用 withLatestFrom 运算符 (RxJS) 后进行过滤。我有一个极端情况,我想在任何未定义值时过滤掉“事件”。

observable1.pipe(
    withLatestFrom(observable2, observable3, observable4), // <-- each returns value or undefined
    filter(isEachItemDefined),
    map(([ item1, item2, item3, item4 ]) => {
      // event if the logic works correctly,
      // typescript wouldn't recognise the items as defined
      // e.g. item2 is still "string | undefined"
    }),
);
function isDefined<T> (value: NonNullable<T> | undefined | null): value is NonNullable<T> {
    return value !== null && value !== undefined
}

function isEachItemDefined<T> (value: Array<NonNullable<T> | undefined | null>): value is Array<NonNullable<T>> {
    return value.every(isDefined)
}

这里有一些代码可以证明它有效:

function isEasyGoing<T>(arr: (T | undefined | null)[]) {
    // do stuff
}

function isHighMaintenance<T>(arr: NonNullable<T>[]) {
    // do stuff carefully
}

function handle<T>(arr: (NonNullable<T> | undefined | null)[]) {
    if (isEachItemDefined(arr)) {
        isHighMaintenance(arr)  // narrowed to string[], no type check error
    } else {
        isEasyGoing(arr) // still (string | undefined | null)[]
    }
}

const withoutNull: string[] = ['a', 'b', 'c']
const withNull: (string | undefined | null)[] = ['a', 'b', null, 'c']

isHighMaintenance(withoutNull)
isHighMaintenance(withNull)    // type check error as expected

handle(withoutNull)
handle(withNull)

type NullableString = string | null
const nullableWithoutNull: (NullableString | undefined | null)[] = ['a', 'b', 'c']
const nullableWithNull: (NullableString | undefined | null)[] = ['a', 'b', null, 'c']

// Since the generic parameter is constrained as NonNullable<T>
// we can't sneak NullableString by as T
isHighMaintenance(nullableWithoutNull) // type check error as expected
isHighMaintenance(nullableWithNull)    // type check error as expected

handle<NullableString>(nullableWithoutNull)
handle<NullableString>(nullableWithNull)