TypeScript의 인터페이스 대 유형
TypeScript에서 이 문장들(대)의 차이점은 무엇입니까?
interface X {
a: number
b: string
}
type X = {
a: number
b: string
};
: 최신 TypeScript 핸드북( 참조)에는 차이점을 설명하는 섹션이 있습니다.
원답 (2016)
에 따라:
항상 명명된 객체 유형을 도입하는 인터페이스 선언과 달리 원시, 결합 및 교차 유형을 포함한 모든 유형의 이름을 도입할 수 있습니다.
사양은 다음과 같이 언급된다:
는 객체 유형 리터럴의 유형 별칭과 많은 유사성이 있지만 인터페이스 유형은 더 많은 기능을 제공하기 때문에 일반적으로 유형 별칭보다 선호됩니다. 예를 들어, 인터페이스
interface Point { x: number; y: number; }
유형 별칭으로 쓸 수 있습니다
type Point = { x: number; y: number; };
그러나 이렇게 하면 다음 기능이 손실됩니다:
- TS 2.7 이후 더 이상 사실이 아닙니다.
- 인터페이스는 여러 개를 가질 수 있지만 개체 유형 리터럴의 유형 별칭은 가질 수 없습니다.
https://www.typescriptlang.org/docs/handbook/advanced-types.html
한 가지 차이점은 인터페이스가 모든 곳에서 사용되는 새로운 이름을 만든다는 것이다. 별칭을 입력하면 새 이름이 생성되지 않습니다. 예를 들어 오류 메시지에서 별칭 이름을 사용하지 않습니다.
2019년 업데이트
현재 답변과 는 구식입니다. 그리고 TypeScript를 처음 접하는 사람들에게 사용되는 용어는 예시 없이는 명확하지 않다. 다음은 최신 차이점 목록입니다.
1. 객체/기능
둘 다 객체의 모양이나 함수 서명을 설명하는 데 사용될 수 있다. 하지만 구문은 다릅니다.
인터페이스
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;
2. 기타 유형
인터페이스와 달리 유형 별칭은 원시, 유니언 및 튜플과 같은 다른 유형에도 사용할 수 있습니다.
// primitive
type Name = string;
// object
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };
// union
type PartialPoint = PartialPointX | PartialPointY;
// tuple
type Data = [number, string];
3. 연장
둘 다 확장할 수 있지만 구문이 다릅니다. 또한 인터페이스와 유형 별칭은 서로 배타적이지 않습니다. 인터페이스는 유형 별칭을 확장할 수 있으며, 그 반대도 마찬가지입니다.
인터페이스 확장 인터페이스
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; };
4. 기구
클래스는 동일한 방식으로 인터페이스 또는 유형 별칭을 구현할 수 있습니다. 그러나 클래스와 인터페이스는 정적 Blueprint로 간주됩니다. 따라서 조합 유형의 이름을 지정하는 형식 별칭을 구현하거나 확장할 수 없습니다.
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;
}
5. 선언 병합
형식 별칭과 달리 인터페이스는 여러 번 정의될 수 있으며 단일 인터페이스로 처리됩니다(모든 선언의 구성원이 병합됨).
// 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 };
타입스크립트 3.2 (2018년 11월) 기준으로 다음과 같다:
측면 | 유형 | 인터페이스 |
---|---|---|
함수를 설명할 수 있습니다 | ✅ | ✅ |
생성자를 설명할 수 있습니다 | ✅ | ✅ |
튜플을 설명할 수 있습니다 | ✅ | ✅ |
인터페이스를 통해 확장 가능 | ⚠️ | ✅ |
클래스에서 확장 가능 | 🚫 | ✅ |
클래스에서 구현할 수 있습니다() | ⚠️ | ✅ |
같은 종류의 다른 것을 교차할 수 있다 | ✅ | ⚠️ |
다른 종류의 조합을 만들 수 있습니다 | ✅ | 🚫 |
매핑된 유형을 만드는 데 사용할 수 있습니다 | ✅ | 🚫 |
매핑된 유형으로 매핑할 수 있습니다 | ✅ | ✅ |
오류 메시지 및 로그에서 확장 | ✅ | 🚫 |
증강 가능 | 🚫 | ✅ |
재귀적일 수 있음 | ⚠️ | ✅ |
⚠️ 경우에 따라
유형이 있는 예:
// 개체의 트리 구조를 만듭니다. 교차점이 없기 때문에 인터페이스에서도 동일한 작업을 수행할 수 없습니다(&)
type Tree<T> = T & { parent: Tree<T> };
// 몇 개의 값만 할당하도록 변수를 제한하려면 type을 입력합니다. 인터페이스에 유니온이 없습니다(|)
type Choise = "A" | "B" | "C";
// 유형 덕분에 조건부 메커니즘 덕분에 NonNullable 유형을 선언할 수 있습니다.
type NonNullable<T> = T extends null | undefined ? never : T;
인터페이스의 예:
// OOP용 인터페이스를 사용하고 'implements'를 사용하여 객체/클래스 스켈레톤을 정의할 수 있습니다
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
}
문서는 설명했다
- 한 가지 차이점은 인터페이스가 모든 곳에서 사용되는 새로운 이름을 만든다는 것이다. 예를 들어, 오류 메시지는 name.in 이전 버전의 TypeScript 별칭을 사용하지 않으며, 유형 별칭을 확장하거나 구현할 수 없습니다(다른 유형을 확장/축소할 수도 없습니다). 버전 2.7부터는 새로운 교차로 유형을 만들어 유형 별칭을 확장할 수 있습니다
- 반면에 인터페이스로 일부 모양을 표현할 수 없고 유니언 또는 튜플 유형을 사용해야 하는 경우에는 일반적으로 유형 별칭을 사용하는 것이 좋습니다.
이미 제공된 훌륭한 답변 외에도 유형과 인터페이스에 관해서는 눈에 띄는 차이가 있습니다. 최근에 인터페이스가 작업을 수행할 수 없는 경우가 몇 가지 있었습니다:
다른 답들도 좋아요! 할 수 있지만 할 수 없는 몇 가지 다른 것들
유형에 유니언을 사용할 수 있습니다
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",
};
교차로 형식(단, 인터페이스에서도 지원됨)
type Name = {
firstName: string;
lastName: string;
};
type Address = {
city: string;
};
const person: Name & Address = {
firstName: "jon",
lastName: "doe",
city: "scranton",
};
또한 TS의 최신 릴리스와 비교하여 나중에 소개된 것은 아닙니다 *할 수 있는 거의 모든 것과 그 이상을 할 수 있습니다!
*() 제외
인터페이스 대 유형
인터페이스와 유형은 객체와 원시 요소의 유형을 설명하는 데 사용됩니다. 인터페이스와 유형 모두 상호 교환적으로 사용할 수 있으며 유사한 기능을 제공하는 경우가 많습니다. 일반적으로 자신의 취향을 선택하는 것은 프로그래머의 선택이다.
그러나 인터페이스는 이러한 개체를 만드는 개체 및 클래스만 설명할 수 있습니다. 따라서 문자열과 숫자와 같은 원시 요소를 설명하기 위해 유형을 사용해야 합니다.
다음은 인터페이스와 유형 간의 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, e.g. 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
언제 사용해야 합니까?
일반 변환
여러 유형을 단일 일반 유형으로 변환할 때 사용합니다.
예:
type Nullable<T> = T | null | undefined
type NonNull<T> = T extends (null | undefined) ? never : T
유형 별칭 지정
읽기 어려울 뿐만 아니라 반복해서 입력하기 불편한 길고 복잡한 유형의 별칭을 만드는 데 사용할 수 있습니다.
예:
type Primitive = number | string | boolean | null | undefined
이와 같은 별칭을 만들면 코드가 더 간결하고 읽을 수 있습니다.
유형 캡처
개체 유형을 알 수 없는 경우 개체 유형을 캡처하려면 를 사용합니다.
예:
const orange = { color: "Orange", vitamin: "C"}
type Fruit = typeof orange
let apple: Fruit
여기에서는 의 알 수 없는 유형을 a라고 부르고 를 사용하여 새 유형 안전 개체를 만듭니다.
언제 사용해야 합니까?
다형성
A는 데이터의 형태를 구현하기 위한 계약이다. 인터페이스를 사용하여 객체가 어떻게 사용될 것인지에 대한 계약으로 구현되고 사용될 것임을 명확히 합니다.
예:
interface Bird {
size: number
fly(): void
sleep(): void
}
class Hummingbird implements Bird { ... }
class Bellbird implements Bird { ... }
이를 위해 를 사용할 수 있지만 유형 스크립트는 개체 지향 언어로 표시되며 개체 지향 언어에서 특별한 위치를 차지합니다. 팀 환경에서 작업하거나 오픈 소스 커뮤니티에 기여할 때 코드를 읽는 것이 더 쉽습니다. 그것은 다른 객체 지향 언어에서 오는 새로운 프로그래머들에게도 쉽다.
공식적인 유형 스크립트는 다음과 같이 말합니다:
... 가능한 경우 별칭을 사용하는 것이 좋습니다.
이는 또한 가 유형 자체를 만드는 것보다 유형 별칭을 만드는 것을 더 목적으로 한다는 것을 나타냅니다.
선언 병합
의 선언 병합 기능을 사용하여 이미 선언된 속성 및 메서드를 추가할 수 있습니다. 이것은 타사 라이브러리의 주변 형식 선언에 유용합니다. 타사 라이브러리에 대한 일부 선언이 없는 경우 동일한 이름으로 인터페이스를 다시 선언하고 새 속성과 메서드를 추가할 수 있습니다.
예:
우리는 새로운 선언을 포함하도록 위의 인터페이스를 확장할 수 있다.
interface Bird {
color: string
eat(): void
}
바로 그거야! 둘 사이의 미묘한 차이에 길을 잃는 것보다 언제 무엇을 사용해야 하는지 기억하는 것이 더 쉽다.
음 'typescriptlang'은 가능한 한 유형보다 인터페이스를 사용할 것을 권장하는 것 같다.
여기 또 다른 차이점이 있습니다. 내가... 이 상황에 대한 이유나 이유를 설명할 수 있다면 맥주를 사세요:
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
관련 문제: 유형에 인덱스 서명이 없습니다(유형 별칭이 아닌 인터페이스에만 해당)
따라서 개체를 인덱싱하려면 이 예를 고려하십시오
이것과 리스코프 원칙 위반에 대해 살펴보세요
평가차이
when is 인터페이스의 결과 유형 보기
/**
* 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";
의 결과 유형을 확인하십시오:
/**
* 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";
컴파일 속도와 관련하여, 합성된 인터페이스는 유형 교차점보다 더 나은 성능을 발휘한다:
[...] 인터페이스는 속성 충돌을 탐지하는 단일 플랫 개체 유형을 생성합니다. 이는 유효 유형에 대해 확인하기 전에 모든 구성요소를 확인하는 교차로 유형과 대조적입니다. 교차 유형과 달리 인터페이스 간 유형 관계도 캐시됩니다.
출처:
는 다음과 같은 대답을 한다:
인터페이스의 거의 모든 기능을 유형으로 사용할 수 있습니다. 핵심적인 차이점은 항상 확장 가능한 인터페이스와 비교하여 새로운 속성을 추가하기 위해 유형을 다시 열 수 없다는 것이다.
2021년 관련
타자기 버전의 경우: 4.3
TLDR;
제 개인적인 관례는 다음과 같습니다:
항상 보다 선호한다.
사용 시기:
- 기본 유형(문자열, 부울, 숫자, bigint, 기호 등)에 대한 별칭을 정의할 때 사용합니다
- 튜플 유형을 정의할 때 사용
- 함수 유형을 정의할 때 사용
- 결합을 정의할 때 사용
- 컴포지션을 통해 객체 유형의 함수를 오버로드하려고 할 때 사용합니다
- 매핑된 유형을 활용해야 할 때 사용합니다
사용 시기:
- 사용할 필요가 없는 모든 개체 유형에 사용(위 참조)
- 선언 병합을 활용할 때 사용합니다.
원시형
와의 가장 쉬운 차이점은 프리미티브의 별칭을 지정하는 데만 사용할 수 있다는 것이다:
type Nullish = null | undefined;
type Fruit = 'apple' | 'pear' | 'orange';
type Num = number | bigint;
이러한 예들 중 어떤 것도 인터페이스로 달성할 수 없다.
💡 기본값에 대한 유형 별칭을 제공할 때는 키워드를 사용합니다.
튜플 종류
튜플은 다음 키워드를 통해서만 입력할 수 있습니다:
type row = [colOne: number, colTwo: string];
💡 Tuples에 대한 유형을 제공할 때 키워드를 사용합니다.
함수 유형
및 키워드를 모두 사용하여 함수를 입력할 수 있습니다:
// via type
type Sum = (x: number, y: number) => number;
// via interface
interface Sum {
(x: number, y: number): number;
}
어느 쪽이든 동일한 효과를 얻을 수 있기 때문에 읽기가 조금 더 쉽고 장황하지 않기 때문에 이러한 시나리오에서 사용하는 것이 규칙입니다.
💡 함수 유형을 정의할 때 사용합니다.
조합 유형
유니온 유형은 다음 키워드를 사용해야 합니다:
type Fruit = 'apple' | 'pear' | 'orange';
type Vegetable = 'broccoli' | 'carrot' | 'lettuce';
// 'apple' | 'pear' | 'orange' | 'broccoli' | 'carrot' | 'lettuce';
type HealthyFoods = Fruit | Vegetable;
💡 조합 유형을 정의할 때 키워드 사용
객체 유형
자바스크립트의 객체는 키/값 맵이며 "오브젝트 타입"은 키/값 맵을 타이핑하는 타입스크립트의 방법이다. 및 둘 다 원래 질문에서 설명한 대로 개체 유형을 제공할 때 사용할 수 있습니다. 그렇다면 객체 유형에 대해 vs를 사용할 때는 언제인가요?
Intersection vs Inheritance
With types and composition, I can do something like this:
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 is totally happy. What about if I tried to extend that with interface:
interface StrAndNumLogger extends NumLogger {
log: (val: string) => void;
};
The declaration of StrAndNumLogger
gives me an error:
Interface 'StrAndNumLogger' incorrectly extends interface 'NumLogger'
With interfaces, the subtypes have to exactly match the types declared in the super type, otherwise TS will throw an error like the one above.
💡 When trying to overload functions in object types, you'll be better off using the type
keyword.
Declaration Merging
The key aspect to interfaces in typescript that distinguish them from types is that they can be extended with new functionality after they've already been declared. A common use case for this feature occurs when you want to extend the types that are exported from a node module. For example, @types/jest
exports types that can be used when working with the jest library. However, jest also allows for extending the main jest
type with new functions. For example, I can add a custom test like this:
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
);
And then I can use it like this:
test.timedTest('this is my custom test', () => {
expect(true).toBe(true);
});
And now the time elapsed for that test will be printed to the console once the test is complete. Great! There's only one problem - typescript has no clue that i've added a timedTest
function, so it'll throw an error in the editor (the code will run fine, but TS will be angry).
To resolve this, I need to tell TS that there's a new type on top of the existing types that are already available from jest. To do that, I can do this:
declare namespace jest {
interface It {
timedTest: (name: string, fn: (mockTrack: Mock) => any, timeout?: number) => void;
}
}
Because of how interfaces work, this type declaration will be merged with the type declarations exported from @types/jest
. So I didn't just re-declare jest.It
; I extended jest.It
with a new function so that TS is now aware of my custom test function.
This type of thing is not possible with the type
keyword. If @types/jest
had declared their types with the type
keyword, I wouldn't have been able to extend those types with my own custom types, and therefore there would have been no good way to make TS happy about my new function. This process that is unique to the interface
keyword is called declaration merging.
Declaration merging is also possible to do locally like this:
interface Person {
name: string;
}
interface Person {
age: number;
}
// no error
const person: Person = {
name: 'Mark',
age: 25
};
If I did the exact same thing above with the type
keyword, I would have gotten an error since types cannot be re-declared/merged. In the real world, JavaScript objects are much like this interface
example; they can be dynamically updated with new fields at runtime.
💡 Because interface declarations can be merged, interfaces more accurately represent the dynamic nature of JavaScript objects than types do, and they should be preferred for that reason.
Mapped object types
With the type
keyword, I can take advantage of mapped types like this:
type Fruit = 'apple' | 'orange' | 'banana';
type FruitCount = {
[key in Fruit]: number;
}
const fruits: FruitCount = {
apple: 2,
orange: 3,
banana: 4
};
This cannot be done with interfaces:
type Fruit = 'apple' | 'orange' | 'banana';
// ERROR:
interface FruitCount {
[key in Fruit]: number;
}
💡 When needing to take advantage of mapped types, use the type
keyword
Performance
Much of the time, a simple type alias to an object type acts very similarly to an interface.
interface Foo { prop: string }
type Bar = { prop: string };
However, and as soon as you need to compose two or more types, you have the option of extending those types with an interface, or intersecting them in a type alias, and that's when the differences start to matter.
Interfaces create a single flat object type that detects property conflicts, which are usually important to resolve! Intersections on the other hand just recursively merge properties, and in some cases produce never. Interfaces also display consistently better, whereas type aliases to intersections can't be displayed in part of other intersections. Type relationships between interfaces are also cached, as opposed to intersection types as a whole. A final noteworthy difference is that when checking against a target intersection type, every constituent is checked before checking against the "effective"/"flattened" type.
For this reason, extending types with interfaces/extends is suggested over creating intersection types.
Also, from the TypeScript documentation
That said, we recommend you use interfaces over type aliases. Specifically, because you will get better error messages. If you hover over the following errors, you can see how TypeScript can provide terser and more focused messages when working with interfaces like Chicken.
More in the typescript wiki.
The key difference pointed out in the documentation is that Interface
can be reopened to add new property but Type alias
cannot be reopened to add new property eg:
This is ok
interface x {
name: string
}
interface x {
age: number
}
this will throw the error Duplicate identifier y
type y = {
name: string
}
type y = {
age: number
}
Asides from that but interface and type alias are similar.
In typescript, "interface" is recommended over "type".
"type" is used for creating
type aliases
. You cannot do this with "interface".type Data=string
Then instead of using string, you can use "Data"
const name:string="Yilmaz" const name:Data="Yilmaz"
Aliases are very useful especially working with generic types.
Declaration Merging:
You can merge interfaces but not types.interface Person { name: string; } interface Person { age: number; } // we have to provide properties in both Person const me: Person = { name: "Yilmaz", age: 30 };
functional programming users use "type", object-oriented programing users choose "interface"
You can’t have computed or calculated properties on interfaces but in type.
type Fullname = "firstname" | "lastname" type Person= { [key in Fullname]: string } const me: Person = { firstname: "Yilmaz", lastname: "Bingol" }
Demonstrate the ability to recursively re-write Object Literal types and interfaces recursively and not class members/properties/functions.
Also how to distinguish and type check differences and workaround to the problem discussed above, when Record<any, string|number> doesn't work due being interfaces and things like that, you work around it. This would allow for simplifications to the following potentially for mongoose types: https://github.com/wesleyolis/mongooseRelationalTypes mongooseRelationalTypes, DeepPopulate, populate
Also, a bunch of another approaches to do advanced type generics and type inference and the quirks around it for speed, all little tricks to get them right from many experiments, of trial and error.
Typescript playground: Click here for all examples in a live play ground
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
Making recursive shapes or keys of specific shape recursive
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]
}
How to apply type constraints, for Record Types, which can validate interfaces like Discriminators:
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
Example of using Discriminators, however, for speed I would rather use literally which defines each key to Record and then have passed to generate the mixed values because use less memory and be faster than this approach.
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;
}
Example of usage:
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) => {
})
Better Approach to discriminate types and Pick Types from a map for narrowing, which typically results in faster performance and less overhead to type manipulation and allow improved caching. compare to the previous example above.
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]
Example of usages:
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.
})
Based on all the discussions I've seen or engaged recently the main difference between types and interfaces is that interfaces can be extended and types can't.
Also if you declare a interface twice they will be merged into a single interface. You can't do it with types.
Differences Between Type Aliases and Interfaces Type aliases and interfaces are very similar, and in many cases you can choose between them freely. Almost all features of an interface are available in type, the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable.
Interface was designed specifically to describe object shapes; however Types are somehow like interfaces that can be used to create new name for any type.
We might say that an Interface can be extended by declaring it more than one time; while types are closed.
https://itnext.io/interfaces-vs-types-in-typescript-cf5758211910
Update 2022 -
Type aliases and interfaces are very similar, and in many cases you can choose between them freely. Almost all features of an interface are available in type, the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable.
Want to add my 2 cents;
I used to be "an interface lover" (preferring interface
to type
except for Unions, Intersections etc)... until I started to use the type "any key-value object" a.k.a Record<string, unknown>
If you type something as "any key-value object":
function foo(data: Record<string, unknown>): void {
for (const [key, value] of Object.entries(data)) {
// whatever
}
}
You might reach an dead end if you use interface
interface IGoo {
iam: string;
}
function getGoo(): IGoo {
return { iam: 'an interface' };
}
const goo = getGoo();
foo(goo); // ERROR
// Argument of type 'IGoo' is not assignable to parameter of type
// 'Record<string, unknown>'.
// Index signature for type 'string' is missing in type
// 'IGoo'.ts(2345)
While type
just works like a charm:
type Hoo = {
iam: string;
};
function getHoo(): Hoo {
return { iam: 'a type' };
}
const hoo = getHoo();
foo(hoo); // works
This particular use case - IMO - makes the difference.
In my daily development, I use this cheatsheet when I don't know which to choose.
For more information, you can read my blog: https://medium.com/@magenta2127/use-which-interface-or-type-alias-in-typescript-bdfaf2e882ae
'개발하자' 카테고리의 다른 글
Write typescript type for given shape (0) | 2023.04.23 |
---|---|
Float로 고정 바닥글로 스크롤 보기를 만드는 방법은 무엇입니까? (0) | 2023.04.22 |
플라우터럭: 카드를 클릭할 수 있게 만드는 방법은 무엇입니까? (0) | 2023.04.21 |
텍스트 파일에서 단어를 검색하고 파이썬으로 전체 줄을 인쇄하는 방법은 무엇입니까? (0) | 2023.04.21 |
데이터() 키가 TypeScript와 함께 작동하도록 만드는 방법은 무엇입니까? (0) | 2023.04.20 |