Kiểm tra loại giao diện với Bản đánh máy


292

Câu hỏi này là kiểm tra loại tương tự trực tiếp đến Loại với TypeScript

Tôi cần tìm ra trong thời gian chạy nếu một biến kiểu bất kỳ thực hiện một giao diện. Đây là mã của tôi:

interface A{
    member:string;
}

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

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

Nếu bạn nhập mã này vào sân chơi bản thảo, dòng cuối cùng sẽ được đánh dấu là lỗi, "Tên A không tồn tại trong phạm vi hiện tại". Nhưng điều đó không đúng, tên tồn tại trong phạm vi hiện tại. Tôi thậm chí có thể thay đổi khai báo biến thành var a:A={member:"foobar"};mà không có khiếu nại từ biên tập viên. Sau khi duyệt web và tìm câu hỏi khác trên SO, tôi đã thay đổi giao diện thành một lớp nhưng sau đó tôi không thể sử dụng các đối tượng bằng chữ để tạo các thể hiện.

Tôi đã tự hỏi làm thế nào loại A có thể biến mất như vậy nhưng nhìn vào javascript được tạo ra giải thích vấn đề:

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

Không có đại diện của A như một giao diện, do đó không thể kiểm tra loại thời gian chạy.

Tôi hiểu rằng javascript như một ngôn ngữ động không có khái niệm về giao diện. Có cách nào để gõ kiểm tra cho các giao diện?

Tự động hoàn thành sân chơi bản thảo cho thấy bản thảo thậm chí cung cấp một phương thức implements. Làm thế nào tôi có thể sử dụng nó?


4
JavaScript không có khái niệm về giao diện, nhưng điều đó không phải vì nó là ngôn ngữ động. Đó là vì giao diện chưa được triển khai.
trusktr

Có, nhưng bạn có thể sử dụng lớp thay vì giao diện. Xem ví dụ này .
Alexey Baranoshnikov 17/12/18

Rõ ràng không phải trong năm 2017. Câu hỏi siêu liên quan bây giờ.
doublejosh

Câu trả lời:


220

Bạn có thể đạt được những gì bạn muốn mà không cần instanceoftừ khóa vì bạn có thể viết các loại bảo vệ tùy chỉnh ngay bây giờ:

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

Rất nhiều thành viên

Nếu bạn cần kiểm tra nhiều thành viên để xác định xem một đối tượng có phù hợp với loại của bạn hay không, thay vào đó bạn có thể thêm một bộ phân biệt đối xử. Dưới đây là ví dụ cơ bản nhất và yêu cầu bạn quản lý những người phân biệt đối xử của riêng bạn ... bạn cần tìm hiểu sâu hơn về các mẫu để đảm bảo bạn tránh được những người phân biệt đối xử trùng lặp.

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
"Không có cách nào để chạy kiểm tra một giao diện." Có điều, họ chưa thực hiện nó vì bất kỳ lý do gì.
trusktr

16
Và nếu giao diện có 100 thành viên, bạn cần kiểm tra tất cả 100? Foobar.
Jenny O'Reilly

4
Bạn có thể thêm một kẻ phân biệt đối xử vào đối tượng của mình thay vì kiểm tra tất cả 100 ...
Fenton

7
mô hình phân biệt đối xử này (như được viết ở đây) không hỗ trợ mở rộng giao diện. Một giao diện dẫn xuất sẽ trả về false nếu kiểm tra xem đó có phải là một thể hiện không.
Aaron

1
@Fenton Có lẽ tôi không biết đủ về điều này, nhưng giả sử bạn có giao diện B mở rộng giao diện A, bạn muốn isInstanceOfA(instantiatedB)trả về true, nhưng bạn muốn isInstanceOfB(instantiatedA)trả về false. Để điều sau xảy ra, liệu người phân biệt đối xử của B không phải là 'I-AM-A'?
Aaron

86

Trong TypeScript 1.6, kiểu bảo vệ do người dùng định nghĩa sẽ thực hiện công việc.

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

Và như Joe Yang đã đề cập: kể từ TypeScript 2.0, bạn thậm chí có thể tận dụng lợi thế của loại kết hợp được gắn thẻ.

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

Và nó hoạt động với switchquá.


1
Điều này có vẻ khá tò mò. Rõ ràng có một số loại thông tin meta có sẵn. Tại sao phơi bày nó với cú pháp bảo vệ kiểu này. Do những ràng buộc nào mà "đối tượng là giao diện" bên cạnh một chức năng hoạt động, trái ngược với isinstanceof? Chính xác hơn, bạn có thể sử dụng "object is interface" trong câu lệnh if trực tiếp không? Nhưng trong mọi trường hợp, cú pháp rất thú vị, +1 từ tôi.
lhk

1
@lhk Không có một tuyên bố như vậy, nó giống như một loại đặc biệt cho biết làm thế nào một loại nên được thu hẹp trong các nhánh có điều kiện. Do "phạm vi" của TypeScript, tôi tin rằng sẽ không có tuyên bố như vậy ngay cả trong tương lai. Một điểm khác giữa object is typeobject instanceof class, đó là, trong TypeScript có cấu trúc, nó chỉ quan tâm đến "hình dạng" thay vì nơi mà một đối tượng có hình dạng từ: một đối tượng đơn giản hoặc một thể hiện của một lớp, điều đó không thành vấn đề.
vilicvane

