Typcript ile arayüz tipi kontrolü


294

Bu soru TypeScript ile Sınıf türü denetimine doğrudan analogon

Herhangi bir tür değişken herhangi bir arabirim uygularsa zamanında öğrenmek gerekir. İşte benim kod:

interface A{
    member:string;
}

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

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

Bu kodu daktilo oyun alanına girerseniz, son satır "Geçerli kapsamda A adı yok" hatası olarak işaretlenir. Ama bu doğru değil, isim mevcut kapsamda mevcut. Değişken bildirimini var a:A={member:"foobar"};editörden şikayet etmeden bile değiştirebilirim . Web'e göz attıktan ve SO'daki diğer soruyu bulduktan sonra arayüzü bir sınıfa değiştirdim, ancak örnek oluşturmak için nesne değişmezlerini kullanamıyorum.

Nasıl A tipi böyle yok olabilir merak ama oluşturulan javascript bir bakışta sorunu açıklar:

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

A'nın arayüz olarak gösterimi yoktur, bu nedenle çalışma zamanı tipi kontrolleri mümkün değildir.

Dinamik bir dil olarak javascript'in arayüz kavramı olmadığını anlıyorum. Arayüzler için kontrol yazmanın bir yolu var mı?

Dizgi oyun alanının otomatik tamamlanması, dizginin bir yöntem bile sunduğunu ortaya koyuyor implements. Nasıl Kullanabilirim?


4
JavaScript'in arayüz kavramı yoktur, ancak bunun nedeni dinamik bir dil olmasıdır. Çünkü arayüzler henüz uygulanmamıştır.
trusktr

Evet, ancak arabirim yerine sınıf kullanabilirsiniz. Bu örneğe bakın .
Alexey Baranoshnikov

Görünüşe göre 2017'de değil. Süper alakalı soru şimdi.
doublejosh

Yanıtlar:


221

instanceofAnahtar kelime olmadan istediğinizi elde edebilirsiniz, çünkü şimdi özel tip korumalar yazabilirsiniz:

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);
}

Çok Üye

Bir nesnenin türünüzle eşleşip eşleşmediğini belirlemek için çok sayıda üyeyi kontrol etmeniz gerekiyorsa, bunun yerine bir ayırıcı ekleyebilirsiniz. Aşağıdakiler en temel örnektir ve kendi ayırıcılarınızı yönetmenizi gerektirir ... yinelenen ayırıcılardan kaçınmak için kalıpların derinliklerine inmeniz gerekir.

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);
}

85
Msgstr "Bir arayüzü çalışma zamanında kontrol etmenin bir yolu yok." Var, herhangi bir nedenle henüz uygulamadılar.
trusktr

16
Ve arayüzün 100 üyesi varsa, 100'ün tümünü kontrol etmeniz mi gerekiyor? Falanca.
Jenny O'Reilly

4
Tüm 100'ü kontrol etmek yerine nesnenize bir ayırıcı ekleyebilirsiniz ...
Fenton

7
bu ayrımcı paradigma (burada yazıldığı gibi) genişleyen arayüzleri desteklemez. Türetilen bir arabirim, temel arabirimin bir örneği olup olmadığını denetlerse false değerini döndürür.
Aaron

1
@Fenton Belki de bu konuda yeterince bilgim yok, ama varsayalım ki A arayüzünü uzatan bir B arayüzünüz var, isInstanceOfA(instantiatedB)doğru dönmek istiyorsunuz , ama isInstanceOfB(instantiatedA)yanlış döndürmek istiyorsunuz . İkincisinin meydana gelmesi için, B'nin ayrımcısının 'I-AM-A' olması gerekmez mi?
Aaron

87

TypeScript 1.6'da, kullanıcı tanımlı tür koruma işi yapar.

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;
}

Joe Yang'ın bahsettiği gibi: TypeScript 2.0'dan beri, etiketli birleşim türünün avantajından bile yararlanabilirsiniz.

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;
}

Ve de çalışıyor switch.


1
Bu oldukça meraklı görünüyor. Görünüşe göre bir çeşit meta bilgi mevcut. Neden bu tip koruma sözdizimi ile ortaya çıkar. İsinstanceof yerine, bir fonksiyonun yanındaki "nesne arayüzdür" hangi kısıtlamalardan dolayı çalışır? Daha doğrusu, doğrudan if ifadelerinde "object is interface" ifadesini kullanabilir misiniz? Ama her durumda, çok ilginç bir sözdizimi, benden +1.
lhk

1
@lhk Hayır böyle bir ifade yok, daha çok bir türün koşullu dallar içinde nasıl daraltılması gerektiğini söyleyen özel bir tür gibi. TypeScript'in "kapsamı" nedeniyle, gelecekte bile böyle bir ifade olmayacağına inanıyorum. Bir başka farklı object is typeve object instanceof classTypeScript'teki tür yapısaldır, bir nesnenin şekli nereden aldığı yerine sadece "şekli" umursar: düz bir nesne veya bir sınıf örneği, önemli değil.
vilicvane

