Typescript indique que array.pop() peut renvoyer undefined même lorsqu'il est garanti que le tableau contient des éléments

Pourquoi Typescript m'avertit-il que cela pop()pourrait revenir undefinedmême après avoir vérifié que je supprime un élément d'un tableau non vide?

type Person = {
name: string;
};
const people: Person[] = [{ name: 'Sandra' }];
if (people.length) {
const { name } = people.pop();
// TS Error: Property 'name' does not exist on type 'Person | undefined'.
}

Même si je rends la garantie plus explicite, j'obtiens la même erreur:

if (people.length > 0 && people[people.length - 1]!== undefined){
const { name } = people.pop();
// TS Error: Property 'name' does not exist on type 'Person | undefined'.
}


Solution du problème

Voir microsoft/TypeScript#30406 pour une réponse canonique.

Pour la question "pourquoi le compilateur pense-t-il que cela pop()pourrait revenir undefinedmême lorsque je vérifie le tableau ", la réponse courte est "parce que la signature d'appellength de la bibliothèque standard TypeScript pour la méthode sur les retours ":pop()Array<T>T | undefined

interface Array<T> {
//... elided
pop(): T | undefined;
//... elided
}

Ainsi, chaque fois que vous appelez pop()un tableau, le type de la valeur de retour inclura undefined.

La prochaine question logique est "pourquoi ne font-ils pas de meilleures signatures d'appel qui retournent Tsi le lengthest différent de zéro et undefinedsi le lengthest zéro?" La réponse à cela est "parce que la vérification de la lengthpropriété d'un type de tableau général ne change pas le type apparent du tableau, donc les signatures d'appel ne peuvent pas faire la différence".

Vous pouvez, par exemple, ajouter quelques signatures d'appel comme ceci :

interface Array<T> {
pop(this: { length: 0 }): undefined;
pop(this: { 0: T }): T;
}

By using this parameters, each call signature will only be selected when the array on which the method is called matches the specified type. If the array has a length of 0, return undefined. If the array has an element of type T at the key 0, return T.

Cela fonctionnera bien pour les types de tuple dont la longueur est fixe et dont les indices d'élément sont connus :

declare const u: [];
const a = u.pop(); // undefined
declare const v: [1, 2];
const b = v.pop(); // 1 | 2

Mais bien sûr, appeler pop()un tuple est une mauvaise idée et ce n'est pas le genre de chose que vous voudriez probablement. Si vous avez un tableau de longueur variable et que vous l'appelez pop(), aucune signature d'appel n'est sélectionnée et vous revenez à la valeur intégrée T | undefined, même si vous essayez de vérifier le length:

const w = Math.random() < 0.5? []: ["a"] // string[]
if (w.length) {
w.pop().toUpperCase(); // error! Object is possibly undefined
}

Le problème est que la lengthpropriété de an Array<T>est de type numberet qu'il n'y a pas de type "non nul number" auquel se limiter w.length. Afin de prendre en charge ce genre de chose, vous auriez besoin de quelque chose comme des types niés which are not part of TypeScript. It's possible that with sufficient compiler work, someone could give enough structure to arrays in TypeScript that a truthiness check on w.length would narrow down the type of the array to something you can call pop() on without worrying about getting undefined out.

Mais cela ajouterait une tonne de complexité au compilateur et au langage pour prendre en charge ce cas d'utilisation. Il est peu probable que l'avantage l'emporte sur le coût.

In the absence of this, it's so much easier for you to skip the length check and just call pop(), checking to see whether it's undefined or not. This is the same amount of work for you, since it's just moving the check from before to after the call, and it makes things more straightforward for the compiler.

Les autres réponses publiées ici suggèrent ceci et d'autres solutions de contournement, donc je ne les aborderai pas. Mon point principal est que le langage n'est tout simplement pas équipé pour permettre à une lengthvérification d'affecter le comportement de pop(). Ou, comme @RyanCavanaugh (chef de développement de TS) commenté dans microsoft/TypeScript#30406,

Ce n'est pas quelque chose que nous sommes capables de suivre

Tant pis!

Lien de l'aire de jeux vers le code

Commentaires

Posts les plus consultés de ce blog

Erreur Symfony : "Une exception a été levée lors du rendu d'un modèle"

Détecter les appuis sur les touches fléchées en JavaScript

Une chaîne vide donne "Des erreurs ont été détectées dans les arguments de la ligne de commande, veuillez vous assurer que tous les arguments sont correctement définis"