typescript - Är det möjligt att designa en typvakt för att en array ska vara tom?
Det finns många exempel på hur man designar en typvakt för en array som INTE är tom. Till exempel fungerar denna metod utmärkt när man använder 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);
}
då :
let foo = [1, 2, 3];
if (isNotEmpty(foo))
foo[0].toString() // does not error
else
foo[0].toString() // does error
För att däremot kontrollera motsatsen till detta måste man invertera boolean-kontrollen:
let foo = [1, 2, 3];
if (!isNotEmpty(foo))
foo[0].toString(); // now errors
else
foo[0].toString(); // now does not error
Problemet är att jag tycker if (!isNotEmpty(foo)) är lite fult att läsa eftersom det är en dubbel negation.
Så frågan är, hur definierar man en isEmpty-typvakt så att man kan göra if (isEmpty(foo)) och ändå få samma resultat som kodsnutten som visas ovan? Det verkar vara ett trivialt problem men alla mina försök hittills har motarbetats.
Jag tror att huvudproblemet är att du inte kan hävda inversen av ett typvakt, du kan inte säga att något INTE är något annat.
REDIGERA: Jag har blivit ombedd att ge fler exempel.
Så här är ett exempel på vad jag skulle vilja göra:
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())
}
Det kan uppnås genom att göra följande
function logFirstDataElement(data: number[]) {
// Dont do anything if no data
if (!isNotEmpty(data)) return;
console.log(data[0].toString())
}
Men som nämnts ovan skulle jag helst undvika "dubbelt negativt" av !isNotEmpty(data)
Svar #1
Du kan använda ett predikat som sätter arrayen till den tomma tuplen []:
TS Lekplats
Lekplatsen (och koden nedan) använder kompilatoralternativet
noUncheckedIndexedAccess(precis som i din fråga), så även ifalse-grenen måste du validera varje indexerat element för att få ett icke-nullbart värde.
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
}
Uppdatering som svar på den uppdaterade frågan: Denna typvakt har ett positivt namn, så att använda logisk INTE (!) för att invertera den kommer inte att resultera i en dubbelt negativ:
TS Lekplats
function hasAtLeastOneElement <T>(array: readonly T[]): array is [T, ...T[]] {
return array.length > 0;
}
function logFirstElement (array: number[]) {
if (!hasAtLeastOneElement(array)) {
const [first] = array;
//^? const first: number | undefined
return;
}
const [first] = array;
//^? const first: number
console.log(first.toString());
}
