TypeScript에서 interface
대 type
)의 차이점은 무엇입니까?
interface X { a: number b: string } type X = { a: number b: string };
질문자 :user6101582
TypeScript에서 interface
대 type
)의 차이점은 무엇입니까?
interface X { a: number b: string } type X = { a: number b: string };
현재 답변과 공식 문서 가 오래되었습니다. 그리고 TypeScript를 처음 사용하는 사람들을 위해 사용된 용어는 예제 없이는 명확하지 않습니다. 다음은 최신 차이점 목록입니다.
둘 다 객체의 모양이나 기능 서명을 설명하는 데 사용할 수 있습니다. 그러나 구문은 다릅니다.
상호 작용
interface Point { x: number; y: number; } interface SetPoint { (x: number, y: number): void; }
유형 별칭
type Point = { x: number; y: number; }; type SetPoint = (x: number, y: number) => void;
인터페이스와 달리 유형 별칭은 프리미티브, 공용체 및 튜플과 같은 다른 유형에도 사용할 수 있습니다.
// primitive type Name = string; // object type PartialPointX = { x: number; }; type PartialPointY = { y: number; }; // union type PartialPoint = PartialPointX | PartialPointY; // tuple type Data = [number, string];
둘 다 확장할 수 있지만 구문이 다릅니다. 또한 인터페이스와 유형 별칭은 상호 배타적이지 않습니다. 인터페이스는 유형 별칭을 확장할 수 있으며 그 반대의 경우도 마찬가지입니다.
인터페이스 확장 인터페이스
interface PartialPointX { x: number; } interface Point extends PartialPointX { y: number; }
유형 별칭은 유형 별칭을 확장합니다.
type PartialPointX = { x: number; }; type Point = PartialPointX & { y: number; };
인터페이스 확장 유형 별칭
type PartialPointX = { x: number; }; interface Point extends PartialPointX { y: number; }
유형 별칭 확장 인터페이스
interface PartialPointX { x: number; } type Point = PartialPointX & { y: number; };
클래스는 동일한 방식으로 인터페이스 또는 유형 별칭을 구현할 수 있습니다. 그러나 클래스와 인터페이스는 정적 청사진으로 간주됩니다. 따라서 공용체 유형을 명명하는 유형 별칭을 구현/확장할 수 없습니다.
interface Point { x: number; y: number; } class SomePoint implements Point { x = 1; y = 2; } type Point2 = { x: number; y: number; }; class SomePoint2 implements Point2 { x = 1; y = 2; } type PartialPoint = { x: number; } | { y: number; }; // FIXME: can not implement a union type class SomePartialPoint implements PartialPoint { x = 1; y = 2; }
유형 별칭과 달리 인터페이스는 여러 번 정의할 수 있으며 단일 인터페이스로 처리됩니다(모든 선언의 멤버가 병합됨).
// These two declarations become: // interface Point { x: number; y: number; } interface Point { x: number; } interface Point { y: number; } const point: Point = { x: 1, y: 2 };
2021년 3월 업데이트 : 최신 TypeScript 핸드북에는 차이점을 설명하는 인터페이스 대 유형 별칭 섹션이 있습니다.
원본 답변(2016)
(현재 보관된) TypeScript 언어 사양에 따라 :
항상 명명된 개체 형식을 도입하는 인터페이스 선언과 달리 형식 별칭 선언 은 기본, 합집합 및 교차 형식을 비롯한 모든 종류의 형식에 대한 이름을 도입할 수 있습니다.
사양은 계속해서 다음과 같이 언급합니다.
인터페이스 유형 은 객체 유형 리터럴의 유형 별칭과 많은 유사점이 있지만 인터페이스 유형이 더 많은 기능을 제공하기 때문에 일반적으로 유형 별칭보다 선호됩니다. 예를 들어 인터페이스 유형
interface Point { x: number; y: number; }
유형 별칭으로 작성할 수 있습니다.
type Point = { x: number; y: number; };
그러나 그렇게 하면 다음 기능이 손실됩니다.
확장 또는 구현 절에서 인터페이스의 이름을 지정할 수 있지만 개체 유형 리터럴에 대한 유형 별칭은TS 2.7 이후로 더 이상 true일 수 없습니다.- 인터페이스는 여러 개의 병합된 선언을 가질 수 있지만 객체 유형 리터럴의 유형 별칭은 그럴 수 없습니다.
typescrpt 버전: 4.3.4
아래에서 설명하는 나의 개인적인 규칙은 다음과 같습니다.
항상
type
보다interface
를 선호합니다.
type
을 사용하는 경우:
type
사용type
튜플 타입을 정의 할 때type
기능 유형을 정의 할 때type
사용type
사용하십시오.type
매핑 유형을 활용할 필요로 할 때 interface
를 사용할 때:
interface
사용하는 모든 객체 유형에 대한 type
필요 없습니다 (위 참조)interface
쉬운 차이 사이에 볼 type
과 interface
만이다 type
별명 프리미티브에 사용될 수있다 :
type Nullish = null | undefined; type Fruit = 'apple' | 'pear' | 'orange'; type Num = number | bigint;
이러한 예 중 어느 것도 인터페이스로 달성할 수 없습니다.
기본 값에 대한 유형 별명을 제공할 때 type
키워드를 사용하십시오.
type
키워드를 통해서만 입력할 수 있습니다.
type row = [colOne: number, colTwo: string];
튜플에 대한 유형을 제공할 때 type
키워드를 사용하십시오.
type
및 interface
키워드로 함수를 입력할 수 있습니다.
// via type type Sum = (x: number, y: number) => number; // via interface interface Sum { (x: number, y: number): number; }
어느 쪽이든 동일한 효과를 얻을 수 있으므로 type
을 사용하는 것이 규칙이 됩니다. 그 이유는 읽기가 더 쉽고 장황하지도 않기 때문입니다.
함수 유형을 정의할 때 type
사용하십시오.
type
키워드로만 얻을 수 있습니다.
type Fruit = 'apple' | 'pear' | 'orange'; type Vegetable = 'broccoli' | 'carrot' | 'lettuce'; // 'apple' | 'pear' | 'orange' | 'broccoli' | 'carrot' | 'lettuce'; type HealthyFoods = Fruit | Vegetable;
공용체 유형을 정의할 때 type
키워드를 사용하십시오.
자바스크립트의 객체는 키/값 맵이고 "객체 유형"은 키/값 맵을 입력하는 typescript의 방법입니다. 원래 질문에서 분명히 알 수 있듯이 객체에 유형을 제공할 때 interface
와 type
그렇다면 객체 유형에 대해 type
대 interface
를 언제 사용합니까?
유형과 구성을 사용하여 다음과 같이 할 수 있습니다.
interface NumLogger { log: (val: number) => void; } type StrAndNumLogger = NumLogger & { log: (val: string) => void; } const logger: StrAndNumLogger = { log: (val: string | number) => console.log(val) } logger.log(1) logger.log('hi')
Typescript는 완전히 만족합니다. 인터페이스로 확장하려고 하면 어떻게 될까요?
interface StrAndNumLogger extends NumLogger { log: (val: string) => void; };
StrAndNumLogger
선언에 오류가 발생합니다 .
Interface 'StrAndNumLogger' incorrectly extends interface 'NumLogger'
인터페이스를 사용하는 경우 하위 유형은 수퍼 유형에 선언된 유형과 정확히 일치해야 합니다. 그렇지 않으면 TS에서 위와 같은 오류가 발생합니다.
객체 유형에서 함수를 오버로드하려고 할 때 type
키워드를 사용하는 것이 좋습니다.
유형과 구별되는 typescript의 인터페이스에 대한 주요 측면은 이미 선언된 후에 새로운 기능으로 확장할 수 있다는 것입니다. 이 기능의 일반적인 사용 사례는 노드 모듈에서 내보낸 유형을 확장하려는 경우에 발생합니다. 예를 들어 @types/jest
는 jest 라이브러리로 작업할 때 사용할 수 있는 유형을 내보냅니다. 그러나 jest는 또한 새로운 기능으로 jest
예를 들어 다음과 같은 사용자 지정 테스트를 추가할 수 있습니다.
jest.timedTest = async (testName, wrappedTest, timeout) => test( testName, async () => { const start = Date.now(); await wrappedTest(mockTrack); const end = Date.now(); console.log(`elapsed time in ms: ${end - start}`); }, timeout );
그런 다음 다음과 같이 사용할 수 있습니다.
test.timedTest('this is my custom test', () => { expect(true).toBe(true); });
이제 테스트가 완료되면 해당 테스트에 대한 경과 시간이 콘솔에 인쇄됩니다. 엄청난! 단 한 가지 문제가 있습니다. typescript는 내가 timedTest
함수를 추가했다는 단서가 없으므로 편집기에서 오류가 발생합니다(코드는 잘 실행되지만 TS는 화를 낼 것입니다).
이 문제를 해결하려면 jest에서 이미 사용할 수 있는 기존 유형 위에 새로운 유형이 있음을 TS에 알려야 합니다. 그렇게 하려면 다음을 수행할 수 있습니다.
declare namespace jest { interface It { timedTest: (name: string, fn: (mockTrack: Mock) => any, timeout?: number) => void; } }
인터페이스가 작동하는 방식 때문에 이 유형 선언은 @types/jest
에서 내보낸 유형 선언과 병합 됩니다. jest.It
다시 선언하지 않았습니다.그것 ; TS가 이제 내 사용자 지정 테스트 기능을 인식할 수 있도록 jest.It
을 새로운 기능으로 확장했습니다.
이 유형의 것은 type
키워드로 불가능합니다. @types/jest
type
키워드를 사용하여 해당 유형을 선언했다면 나는 내 자신의 사용자 정의 유형으로 해당 유형을 확장할 수 없었을 것이므로 TS가 내 새 기능에 대해 만족할 수 있는 좋은 방법이 없었을 것입니다. interface
키워드에 고유한 이 프로세스를 선언 병합 이라고 합니다.
선언 병합은 다음과 같이 로컬에서 수행할 수도 있습니다.
interface Person { name: string; } interface Person { age: number; } // no error const person: Person = { name: 'Mark', age: 25 };
type
키워드로 위와 똑같은 작업을 수행했다면 유형을 다시 선언/병합할 수 없기 때문에 오류가 발생했을 것입니다. 현실 세계에서 자바스크립트 객체는 이 interface
예제와 매우 비슷합니다. 런타임 시 새 필드로 동적으로 업데이트할 수 있습니다.
인터페이스 선언은 병합될 수 있기 때문에 인터페이스는 유형보다 자바스크립트 객체의 동적 특성을 더 정확하게 나타내므로 이러한 이유로 선호되어야 합니다.
type
키워드를 사용하면 다음과 같이 매핑된 유형 을 활용할 수 있습니다.
type Fruit = 'apple' | 'orange' | 'banana'; type FruitCount = { [key in Fruit]: number; } const fruits: FruitCount = { apple: 2, orange: 3, banana: 4 };
이것은 인터페이스로 수행할 수 없습니다:
type Fruit = 'apple' | 'orange' | 'banana'; // ERROR: interface FruitCount { [key in Fruit]: number; }
매핑된 유형을 활용해야 하는 경우 type
키워드를 사용하십시오.
대부분의 경우 객체 유형에 대한 단순 유형 별칭은 인터페이스와 매우 유사하게 작동합니다.
interface Foo { prop: string } type Bar = { prop: string };
그러나 두 개 이상의 유형을 작성해야 하는 경우 인터페이스를 사용하여 해당 유형을 확장하거나 유형 별칭에서 교차하는 옵션이 있으며, 이때 차이점이 중요해지기 시작합니다.
인터페이스는 일반적으로 해결하는 데 중요한 속성 충돌을 감지하는 단일 평면 개체 유형을 만듭니다! 반면에 교차는 속성을 재귀적으로 병합하고 어떤 경우에는 생성하지 않습니다. 인터페이스도 일관되게 더 잘 표시되는 반면 교차에 대한 유형 별칭은 다른 교차의 일부에 표시할 수 없습니다. 교차 유형 전체와 달리 인터페이스 간의 유형 관계도 캐시됩니다. 마지막으로 주목할만한 차이점은 대상 교차 유형에 대해 확인할 때 "유효"/"평평한" 유형에 대해 확인하기 전에 모든 구성 요소를 확인한다는 것입니다.
이러한 이유로 교차 유형 생성보다 인터페이스/확장으로 유형을 확장하는 것이 좋습니다.
typescript wiki 에 대한 자세한 내용.
type
을 사용합니까?일반 변환
여러 형식을 단일 제네릭 형식으로 변환할 때 type
사용합니다.
예시:
type Nullable<T> = T | null | undefined type NonNull<T> = T extends (null | undefined) ? never : T
유형 앨리어싱
우리는 사용할 수있는 type
다시하고 다시 입력 할 읽기 어려울뿐만 아니라 불편 길거나 복잡한 유형의 별칭을 만들기위한.
예시:
type Primitive = number | string | boolean | null | undefined
이와 같은 별칭을 만들면 코드가 더 간결하고 읽기 쉽습니다.
유형 캡처
type
을 알 수 없는 경우 유형을 사용하여 개체의 유형을 캡처합니다.
예시:
const orange = { color: "Orange", vitamin: "C"} type Fruit = typeof orange let apple: Fruit
여기서 우리는 알 수 없는 유형의 orange
Fruit
이라고 부르고 Fruit
을 사용하여 유형이 안전한 새로운 개체 apple
을 만듭니다.
interface
를 사용합니까?다형성
interface
는 데이터의 형태를 구현하기 위한 계약입니다. 인터페이스를 사용하여 구현되고 개체가 사용되는 방식에 대한 계약으로 사용됨을 분명히 하십시오.
예시:
interface Bird { size: number fly(): void sleep(): void } class Hummingbird implements Bird { ... } class Bellbird implements Bird { ... }
type
을 사용하여 이를 달성할 수 있지만 Typescript는 객체 지향 언어로 더 많이 보여지며 interface
는 객체 지향 언어에서 특별한 위치를 차지합니다. 팀 환경에서 작업하거나 오픈 소스 커뮤니티에 기여할 때 interface
가 있는 코드를 더 쉽게 읽을 수 있습니다. 다른 객체 지향 언어에서 온 새로운 프로그래머도 쉽게 사용할 수 있습니다.
공식 Typescript 문서 에도 다음과 같이 나와 있습니다.
... 가능하면
type
interface
를 사용하는 것이 좋습니다.
type
이 형식 자체를 만드는 것보다 형식 별칭을 만드는 데 더 적합하다는 것을 암시합니다.
선언 병합
interface
새 속성과 메서드를 추가하기 위해 interface
의 선언 병합 기능을 사용할 수 있습니다. 이는 타사 라이브러리의 앰비언트 유형 선언에 유용합니다. 타사 라이브러리에 대한 일부 선언이 누락된 경우 동일한 이름으로 인터페이스를 다시 선언하고 새 속성 및 메서드를 추가할 수 있습니다.
예시:
Bird
인터페이스를 확장하여 새 선언을 포함할 수 있습니다.
interface Bird { color: string eat(): void }
그게 다야! 둘 사이의 미묘한 차이에서 길을 잃는 것보다 언제 무엇을 사용해야 하는지 기억하는 것이 더 쉽습니다.
https://www.typescriptlang.org/docs/handbook/advanced-types.html
한 가지 차이점은 인터페이스가 모든 곳에서 사용되는 새 이름을 생성한다는 것입니다. 유형 별칭은 새 이름을 생성하지 않습니다. 예를 들어 오류 메시지는 별칭 이름을 사용하지 않습니다.
// 객체에 대한 트리 구조를 생성합니다. 교집합(&)이 없기 때문에 인터페이스로 동일한 작업을 수행할 수 없습니다.
type Tree<T> = T & { parent: Tree<T> };
// 소수의 값만 할당하도록 변수를 제한하는 유형입니다. 인터페이스에는 공용체(|)가 없습니다.
type Choise = "A" | "B" | "C";
// 유형 덕분에 조건부 메커니즘 덕분에 NonNullable 유형을 선언할 수 있습니다.
type NonNullable<T> = T extends null | undefined ? never : T;
// OOP용 인터페이스를 사용하고 '구현'을 사용하여 객체/클래스 골격을 정의할 수 있습니다.
interface IUser { user: string; password: string; login: (user: string, password: string) => boolean; } class User implements IUser { user = "user1" password = "password1" login(user: string, password: string) { return (user == user && password == password) } }
// 다른 인터페이스로 인터페이스를 확장할 수 있습니다.
interface IMyObject { label: string, } interface IMyObjectWithSize extends IMyObject{ size?: number }
다른 답변은 훌륭합니다! Type
은 할 수 있지만 Interface
는 할 수 없는 몇 가지 다른 작업
type Name = string | { FullName: string }; const myName = "Jon"; // works fine const myFullName: Name = { FullName: "Jon Doe", //also works fine };
type Keys = "firstName" | "lastName"; type Name = { [key in Keys]: string; }; const myName: Name = { firstName: "jon", lastName: "doe", };
extends
에서도 지원됨) type Name = { firstName: string; lastName: string; }; type Address = { city: string; }; const person: Name & Address = { firstName: "jon", lastName: "doe", city: "scranton", };
type
interface
와 비교하여 나중에 소개되지 않았으며 type
의 최신 릴리스에 따르면 interface
할 수 있는 거의 모든 작업을 수행할 수 있으며 훨씬 더 많습니다!
* Declaration merging
제외 ( 개인 의견: 코드의 불일치로 이어질 수 있으므로 형식에서 지원하지 않는 것이 좋습니다 )
TypeScript 핸드북 은 다음과 같이 답을 제공합니다.
주요 차이점은 새로운 속성을 추가하기 위해 유형을 다시 열 수 없으며 항상 확장 가능한 인터페이스가 있다는 것입니다.
이미 제공된 훌륭한 답변 외에도 유형 대 인터페이스 를 확장 할 때 눈에 띄는 차이점이 있습니다. 최근에 인터페이스가 작업을 수행할 수 없는 몇 가지 경우에 부딪쳤습니다.
인터페이스와 유형은 객체와 기본 요소의 유형을 설명하는 데 사용됩니다. 인터페이스와 유형은 종종 서로 바꿔서 사용할 수 있으며 종종 유사한 기능을 제공합니다. 일반적으로 자신의 선호도를 선택하는 것은 프로그래머의 선택입니다.
그러나 인터페이스는 이러한 개체를 만드는 개체와 클래스만 설명할 수 있습니다. 따라서 문자열 및 숫자와 같은 기본 요소를 설명하려면 유형을 사용해야 합니다.
다음은 인터페이스와 유형 간의 2가지 차이점에 대한 예입니다.
// 1. Declaration merging (interface only) // This is an extern dependency which we import an object of interface externDependency { x: number, y: number; } // When we import it, we might want to extend the interface, eg z:number // We can use declaration merging to define the interface multiple times // The declarations will be merged and become a single interface interface externDependency { z: number; } const dependency: externDependency = {x:1, y:2, z:3} // 2. union types with primitives (type only) type foo = {x:number} type bar = { y: number } type baz = string | boolean; type foobarbaz = foo | bar | baz; // either foo, bar, or baz type // instances of type foobarbaz can be objects (foo, bar) or primitives (baz) const instance1: foobarbaz = {y:1} const instance2: foobarbaz = {x:1} const instance3: foobarbaz = true
여기에 또 다른 차이점이 있습니다. 이 상황에 대한 이유나 이유를 설명할 수 있다면... 맥주를 사겠습니다.
enum Foo { a = 'a', b = 'b' } type TFoo = { [k in Foo]: boolean; } const foo1: TFoo = { a: true, b: false} // good // const foo2: TFoo = { a: true } // bad: missing b // const foo3: TFoo = { a: true, b: 0} // bad: b is not a boolean // So type does roughly what I'd expect and want interface IFoo { // [k in Foo]: boolean; /* Uncommenting the above line gives the following errors: A computed property name in an interface must refer to an expression whose type is a literal type or a 'unique symbol' type. A computed property name must be of type 'string', 'number', 'symbol', or 'any'. Cannot find name 'k'. */ } // ???
차종 이런 종류의 나를 내가 의도적으로 일부 OOP 디자인 패턴을 구현하고있어, 또는 (이 나는 그것을위한 아주 좋은 이유가없는 한 나는 결코하지 않을 것) 전술 한 바와 같이 병합 요구하지 않는 인터페이스 지옥을 말하고 싶습니다.
인덱싱의 차이.
interface MyInterface { foobar: string; } type MyType = { foobar: string; } const exampleInterface: MyInterface = { foobar: 'hello world' }; const exampleType: MyType = { foobar: 'hello world' }; let record: Record<string, string> = {}; record = exampleType; // Compiles record = exampleInterface; // Index signature is missing
따라서 개체를 인덱싱하려면 이 예를 고려하십시오.
이 질문을 살펴보세요
평가의 차이
FirstLevelType
이 인터페이스인 경우 ExtendFirst
의 결과 유형을 참조하십시오.
/** * When FirstLevelType is interface */ interface FirstLevelType<A, Z> { _: "typeCheck"; }; type TestWrapperType<T, U> = FirstLevelType<T, U>; const a: TestWrapperType<{ cat: string }, { dog: number }> = { _: "typeCheck", }; // { cat: string; } type ExtendFirst = typeof a extends FirstLevelType<infer T, infer _> ? T : "not extended";
FirstLevelType
이 ExtendFirst
의 결과 유형을 참조하십시오.
/** * When FirstLevelType is type */ type FirstLevelType<A, Z>= { _: "typeCheck"; }; type TestWrapperType<T, U> = FirstLevelType<T, U>; const a: TestWrapperType<{ cat: string }, { dog: number }> = { _: "typeCheck", }; // unknown type ExtendFirst = typeof a extends FirstLevelType<infer T, infer _> ? T : "not extended";
컴파일 속도와 관련하여 구성된 인터페이스는 유형 교차보다 성능이 좋습니다.
[...] 인터페이스는 속성 충돌을 감지하는 단일 평면 개체 유형을 만듭니다. 이는 유효 유형을 확인하기 전에 모든 구성 요소를 확인하는 교차 유형과 대조됩니다. 교차 유형과 달리 인터페이스 간의 유형 관계도 캐시됩니다.
출처: https://github.com/microsoft/TypeScript/wiki/Performance#preferring-interfaces-over-intersections
문서 에서 지적한 주요 차이점은 Interface
를 다시 열 수 있지만 새 속성을 추가하기 위해 Type alias
을 다시 열 수 없다는 것입니다. 예:
괜찮아
interface x { name: string } interface x { age: number }
Duplicate identifier y
오류가 발생합니다.
type y = { name: string } type y = { age: number }
그 외에는 인터페이스와 유형 별칭이 비슷합니다.
typescript에서는 "유형"보다 "인터페이스"를 권장합니다.
"type"은 유형 별칭을 만드는 데 사용됩니다.
type Data=string
그런 다음 문자열을 사용하는 대신 "데이터"를 사용할 수 있습니다.
const name:string="Yilmaz" const name:Data="Yilmaz"
별칭은 특히 제네릭 유형 작업에 매우 유용합니다.
"인터페이스"로는 이것을 할 수 없습니다.
인터페이스는 병합할 수 있지만 유형은 병합할 수 없습니다.
인터페이스 사람 { 이름: 문자열; }
interface Person { age: number; } const me: Person = { name: "Yilmaz Bingol", age: 30 };
함수형 프로그래밍 사용자는 "유형"을 사용하고 객체 지향 프로그래밍 사용자는 "인터페이스"를 선택합니다.
인터페이스가 아닌 유형에는 계산되거나 계산된 속성이 있을 수 없습니다.
유형 전체 이름 = "이름" | "성"
type Person= { [key in Keys]: string } const me: Person = { firstname: "Yilmaz", lastname: "Bingol" }
음 'typescriptlang'은 가능한 한 유형보다 인터페이스 사용을 권장하는 것 같습니다. 인터페이스 대 유형 별칭
그들은 의미론에서 다릅니다.
인터페이스는 TS 유형 시스템의 일반 구문 요소입니다. TS 구문의 기본 부분입니다.
유형 별칭은 구문 설탕입니다. 일종의 메타 프로그래밍입니다.
클래스 멤버/속성/함수가 아닌 재귀적으로 개체 리터럴 유형 및 인터페이스를 재귀적으로 다시 작성하는 기능을 보여줍니다.
또한 위에서 논의한 문제에 대한 차이점과 해결 방법을 구별하고 유형 확인하는 방법, Record<any, string|number>가 인터페이스 등으로 인해 작동하지 않을 때 해결합니다. 이렇게 하면 몽구스 유형에 대해 잠재적으로 다음을 단순화할 수 있습니다. https://github.com/wesleyolis/mongooseRelationalTypes mongooseRelationalTypes, DeepPopulate, populate
또한 고급 유형 제네릭 및 유형 추론을 수행하는 다른 접근 방식과 속도에 대한 주변의 단점, 시행착오의 많은 실험을 통해 올바른 결과를 얻을 수 있는 모든 작은 트릭이 있습니다.
Typescript 놀이터: 라이브 플레이 그라운드의 모든 예를 보려면 여기를 클릭하십시오.
class TestC { constructor(public a: number, public b: string, private c: string) { } } class TestD implements Record<any, any> { constructor(public a: number, public b: string, private c: string) { } test() : number { return 1; } } type InterfaceA = { a: string, b: number, c: Date e: TestC, f: TestD, p: [number], neastedA: { d: string, e: number h: Date, j: TestC neastedB: { d: string, e: number h: Date, j: TestC } } } type TCheckClassResult = InterfaceA extends Record<any, unknown> ? 'Y': 'N' // Y const d = new Date(); type TCheckClassResultClass = typeof d extends Record<any, unknown> ? 'Y': 'N' // N const metaData = Symbol('metaData'); type MetaDataSymbol = typeof metaData; // Allows us to not recuse into class type interfaces or traditional interfaces, in which properties and functions become optional. type MakeErrorStructure<T extends Record<any, any>> = { [K in keyof T] ?: (T[K] extends Record<any, unknown> ? MakeErrorStructure<T[K]>: T[K] & Record<MetaDataSymbol, 'customField'>) } type MakeOptional<T extends Record<any, any>> = { [K in keyof T] ?: T[K] extends Record<any, unknown> ? MakeOptional<T[K]> : T[K] } type RRR = MakeOptional<InterfaceA> const res = {} as RRR; const num = res.e!.a; // type == number const num2 = res.f!.test(); // type == number
재귀 모양 또는 특정 모양의 키를 재귀적으로 만들기
type MakeRecusive<Keys extends string, T> = { [K in Keys]: T & MakeRecusive<K, T> } & T type MakeRecusiveObectKeys<TKeys extends string, T> = { [K in keyof T]: K extends TKeys ? T[K] & MakeRecusive<K, T[K]>: T[K] }
판별자와 같은 인터페이스의 유효성을 검사할 수 있는 레코드 유형에 대한 유형 제약 조건을 적용하는 방법:
type IRecordITypes = string | symbol | number; // Used for checking interface, because Record<'key', Value> excludeds interfaces type IRecord<TKey extends IRecordITypes, TValue> = { [K in TKey as `${K & string}`] : TValue } // relaxies the valiation, older versions can't validate. // type IRecord<TKey extends IRecordITypes, TValue> = { // [index: TKey] : TValue // } type IRecordAnyValue<T extends Record<any,any>, TValue> = { [K in keyof T] : TValue } interface AA { A : number, B : string } interface BB { A: number, D: Date } // This approach can also be used, for indefinitely recursive validation like a deep populate, which can't determine what validate beforehand. interface CheckRecConstraints<T extends IRecordAnyValue<T, number | string>> { } type ResA = CheckRecConstraints<AA> // valid type ResB = CheckRecConstraints<BB> // invalid Alternative for checking keys: type IRecordKeyValue<T extends Record<any,any>, TKey extends IRecordITypes, TValue> = { [K in keyof T] : (TKey & K) extends never ? never : TValue } // This approach can also be used, for indefinitely recursive validation like a deep populate, which can't determine what validate beforehand. interface CheckRecConstraints<T extends IRecordKeyValue<T, number | string, number | string>> { A : T } type UUU = IRecordKeyValue<AA, string, string | number> type ResA = CheckRecConstraints<AA> // valid type ResB = CheckRecConstraints<BB> // invalid
그러나 Discriminators를 사용하는 예는 속도를 위해 각 키를 Record에 정의한 다음 이 접근 방식보다 적은 메모리를 사용하고 더 빠르기 때문에 혼합 값을 생성하기 위해 전달하는 문자 그대로 사용하고 싶습니다.
type EventShapes<TKind extends string> = IRecord<TKind, IRecordITypes> | (IRecord<TKind, IRecordITypes> & EventShapeArgs) type NonClassInstance = Record<any, unknown> type CheckIfClassInstance<TCheck, TY, TN> = TCheck extends NonClassInstance ? 'N' : 'Y' type EventEmitterConfig<TKind extends string = string, TEvents extends EventShapes<TKind> = EventShapes<TKind>, TNever = never> = { kind: TKind events: TEvents noEvent: TNever } type UnionDiscriminatorType<TKind extends string, T extends Record<TKind, any>> = T[TKind] type PickDiscriminatorType<TConfig extends EventEmitterConfig<any, any, any>, TKindValue extends string, TKind extends string = TConfig['kind'], T extends Record<TKind, IRecordITypes> & ({} | EventShapeArgs) = TConfig['events'], TNever = TConfig['noEvent']> = T[TKind] extends TKindValue ? TNever : T extends IRecord<TKind, TKindValue> ? T extends EventShapeArgs ? T['TArgs'] : [T] : TNever type EventEmitterDConfig = EventEmitterConfig<'kind', {kind: string | symbol}, any> type EventEmitterDConfigKeys = EventEmitterConfig<any, any> // Overide the cached process of the keys. interface EventEmitter<TConfig extends EventEmitterConfig<any, any, any> = EventEmitterDConfig, TCacheEventKinds extends string = UnionDiscriminatorType<TConfig['kind'], TConfig['events']> > { on<TKey extends TCacheEventKinds, T extends Array<any> = PickDiscriminatorType<TConfig, TKey>>( event: TKey, listener: (...args: T) => void): this; emit<TKey extends TCacheEventKinds>(event: TKey, args: PickDiscriminatorType<TConfig, TKey>): boolean; }
사용 예:
interface EventA { KindT:'KindTA' EventA: 'EventA' } interface EventB { KindT:'KindTB' EventB: 'EventB' } interface EventC { KindT:'KindTC' EventC: 'EventC' } interface EventArgs { KindT:1 TArgs: [string, number] } const test :EventEmitter<EventEmitterConfig<'KindT', EventA | EventB | EventC | EventArgs>>; test.on("KindTC",(a, pre) => { })
좁히기 위해 맵에서 유형을 구별하고 유형을 선택하는 더 나은 접근 방식을 통해 일반적으로 성능이 빨라지고 유형 조작에 대한 오버헤드가 줄어들고 캐싱이 개선됩니다. 위의 이전 예와 비교하십시오.
type IRecordKeyValue<T extends Record<any,any>, TKey extends IRecordITypes, TValue> = { [K in keyof T] : (TKey & K) extends never ? never : TValue } type IRecordKeyRecord<T extends Record<any,any>, TKey extends IRecordITypes> = { [K in keyof T] : (TKey & K) extends never ? never : T[K] // need to figure out the constrint here for both interface and records. } type EventEmitterConfig<TKey extends string | symbol | number, TValue, TMap extends IRecordKeyValue<TMap, TKey, TValue>> = { map: TMap } type PickKey<T extends Record<any,any>, TKey extends any> = (T[TKey] extends Array<any> ? T[TKey] : [T[TKey]]) & Array<never> type EventEmitterDConfig = EventEmitterConfig<string | symbol, any, any> interface TDEventEmitter<TConfig extends EventEmitterConfig<any, any, TConfig['map']> = EventEmitterDConfig, TMap = TConfig['map'], TCacheEventKinds = keyof TMap > { on<TKey extends TCacheEventKinds, T extends Array<any> = PickKey<TMap, TKey>>(event: TKey, listener: (...args: T) => void): this; emit<TKey extends TCacheEventKinds, T extends Array<any> = PickKey<TMap, TKey>>(event: TKey, ...args: T): boolean; } type RecordToDiscriminateKindCache<TKindType extends string | symbol | number, TKindName extends TKindType, T extends IRecordKeyRecord<T, TKindType>> = { [K in keyof T] : (T[K] & Record<TKindName, K>) } type DiscriminateKindFromCache<T extends IRecordKeyRecord<T, any>> = T[keyof T]
사용 예:
interface EventA { KindT:'KindTA' EventA: 'EventA' } interface EventB { KindT:'KindTB' EventB: 'EventB' } interface EventC { KindT:'KindTC' EventC: 'EventC' } type EventArgs = [number, string] type Items = { KindTA : EventA, KindTB : EventB, KindTC : EventC //0 : EventArgs, } type DiscriminatorKindTypeUnionCache = RecordToDiscriminateKindCache<string //| number, "KindGen", Items>; type CachedItemForSpeed = DiscriminatorKindTypeUnionCache['KindTB'] type DiscriminatorKindTypeUnion = DiscriminateKindFromCache<DiscriminatorKindTypeUnionCache>; function example() { const test: DiscriminatorKindTypeUnion; switch(test.KindGen) { case 'KindTA': test.EventA break; case 'KindTB': test.EventB break; case 'KindTC': test.EventC case 0: test.toLocaleString } } type EmitterConfig = EventEmitterConfig<string //| number , any, Items>; const EmitterInstance :TDEventEmitter<EmitterConfig>; EmitterInstance.on("KindTB",(a, b) => { a. })
문서가 설명했습니다
- 한 가지 차이점은 인터페이스가 모든 곳에서 사용되는 새 이름을 생성한다는 것입니다. 유형 별칭은 새 이름을 생성하지 않습니다. 예를 들어, 오류 메시지는 별칭 이름을 사용하지 않습니다. TypeScript의 이전 버전에서는 유형 별칭을 확장하거나 구현할 수 없었습니다(다른 유형을 확장/구현할 수도 없음). 버전 2.7부터 새로운 교차 유형을 생성하여 유형 별칭을 확장할 수 있습니다.
- 반면에 인터페이스로 어떤 모양을 표현할 수 없고 공용체 또는 튜플 유형을 사용해야 하는 경우 일반적으로 유형 별칭을 사용하는 것이 좋습니다.
출처 : http:www.stackoverflow.com/questions/37233735/typescript-interfaces-vs-types
이미 원격 브랜치로 푸시된 병합 커밋을 되돌리는 방법은 무엇입니까? (0) | 2023.04.14 |
---|---|
JavaScript에서 null과 undefined의 차이점은 무엇입니까? (0) | 2023.04.14 |
window.onload 대 $(document).ready() (0) | 2023.04.14 |
Android Studio 프로젝트의 .gitignore에는 무엇이 있어야 합니까? (0) | 2023.04.14 |
MySQL 쿼리 결과를 CSV 형식으로 출력하려면 어떻게 해야 합니까? (0) | 2023.04.14 |