Typescript를 사용한 인터페이스 유형 확인


294

이 질문은 TypeScript를 사용하여 클래스 유형 검사와 직접 유사합니다.

임의 유형의 변수가 인터페이스를 구현하는지 런타임에 찾아야합니다. 내 코드는 다음과 같습니다.

interface A{
    member:string;
}

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

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

타이프 스크립트 놀이터에서이 코드를 입력하면 마지막 줄에 "이름 A가 현재 범위에 없습니다"라는 오류가 표시됩니다. 그러나 사실이 아닙니다. 이름이 현재 범위에 존재합니다. 변수 선언을 var a:A={member:"foobar"};편집기에서 불만없이 변경할 수도 있습니다 . 웹을 탐색하고 SO에서 다른 질문을 찾은 후 인터페이스를 클래스로 변경했지만 객체 리터럴을 사용하여 인스턴스를 만들 수 없습니다.

유형 A가 어떻게 사라질 수 있는지 궁금했지만 생성 된 자바 스크립트를 살펴보면 문제가 설명됩니다.

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

A를 인터페이스로 나타내지 않으므로 런타임 유형 검사가 불가능합니다.

동적 언어로서의 자바 스크립트에는 인터페이스 개념이 없다는 것을 이해합니다. 인터페이스 검사를 입력하는 방법이 있습니까?

타이프 스크립트 놀이터의 자동 완성 기능은 타이프 스크립트에서도 메소드를 제공한다는 것을 보여줍니다 implements. 어떻게 사용합니까?


4
JavaScript에는 인터페이스 개념이 없지만 동적 언어이기 때문이 아닙니다. 인터페이스가 아직 구현되지 않았기 때문입니다.
trusktr

예, 그러나 인터페이스 대신 클래스를 사용할 수 있습니다. 예를 참조하십시오 .
Alexey Baranoshnikov

분명히 2017 년이 아닙니다. 지금 슈퍼 관련 질문입니다.
5

답변:


221

instanceof사용자 정의 유형 가드를 작성할 수 있으므로 키워드 없이 원하는 것을 얻을 수 있습니다 .

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

많은 회원

개체가 유형과 일치하는지 확인하기 위해 많은 구성원을 확인해야하는 경우 대신 판별자를 추가 할 수 있습니다. 아래는 가장 기본적인 예이며 자신의 판별자를 관리해야합니다. 중복 판별자를 피하려면 패턴에 대해 더 깊이 알아야합니다.

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
"인터페이스를 런타임으로 확인할 수있는 방법이 없습니다." 그들은 어떤 이유로 든 아직 구현하지 않았습니다.
trusktr

16
인터페이스에 멤버가 100 명이라면 100 명을 모두 확인해야합니까? 푸바.
Jenny O'Reilly

4
100 개를 모두 확인하는 대신 개체에 판별기를 추가 할 수 있습니다.
Fenton

7
이 판별 기 패러다임 (여기서 작성)은 인터페이스 확장을 지원하지 않습니다. 파생 인터페이스는 기본 인터페이스의 인스턴스인지 확인하면 false를 반환합니다.
Aaron

1
@ 펜톤 아마도 이것에 대해 충분히 알지 못하지만 인터페이스 A를 확장하는 인터페이스 B가 있다고 가정하면 isInstanceOfA(instantiatedB)true를 반환하고 싶지만 isInstanceOfB(instantiatedA)false를 반환 하려고 합니다. 후자가 발생하기 위해 B의 차별자가 'I-AM-A'일 필요는 없습니까?
Aaron

87

TypeScript 1.6에서는 사용자 정의 유형 가드 가 작업을 수행합니다.

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이 언급했듯이 TypeScript 2.0부터 태그가 지정된 Union 유형을 활용할 수도 있습니다.

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

그리고 그것은 또한 작동합니다 switch.


1
다소 궁금해 보인다. 분명히 어떤 종류의 메타 정보가 있습니다. 이 type-guard 구문으로 노출해야하는 이유 isinstanceof?와는 달리 함수가 작동하는 옆에 "객체가 인터페이스"라는 제약 조건이 있습니까? 보다 정확하게 if 문에서 "object is interface"를 직접 사용할 수 있습니까? 그러나 어쨌든 매우 흥미로운 구문은 +1입니다.
lhk

1
@lhk 그러한 진술은 없습니다. 조건부 분기 내에서 유형을 좁히는 방법을 알려주는 특수 유형과 비슷합니다. TypeScript의 "범위"로 인해 앞으로도 그러한 언급이 없을 것이라고 생각합니다. 사이의 또 다른 object is type과는 object instanceof class상관 없습니다, 일반 객체 또는 클래스의 인스턴스 : 대신 물체가 모양에서셨어요 경우 만 "모양"관심, 타이프 라이터에 그, 유형 구조입니다.
vilicvane