2
Chỉ cần xóa một quan niệm sai lầm, câu trả lời này có thể tạo ra: không có thông tin meta để loại trừ loại đối tượng hoặc giao diện của nó trong thời gian chạy.
mostruash 30/03/2016

@ mostruash Yep, nửa sau của câu trả lời sẽ không hoạt động trong thời gian chạy mặc dù nó được biên dịch.
trusktr

4
Ồ, nhưng, điều này phải cho rằng trong thời gian chạy, các đối tượng này sẽ được tạo bằng một thuộc typetính. Trong trường hợp đó nó hoạt động. Ví dụ đó không cho thấy thực tế này.
trusktr

40

typcript 2.0 giới thiệu công đoàn được gắn thẻ

Tính năng bản đánh máy 2.0

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

Tôi đang sử dụng 2.0 beta nhưng công đoàn được gắn thẻ không hoạt động. <TypeScriptToolsVersion> 2.0 </ TypeScriptToolsVersion>
Makla

Được biên dịch với bản dựng hàng đêm, nhưng intellisense không hoạt động. Nó cũng liệt kê các lỗi: Chiều rộng / kích thước thuộc tính / ... không tồn tại trên Loại 'Square | Hình chữ nhật | Vòng tròn trong trường hợp tuyên bố. Nhưng nó biên dịch.
Makla

22
Đây thực sự chỉ là sử dụng một phân biệt đối xử.
Erik Philips

32

Làm thế nào về các loại bảo vệ do người dùng xác định? https://www.typescriptlang.org/docs/handbook/advified-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
Đây là câu trả lời yêu thích của tôi - tương tự như stackoverflow.com/a/33733258/469777 nhưng không có chuỗi ma thuật có thể bị phá vỡ do những thứ như thu nhỏ.
Stafford Williams

1
Điều này đã không làm việc cho tôi vì một số lý do nhưng (pet as Fish).swim !== undefined;đã làm.
CyberMew

18

Bây giờ có thể, tôi vừa phát hành một phiên bản nâng cao của TypeScripttrình biên dịch cung cấp khả năng phản chiếu đầy đủ. Bạn có thể khởi tạo các lớp từ các đối tượng siêu dữ liệu của chúng, truy xuất siêu dữ liệu từ các hàm tạo của lớp và kiểm tra giao diện / lớp khi chạy. Bạn có thể kiểm tra nó ở đây

Ví dụ sử dụng:

Trong một trong các tệp bản thảo của bạn, hãy tạo một giao diện và một lớp thực hiện nó như sau:

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

class MyClass implements MyInterface {
    counter = 0;

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

Bây giờ hãy in một số danh sách các giao diện được thực hiện.

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

biên dịch với Reflec-ts và khởi chạy nó:

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

Xem Refl.d.ts để Interfacebiết chi tiết loại meta.

CẬP NHẬT: Bạn có thể tìm thấy một ví dụ làm việc đầy đủ ở đây


8
Tôi đã nghĩ rằng điều này thật ngu ngốc, nhưng sau đó dừng lại một chút, nhìn vào trang github của bạn và thấy nó được cập nhật và được ghi lại rõ ràng để thay thế :-) Tôi vẫn không thể tự mình sử dụng nó ngay bây giờ implementsnhưng muốn nhận ra cam kết của bạn và không muốn có ý nghĩa :-)
Simon_Weaver

5
Trên thực tế, mục đích chính mà tôi thấy về các tính năng phản chiếu này là tạo ra các khung công tác IoC tốt hơn giống như các thế giới Java đã có từ lâu (Spring là lần đầu tiên và quan trọng nhất). Tôi tin chắc rằng TypeScript có thể trở thành một trong những công cụ phát triển tốt nhất trong tương lai và sự phản chiếu là một trong những tính năng mà nó thực sự cần.
pcan

5
... uh, vậy thì, chúng ta phải đưa những "cải tiến" của trình biên dịch này vào bất kỳ bản dựng nào của Bản mô tả trong tương lai? Đây thực sự là một nhánh của Bản in, không phải bản in Bản sao, phải không? Nếu vậy, đây không phải là một giải pháp lâu dài khả thi.
dudewad

1
@dudewad như đã nói trong nhiều chủ đề khác, đây là một giải pháp tạm thời. Chúng tôi đang chờ mở rộng trình biên dịch thông qua các máy biến áp. Vui lòng xem các vấn đề liên quan trong repo TypeScript chính thức. Hơn nữa, tất cả các ngôn ngữ gõ mạnh được chấp nhận rộng rãi đều có sự phản chiếu và tôi nghĩ TypeScript cũng nên có nó. Và giống như tôi, nhiều người dùng khác nghĩ như vậy.
pcan

