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?
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);
}
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);
}
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
.
TypeScript 2.0 introduit une union étiquetée
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;
}
}
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();
}
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
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); }
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);
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) {
...
}
TypeGuards
interface MyInterfaced {
x: number
}
function isMyInterfaced(arg: any): arg is MyInterfaced {
return arg.x !== undefined;
}
if (isMyInterfaced(obj)) {
(obj as MyInterfaced ).x;
}
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:
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.
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