2
오해의 여지를 없애기 위해이 답변이 만들 수 있습니다. 런타임 중에 객체 유형이나 인터페이스를 공제 할 메타 정보가 없습니다.
mostruash

@mostruash Yep, 답변의 후반부는 컴파일하더라도 런타임에 작동하지 않습니다.
trusktr

4
그러나 런타임시 이러한 오브젝트가 type특성 으로 작성되었다고 가정해야합니다 . 이 경우 작동합니다. 이 예는이 사실을 보여주지 않습니다.
trusktr

40

typescript 2.0 소개 태그 연합

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

2.0 베타 버전을 사용하고 있지만 태그 조합이 작동하지 않습니다. <TypeScriptToolsVersion> 2.0 </ TypeScriptToolsVersion>
Makla

야간 빌드로 컴파일되었지만 인텔리전스가 작동하지 않습니다. 또한 오류가 나열됩니다. 속성 너비 / 크기 / ...가 유형 '평방 | 사각형 | 사건 진술서에 동그라미를 치십시오. 그러나 컴파일됩니다.
Makla

23
이것은 실제로 차별자를 사용하는 것입니다.
Erik Philips

33

사용자 정의 타입 가드는 어떻습니까? 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
유사 - 이것은 나의 마음에 드는 대답이다 stackoverflow.com/a/33733258/469777 하지만 인해 축소를 같은 것들에 중단 될 수 있습니다 마법 문자열없이.
Stafford Williams

1
이것은 어떤 이유로 든 나를 위해 작동하지 않았지만 작동했습니다 (pet as Fish).swim !== undefined;.
CyberMew

18

이제 가능합니다. 방금 TypeScript완전한 리플렉션 기능을 제공 하는 향상된 버전의 컴파일러를 출시했습니다 . 메타 데이터 개체에서 클래스를 인스턴스화하고 클래스 생성자에서 메타 데이터를 검색하고 런타임에 인터페이스 / 클래스를 검사 할 수 있습니다. 여기서 확인할 수 있습니다

사용 예 :

타입 스크립트 파일 중 하나에서 다음과 같이 인터페이스와 인터페이스를 구현하는 클래스를 만듭니다.

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

class MyClass implements MyInterface {
    counter = 0;

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

이제 구현 된 인터페이스 목록을 인쇄하겠습니다.

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

reflec-ts로 컴파일하고 시작하십시오.

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

Interface메타 유형에 대한 자세한 내용은 reflection.d.ts를 참조 하십시오.

업데이트 : 전체 작업 예를 찾을 수 있습니다 여기


8
downvoted cos 나는 이것이 바보라고 생각했지만 잠시 잠시 멈추고 github 페이지를보고 그것을 최신 상태로 유지하고 대신 잘 문서화 된 것을 보았습니다. implements그러나 당신의 헌신을 인정하고 싶었고 의미가되고 싶지 않았습니다 :-)
Simon_Weaver

5
실제로,이 리플렉션 기능에서 볼 수있는 주요 목적은 자바 세계가 오랫동안 가지고 온 프레임 워크와 같은 더 나은 IoC 프레임 워크를 만드는 것입니다 (봄은 첫 번째이자 가장 중요한 프레임 워크입니다). 나는 TypeScript가 미래의 최고의 개발 도구 중 하나가 될 수 있다고 굳게 믿고 있으며 리플렉션은 실제로 필요한 기능 중 하나입니다.
pcan

5
... 어떻게 미래의 Typescript 빌드에 이러한 컴파일러 "향상된 기능"을 적용해야합니까? 이것은 사실상 Typescript 자체가 아니라 Typescript 포크입니다. 그렇다면, 이것은 가능한 장기적인 해결책이 아닙니다.
dudewad

1
다른 많은 주제에서 말한 것처럼 @dudewad는 임시 해결책입니다. 우리는 변압기를 통해 컴파일러 확장 성을 기다리고 있습니다. 공식 TypeScript 저장소에서 관련 문제를 참조하십시오. 또한 널리 채택 된 모든 강력한 유형의 언어에는 반성이 있으며 TypeScript에도 해당 언어가 있어야한다고 생각합니다. 저처럼 다른 많은 사용자들도 그렇게 생각합니다.
pcan