vâng không phải là tôi không đồng ý - tôi cũng muốn điều này. Chỉ cần quay một trình biên dịch tùy chỉnh ... không có nghĩa là bản vá tiếp theo của Bản mô tả cần được chuyển? Nếu bạn đang bảo vệ nó thì kudos. Có vẻ như rất nhiều công việc. Không gõ nó.
dudewad

9

giống như trên, nơi các vệ sĩ do người dùng định nghĩa đã được sử dụng nhưng lần này với một vị từ chức năng mũi tên

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

8

Đây là một tùy chọn khác: trình xây dựng mô-đun giao diện ts cung cấp công cụ thời gian xây dựng để chuyển đổi giao diện TypeScript thành bộ mô tả thời gian chạy và trình kiểm tra giao diện ts có thể kiểm tra xem một đối tượng có thỏa mãn nó không.

Ví dụ của OP,

interface A {
  member: string;
}

Trước tiên, bạn sẽ chạy ts-interface-buildermột tệp ngắn gọn mới với một bộ mô tả foo-ti.ts, mà bạn có thể sử dụng như thế này:

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" 

Bạn có thể tạo một chức năng bảo vệ loại một lớp lót:

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

6

Tôi muốn chỉ ra rằng TypeScript không cung cấp một cơ chế trực tiếp để kiểm tra động nếu một đối tượng thực hiện một giao diện cụ thể.

Thay vào đó, mã TypeScript có thể sử dụng kỹ thuật JavaScript để kiểm tra xem một nhóm thành viên phù hợp có hiện diện trên đối tượng hay không. Ví dụ:

var obj : any = new Foo();

if (obj.someInterfaceMethod) {
    ...
}

4
Nếu bạn có một hình dạng phức tạp thì sao? bạn sẽ không muốn mã hóa cứng mọi tài sản ở mỗi cấp độ sâu
Tom

@Tom Tôi đoán bạn có thể chuyển (dưới dạng tham số thứ hai cho hàm kiểm tra) một giá trị thời gian chạy hoặc ví dụ / ví dụ - tức là một đối tượng của giao diện mà bạn muốn. Sau đó, thay vì mã hóa cứng, bạn viết bất kỳ ví dụ nào về giao diện mà bạn muốn ... và viết một số mã so sánh đối tượng một lần (sử dụng ví dụ for (element in obj) {}) để xác minh rằng hai đối tượng có các thành phần giống nhau.
ChrisW

3

Kiểu bảo vệ

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 MyInterfaces" là một chú thích thú vị. Điều gì xảy ra nếu thất bại? Trông giống như một kiểm tra giao diện thời gian biên dịch - đó sẽ chỉ là những gì tôi muốn ở nơi đầu tiên. Nhưng nếu trình biên dịch kiểm tra các tham số, tại sao lại có một thân hàm. Và nếu kiểm tra như vậy là có thể, tại sao lại chuyển nó sang một chức năng riêng biệt.
lhk

1
@lhk chỉ cần đọc tài liệu bản thảo về các loại bảo vệ ... typecolang.org/docs/handbook/advified-types.html
Dmitry Matveev

3

Dựa trên câu trả lời của Fenton , đây là việc tôi thực hiện một chức năng để xác minh xem một cái objectđã cho có các khóa mà nó interfacecó, cả đầy đủ hay một phần.

Tùy thuộc vào trường hợp sử dụng của bạn, bạn cũng có thể cần kiểm tra các loại thuộc tính của từng giao diện. Mã dưới đây không làm điều đó.

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

Ví dụ về cách sử dụng:

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
Mặc dù mã này có thể trả lời câu hỏi, cung cấp ngữ cảnh bổ sung về lý do và / hoặc cách mã này trả lời câu hỏi cải thiện giá trị lâu dài của nó.
xiawi

0

Bởi vì loại không xác định tại thời gian chạy, tôi đã viết mã như sau để so sánh đối tượng chưa biết, không phải với một loại, nhưng đối với một đối tượng thuộc loại đã biết:

  1. Tạo một đối tượng mẫu đúng loại
  2. Chỉ định phần tử nào là tùy chọn
  3. So sánh sâu đối tượng chưa biết của bạn với đối tượng mẫu này

Đây là mã (giao diện bất khả tri) mà tôi sử dụng để so sánh sâu:

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

Dưới đây là một ví dụ về cách tôi sử dụng nó.

Trong ví dụ này, tôi hy vọng JSON chứa một mảng các bộ dữ liệu, trong đó phần tử thứ hai là một thể hiện của một giao diện được gọi là User (có hai phần tử tùy chọn).

Kiểm tra kiểu của TypeScript sẽ đảm bảo rằng đối tượng mẫu của tôi là chính xác, sau đó hàm assertTypeT kiểm tra xem đối tượng không xác định (được tải từ JSON) khớp với đối tượng mẫu.

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

Bạn có thể gọi một kiểm tra như thế này trong việc thực hiện bảo vệ loại do người dùng xác định.


0

Bạn có thể xác thực loại TypeScript khi chạy bằng cách sử dụng loại ts-verifyate , như vậy (mặc dù không yêu cầu plugin Babel):

const user = validateType<{ name: string }>(data);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.