web-dev-qa-db-fra.com

Vérification du type d'interface avec Typescript

Cette question est l’analogon direct à Vérification du type de classe avec TypeScript

J'ai besoin de savoir au moment de l'exécution si une variable de type implémente une interface. Voici mon code:

interface A{
    member:string;
}

var a:any={member:"foobar"};

if(a instanceof A) alert(a.member);

Si vous entrez ce code dans le terrain de jeu TypeScript, la dernière ligne sera marquée comme une erreur "Le nom A n’existe pas dans la portée actuelle". Mais ce n'est pas vrai, le nom existe dans la portée actuelle. Je peux même changer la déclaration de variable en var a:A={member:"foobar"}; sans se plaindre de l'éditeur. Après avoir parcouru le Web et trouvé l’autre question sur SO, j’ai modifié l’interface en classe mais je ne peux pas utiliser de littéraux d’objet pour créer des instances.

Je me demandais comment le type A pouvait disparaître comme ça, mais un coup d'œil au javascript généré explique le problème:

var a = {
    member: "foobar"
};
if(a instanceof A) {
    alert(a.member);
}

Il n'y a pas de représentation de A en tant qu'interface, donc aucune vérification de type à l'exécution n'est possible.

Je comprends que javascript, en tant que langage dynamique, n’a pas de concept d’interfaces. Est-il possible de taper check pour les interfaces?

La complétion automatique du terrain de jeu TypeScript révèle que TypeScript propose même une méthode implements. Comment puis-je l'utiliser?

210
lhk

Vous pouvez obtenir ce que vous voulez sans le mot clé instanceof, car vous pouvez désormais écrire des gardes de type personnalisé:

interface A{
    member:string;
}

function instanceOfA(object: any): object is A {
    return 'member' in object;
}

var a:any={member:"foobar"};

if (instanceOfA(a)) {
    alert(a.member);
}

Beaucoup de membres

Si vous devez vérifier de nombreux membres pour déterminer si un objet correspond à votre type, vous pouvez plutôt ajouter un discriminateur. L'exemple ci-dessous est l'exemple le plus élémentaire, et vous oblige à gérer vos propres discriminateurs ... vous devez approfondir les schémas pour éviter les discriminateurs en double.

interface A{
    discriminator: 'I-AM-A';
    member:string;
}

function instanceOfA(object: any): object is A {
    return object.discriminator === 'I-AM-A';
}

var a:any = {discriminator: 'I-AM-A', member:"foobar"};

if (instanceOfA(a)) {
    alert(a.member);
}
147
Fenton

Dans TypeScript 1.6, type défini par l'utilisateur fera le travail.

interface Foo {
    fooProperty: string;
}

interface Bar {
    barProperty: string;
}

function isFoo(object: any): object is Foo {
    return 'fooProperty' in object;
}

let object: Foo | Bar;

if (isFoo(object)) {
    // `object` has type `Foo`.
    object.fooProperty;
} else {
    // `object` has type `Bar`.
    object.barProperty;
}

Et comme Joe Yang l'a mentionné: depuis TypeScript 2.0, vous pouvez même tirer parti du type d'union étiqueté.

interface Foo {
    type: 'foo';
    fooProperty: string;
}

interface Bar {
    type: 'bar';
    barProperty: number;
}

let object: Foo | Bar;

// You will see errors if `strictNullChecks` is enabled.
if (object.type === 'foo') {
    // object has type `Foo`.
    object.fooProperty;
} else {
    // object has type `Bar`.
    object.barProperty;
}

Et cela fonctionne aussi avec switch.

67
vilicvane

TypeScript 2.0 introduit une union étiquetée

fonctionnalités TypeScript 2.

interface Square {
    kind: "square";
    size: number;
}

interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}

interface Circle {
    kind: "circle";
    radius: number;
}

type Shape = Square | Rectangle | Circle;

function area(s: Shape) {
    // In the following switch statement, the type of s is narrowed in each case clause
    // according to the value of the discriminant property, thus allowing the other properties
    // of that variant to be accessed without a type assertion.
    switch (s.kind) {
        case "square": return s.size * s.size;
        case "rectangle": return s.width * s.height;
        case "circle": return Math.PI * s.radius * s.radius;
    }
}
34
Joe Yang