네, 동의하지 않는 것이 아닙니다. 나도 이것을 원합니다. 그냥, 커스텀 컴파일러를 돌리는 것은 ... 다음 Typescript 패치를 이식해야한다는 의미는 아닙니까? 당신이 그것을 유지하고 있다면 kudos. 많은 일처럼 보입니다. 노크하지 않습니다.
dudewad

10

사용자 정의 가드 가 사용 위와 동일 하지만 이번에는 화살표 함수 술어

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

또 다른 옵션은 다음과 같습니다. ts-interface-builder 모듈 은 TypeScript 인터페이스를 런타임 디스크립터로 변환하는 빌드 타임 도구를 제공하며, ts-interface-checker 는 객체가이를 만족하는지 확인할 수 있습니다.

OP의 예에서

interface A {
  member: string;
}

먼저 ts-interface-builder설명자와 함께 새로운 간결한 파일을 생성하는 foo-ti.ts다음과 같이 사용할 수 있습니다.

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" 

단일 라이너 유형 보호 기능을 만들 수 있습니다.

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

6

TypeScript는 객체가 특정 인터페이스를 구현하는지 여부를 동적으로 테스트하기위한 직접적인 메커니즘을 제공하지 않는다고 지적하고 싶습니다.

대신 TypeScript 코드는 JavaScript 멤버를 사용하여 개체에 적절한 멤버 집합이 있는지 확인합니다. 예를 들면 다음과 같습니다.

var obj : any = new Foo();

if (obj.someInterfaceMethod) {
    ...
}

4
복잡한 모양이라면 어떻게해야합니까? 각 수준의 깊이에서 모든 단일 속성을 하드 코딩하고 싶지는 않습니다.
Tom

@Tom 런타임 값이나 예제 / 예제-원하는 인터페이스의 객체 (체커 함수에 두 번째 매개 변수로)를 전달할 수 있다고 생각합니다. 그런 다음 하드 코딩 코드 대신 원하는 인터페이스의 예를 작성하고 일회성 객체 비교 코드 (예 for (element in obj) {}:)를 작성하여 두 객체가 유사한 유형의 유사한 요소를 가지고 있는지 확인합니다.
ChrisW

5

타입 가드

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"는 흥미로운 주석입니다. 실패하면 어떻게됩니까? 컴파일 타임 인터페이스 검사처럼 보입니다. 처음에 원하는 것입니다. 그러나 컴파일러가 매개 변수를 확인하면 함수 본문이있는 이유는 무엇입니까? 그리고 그러한 점검이 가능하다면 왜 별도의 기능으로 옮기십시오.
lhk

1
@lhk 단지 형 가드에 대한 타이프 라이터 문서를 읽어 ... typescriptlang.org/docs/handbook/advanced-types.html
드미트리 Matveev

3

Fenton의 답변을 바탕으로 주어진 또는 주어진 object키에 interface전체 또는 부분 키가 있는지 확인하는 함수를 구현했습니다 .

사용 사례에 따라 각 인터페이스 속성 유형을 확인해야 할 수도 있습니다. 아래 코드는 그렇게하지 않습니다.

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

사용 예 :

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
이 코드는 질문에 대답 할 수 있지만,이 코드가 질문에 응답하는 이유 및 / 또는 방법에 대한 추가 컨텍스트를 제공하면 장기적인 가치가 향상됩니다.
xiawi

0

런타임에 유형을 알 수 없으므로 알 수없는 객체를 유형이 아니라 알려진 유형의 객체와 비교하기 위해 다음과 같이 코드를 작성했습니다.

  1. 올바른 유형의 샘플 객체 생성
  2. 해당 요소 중 어느 것이 선택적인지 지정
  3. 이 샘플 객체와 알 수없는 객체를 심도있게 비교하십시오.

자세한 비교에 사용하는 (인터페이스에 구애받지 않는) 코드는 다음과 같습니다.

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

아래는 내가 그것을 사용하는 방법의 예입니다.

이 예제에서 JSON에는 튜플 배열이 포함되어 있으며 두 번째 요소는 User두 개의 선택적 요소가 있는 인터페이스의 인스턴스입니다 .

TypeScript의 유형 검사는 샘플 객체가 올바른지 확인한 다음 assertTypeT 함수는 알 수없는 (JSON에서로드 된) 객체가 샘플 객체와 일치하는지 확인합니다.

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

사용자 정의 유형 가드 구현에서 이와 같은 검사를 호출 할 수 있습니다.


0

ts-validate-type을 사용하여 런타임에 TypeScript 유형의 유효성을 검사 할 수 있습니다 (Babel 플러그인이 필요함).

const user = validateType<{ name: string }>(data);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.