2
Bu cevabın yaratabileceği bir yanlış anlayışı temizlemek için: çalışma sırasında nesne türünü veya arayüzünü düşecek meta bilgi yoktur.
mostruash

@mostruash Evet, cevabın ikinci yarısı derlenmesine rağmen çalışma zamanında çalışmaz.
trusktr

4
Oh, ama, bu çalışma zamanında bu nesnelerin bir typeözellik ile oluşturulacağını varsaymalıdır . Bu durumda işe yarıyor. Bu örnek bu gerçeği göstermiyor.
trusktr

40

daktilo 2.0 etiketli birliği tanıtmak

Daktilo 2.0 özellikleri

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;
    }
}

2.0 beta kullanıyorum ama etiketli sendika çalışmıyor. <TypeScriptToolsVersion> 2.0 </TypeScriptToolsVersion>
Makla

Her gece inşa ile derlenir, ancak akıllı çalışmaz. Ayrıca hataları da listeler: 'Square | Dikdörtgen | Durum ifadesinde daire. Ama derler.
Makla

23
Bu gerçekten sadece bir ayrımcı kullanıyor.
Erik Philips

33

Kullanıcı Tanımlı Tip Muhafızlara ne dersiniz? 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();
}

3
Bu benim en sevdiğim cevaptır - stackoverflow.com/a/33733258/469777'ye benzer, ancak minyatür gibi şeyler nedeniyle kırılabilecek sihirli dizeler olmadan.
Stafford Williams

1
Bu benim için bir nedenden dolayı işe yaramadı ama işe (pet as Fish).swim !== undefined;yaradı.
CyberMew

18

Artık mümkün, TypeScripttam yansıma özellikleri sağlayan derleyicinin gelişmiş bir sürümünü çıkardım . Sınıfları meta veri nesnelerinden başlatabilir, sınıf yapıcılarından meta verileri alabilir ve çalışma zamanında arabirimi / sınıfları inceleyebilirsiniz. Buradan kontrol edebilirsiniz

Kullanım örneği:

Daktilo dosyalarınızdan birinde, aşağıdaki gibi uygulayan bir arabirim ve bir sınıf oluşturun:

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

class MyClass implements MyInterface {
    counter = 0;

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

Şimdi bazı uygulanmış arayüzlerin listesini yazdıralım.

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

reflec-ts ile derleyin ve başlatın:

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

InterfaceMeta türü ayrıntıları için yansıma.d.ts dosyasına bakın.

GÜNCELLEME: Burada tam bir çalışma örneği bulabilirsiniz


8
çünkü bu aptal olduğunu düşündüm, ama sonra bir saniyeliğine durakladı, github sayfanıza baktı ve güncel tutulduğunu gördüm ve bunun yerine çok iyi belgelendiğini gördüm :-) Hala şu anda sadece kendim için kullanamıyorum implementsama taahhüdünüzü tanımak istedim ve
kaba

5
Aslında, bu yansıma özelliklerini gördüğüm temel amaç, Java dünyasının uzun zamandır sahip olduğu gibi daha iyi IoC çerçeveleri oluşturmaktır (Bahar ilk ve en önemlisidir). TypeScript'in geleceğin en iyi geliştirme araçlarından biri olabileceğine ve yansıtmanın gerçekten ihtiyaç duyduğu özelliklerden biri olduğuna kesinlikle inanıyorum.
pcan

5
... uh, yani ne, bu derleyici "geliştirmeleri" dizgisi gelecekteki herhangi bir yapıya yuvarlamak zorunda? Bu etkili bir şekilde tam bir yazıtipi çatalı, yazıtipi değil, değil mi? Eğer öyleyse, bu uygulanabilir uzun vadeli bir çözüm değildir.
dudewad

1
@dudewad diğer birçok konuda söylendiği gibi, bu geçici bir çözümdür. Transformatörler aracılığıyla derleyici genişletilebilirliğini bekliyoruz. Lütfen resmi TypeScript deposundaki ilgili sorunlara bakın. Dahası, yaygın olarak benimsenen güçlü yazılan dillerin yansıması vardır ve bence TypeScript de buna sahip olmalıdır. Ve benim gibi, diğer birçok kullanıcı böyle düşünüyor.
pcan

evet kabul etmiyorum değil - bunu da istiyorum. Sadece, özel bir derleyiciyi döndürmek ... Bu, Typkscript'in bir sonraki yamasının taşınması gerektiği anlamına gelmiyor mu? Eğer bakıyorsanız o zaman kudos. Çok fazla iş gibi görünüyor. Vurmayın.
dudewad


8

İşte başka bir seçenek: ts-interface-builder modülü , bir TypeScript arabirimini bir çalışma zamanı tanımlayıcısına dönüştüren bir oluşturma zamanı aracı sağlar ve ts-interface-checker bir nesnenin onu tatmin edip etmediğini kontrol edebilir.

OP örneği için,

interface A {
  member: string;
}