Qu'en est-il des gardes de type défini par l'utilisateur? https://www.typescriptlang.org/docs/handbook/advanced-types.html

interface Bird {
    fly();
    layEggs();
}

interface Fish {
    swim();
    layEggs();
}

function isFish(pet: Fish | Bird): pet is Fish { //magic happens here
    return (<Fish>pet).swim !== undefined;
}

// Both calls to 'swim' and 'fly' are now okay.

if (isFish(pet)) {
    pet.swim();
}
else {
    pet.fly();
}
25

C'est maintenant possible, je viens de publier une version améliorée du compilateur TypeScript qui fournit des capacités de réflexion complètes. Vous pouvez instancier des classes à partir de leurs objets de métadonnées, récupérer des métadonnées de constructeurs de classe et inspecter une interface/des classes au moment de l'exécution. Vous pouvez le vérifier ici

Exemple d'utilisation:

Dans l'un de vos fichiers TypeScript, créez une interface et une classe qui l'implémente, comme suit:

interface MyInterface {
    doSomething(what: string): number;
}

class MyClass implements MyInterface {
    counter = 0;

    doSomething(what: string): number {
        console.log('Doing ' + what);
        return this.counter++;
    }
}

imprimons maintenant la liste des interfaces implémentées.

for (let classInterface of MyClass.getClass().implements) {
    console.log('Implemented interface: ' + classInterface.name)
}

compiler avec reflec-ts et le lancer:

$ node main.js
Implemented interface: MyInterface
Member name: counter - member kind: number
Member name: doSomething - member kind: function

Voir reflection.d.ts pour les détails de Interface méta-type.

UPDATE: Vous pouvez trouver un exemple de travail complet ici

14
pcan

Voici une autre option: le module ts-interface-builder fournit un outil de construction qui convertit une interface TypeScript en descripteur d'exécution, et ts-interface-checker peut vérifier si un objet le satisfait.

Pour l'exemple d'OP,

interface A {
  member: string;
}

Vous devez d’abord exécuter ts-interface-builder, qui produit un nouveau fichier concis avec un descripteur, par exemple, foo-ti.ts, que vous pouvez utiliser comme ceci:

import fooDesc from './foo-ti.ts';
import {createCheckers} from "ts-interface-checker";
const {A} = createCheckers(fooDesc);

A.check({member: "hello"});           // OK
A.check({member: 17});                // Fails with ".member is not a string" 

Vous pouvez créer une fonction de protection de type à une ligne:

function isA(value: any): value is A { return A.test(value); }
7
DS.

comme ci-dessus où gardes définis par l'utilisateur ont été utilisés, mais cette fois avec un prédicat de fonction de flèche

interface A {
  member:string;
}

const check = (p: any): p is A => p.hasOwnProperty('member');

var foo: any = { member: "foobar" };
if (check(foo))
    alert(foo.member);
5
Dan Dohotaru

Je voudrais souligner que TypeScript ne fournit pas de mécanisme direct pour tester de manière dynamique si un objet implémente une interface particulière.

Au lieu de cela, le code TypeScript peut utiliser la technique JavaScript permettant de vérifier si un ensemble approprié de membres est présent sur l'objet. Par exemple:

var obj : any = new Foo();

if (obj.someInterfaceMethod) {
    ...
}
5
Daniel Ribeiro

TypeGuards

interface MyInterfaced {
    x: number
}

function isMyInterfaced(arg: any): arg is MyInterfaced {
    return arg.x !== undefined;
}

if (isMyInterfaced(obj)) {
    (obj as MyInterfaced ).x;
}
3
Dmitry Matveev

Le type étant inconnu au moment de l'exécution, j'ai écrit le code suivant pour comparer l'objet inconnu, non pas à un type, mais à un objet de type connu:

  1. Créer un exemple d'objet du bon type
  2. Spécifiez lesquels de ses éléments sont facultatifs
  3. Faites une comparaison approfondie de votre objet inconnu avec cet exemple

Voici le code (interface-agnostique) que j'utilise pour la comparaison en profondeur:

function assertTypeT<T>(loaded: any, wanted: T, optional?: Set<string>): T {
  // this is called recursively to compare each element
  function assertType(found: any, wanted: any, keyNames?: string): void {
    if (typeof wanted !== typeof found) {
      throw new Error(`assertType expected ${typeof wanted} but found ${typeof found}`);
    }
    switch (typeof wanted) {
      case "boolean":
      case "number":
      case "string":
        return; // primitive value type -- done checking
      case "object":
        break; // more to check
      case "undefined":
      case "symbol":
      case "function":
      default:
        throw new Error(`assertType does not support ${typeof wanted}`);
    }
    if (Array.isArray(wanted)) {
      if (!Array.isArray(found)) {
        throw new Error(`assertType expected an array but found ${found}`);
      }
      if (wanted.length === 1) {
        // assume we want a homogenous array with all elements the same type
        for (const element of found) {
          assertType(element, wanted[0]);
        }
      } else {
        // assume we want a Tuple
        if (found.length !== wanted.length) {
          throw new Error(
            `assertType expected Tuple length ${wanted.length} found ${found.length}`);
        }
        for (let i = 0; i < wanted.length; ++i) {
          assertType(found[i], wanted[i]);
        }
      }
      return;
    }
    for (const key in wanted) {
      const expectedKey = keyNames ? keyNames + "." + key : key;
      if (typeof found[key] === 'undefined') {
        if (!optional || !optional.has(expectedKey)) {
          throw new Error(`assertType expected key ${expectedKey}`);
        }
      } else {
        assertType(found[key], wanted[key], expectedKey);
      }
    }
  }

  assertType(loaded, wanted);
  return loaded as T;
}

Vous trouverez ci-dessous un exemple d'utilisation.

Dans cet exemple, le JSON devrait contenir un tableau de nuplets, dont le deuxième élément est une instance d'une interface appelée User (qui comporte deux éléments facultatifs).

La vérification de type de TypeScript garantira que mon exemple d'objet est correct, puis la fonction assertTypeT vérifie que l'objet inconnu (chargé à partir de JSON) correspond à l'objet exemple.

export function loadUsers(): Map<number, User> {
  const found = require("./users.json");
  const sample: [number, User] = [
    49942,
    {
      "name": "ChrisW",
      "email": "[email protected]",
      "gravatarHash": "75bfdecf63c3495489123fe9c0b833e1",
      "profile": {
        "location": "Normandy",
        "aboutMe": "I wrote this!\n\nFurther details are to be supplied ..."
      },
      "favourites": []
    }
  ];
  const optional: Set<string> = new Set<string>(["profile.aboutMe", "profile.location"]);
  const loaded: [number, User][] = assertTypeT(found, [sample], optional);
  return new Map<number, User>(loaded);
}

Vous pouvez invoquer une vérification comme celle-ci dans l'implémentation d'un type de garde défini par l'utilisateur.

0
ChrisW

Sur la base de réponse de Fenton, voici ma mise en œuvre d'une fonction permettant de vérifier si un object donné a les clés, un interface, entièrement ou partiellement.

Selon votre cas d'utilisation, vous devrez peut-être également vérifier les types de chacune des propriétés de l'interface. Le code ci-dessous ne fait pas cela.

function implementsTKeys<T>(obj: any, keys: (keyof T)[]): obj is T {
    if (!obj || !Array.isArray(keys)) {
        return false;
    }

    const implementKeys = keys.reduce((impl, key) => impl && key in obj, true);

    return implementKeys;
}

Exemple d'utilisation:

interface A {
    propOfA: string;
    methodOfA: Function;
}

let objectA: any = { propOfA: '' };

// Check if objectA partially implements A
let implementsA = implementsTKeys<A>(objectA, ['propOfA']);

console.log(implementsA); // true

objectA.methodOfA = () => true;

// Check if objectA fully implements A
implementsA = implementsTKeys<A>(objectA, ['propOfA', 'methodOfA']);

console.log(implementsA); // true

objectA = {};

// Check again if objectA fully implements A
implementsA = implementsTKeys<A>(objectA, ['propOfA', 'methodOfA']);

console.log(implementsA); // false, as objectA now is an empty object
0
aledpardo