İlk olarak ts-interface-builder, bir tanımlayıcıyla yeni bir özlü dosya üreten foo-ti.ts, örneğin şu şekilde kullanabileceğiniz bir çalışma yürüttünüz:

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" 

Tek astarlı tip koruma işlevi oluşturabilirsiniz:

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

6

TypeScript'in bir nesnenin belirli bir arabirimi uygulayıp uygulamadığını dinamik olarak sınamak için doğrudan bir mekanizma sağlamadığını belirtmek isterim.

Bunun yerine, TypeScript kodu, nesnede uygun bir üye kümesinin bulunup bulunmadığını denetlemek için JavaScript tekniğini kullanabilir. Örneğin:

var obj : any = new Foo();

if (obj.someInterfaceMethod) {
    ...
}

4
ya karmaşık bir şeklin varsa? her bir derinlikte her bir mülkü sabit kodlamak istemezsiniz
Tom

@ Sanırım (denetleyici işlevine ikinci bir parametre olarak) bir çalışma zamanı değeri veya örnek / örnek - yani istediğiniz arabirimin bir nesnesini iletebilirsiniz. Ardından, sabit kodlama kodu yerine, istediğiniz arabirimin herhangi bir örneğini yazarsınız for (element in obj) {}ve iki nesnenin benzer türlere benzer öğelere sahip olduğunu doğrulamak için bir kerelik bir nesne karşılaştırma kodu yazarsınız (örneğin ).
ChrisW

5

TypeGuards

interface MyInterfaced {
    x: number
}

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

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

2
"arg is MyInterfaced" ilginç bir ek açıklamadır. Bu başarısız olursa ne olur? Derleme zaman arayüzü kontrolü gibi görünüyor - ki ilk etapta istediğim gibi olurdu. Ancak derleyici parametreleri kontrol ederse, neden bir işlev gövdesine sahip olursunuz. Ve eğer böyle bir kontrol mümkünse, neden ayrı bir işleve taşıyabilirsiniz.
lhk

1
@lhk sadece tip koruyucular hakkında daktilo belgelerini okuyun ... typescriptlang.org/docs/handbook/advanced-types.html
Dmitry Matveev

3

Fenton'un cevabına dayanarak, verilen objectbir anahtarın interfacetamamen veya kısmen anahtarlarına sahip olup olmadığını doğrulamak için bir işlev uygulaması .

Kullanım durumunuza bağlı olarak, arabirim özelliklerinin her birinin türlerini de kontrol etmeniz gerekebilir. Aşağıdaki kod bunu yapmaz.

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;
}

Kullanım örneği:

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

2
export interface ConfSteps {
    group: string;
    key: string;
    steps: string[];
}
private verify(): void {
    const obj = `{
      "group": "group",
      "key": "key",
      "steps": [],
      "stepsPlus": []
    } `;
    if (this.implementsObject<ConfSteps>(obj, ['group', 'key', 'steps'])) {
      console.log(`Implements ConfSteps: ${obj}`);
    }
  }
private objProperties: Array<string> = [];

private implementsObject<T>(obj: any, keys: (keyof T)[]): boolean {
    JSON.parse(JSON.stringify(obj), (key, value) => {
      this.objProperties.push(key);
    });
    for (const key of keys) {
      if (!this.objProperties.includes(key.toString())) {
        return false;
      }
    }
    this.objProperties = null;
    return true;
  }

1
Bu kod soruyu cevaplayabilirken, bu kodun soruyu neden ve / veya nasıl cevapladığı konusunda ek bağlam sağlamak uzun vadeli değerini arttırır.
xiawi

0

Tür çalışma zamanında bilinmediğinden, bilinmeyen nesneyi bir türe karşı değil, bilinen türde bir nesneye karşı karşılaştırmak için aşağıdaki gibi kod yazdım:

  1. Doğru türde bir örnek nesne oluşturma
  2. Hangi öğelerinin isteğe bağlı olduğunu belirtme
  3. Bilinmeyen nesnenizi bu örnek nesneyle derinlemesine karşılaştırın

İşte derin karşılaştırma için kullandığım (arayüz agnostik) kodu:

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;
}

Aşağıda onu nasıl kullandığımın bir örneği var.

Bu örnekte JSON'un, ikinci öğesinin User(iki isteğe bağlı öğeye sahip) adlı bir arabirimin bir örneği olduğu bir dizi tuples içermesini bekliyorum .

TypeScript'in tür denetimi, örnek nesnemin doğru olduğundan emin olur, ardından assertTypeT işlevi, bilinmeyen (JSON'dan yüklenen) nesnenin örnek nesneyle eşleşip eşleşmediğini denetler.

export function loadUsers(): Map<number, User> {
  const found = require("./users.json");
  const sample: [number, User] = [
    49942,
    {
      "name": "ChrisW",
      "email": "example@example.com",
      "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);
}

Kullanıcı tanımlı bir tür koruyucunun uygulanmasında böyle bir denetimi çağırabilirsiniz.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.