一.타입 별명
type PersonName = string;
type PhoneNumber = string;
type PhoneBookItem = [PersonName, PhoneNumber];
type PhoneBook = PhoneBookItem[];
let book: PhoneBook = [
['Lily', '1234'],
['Jean', '1234']
];
type 키워드는 기존 타입에 별명을 생성할 수 있어, 그 가독성을 향상시킵니다
인터페이스와 타입 별명
타입은 형식적으로 인터페이스와 조금 비슷하며, 둘 다 타입 파라미터를 서포트하고, 자신을 인용할 수 있습니다. 예를 들어:
type Tree<T> = {
value: T;
left: Tree<T>;
right: Tree<T>;
}
interface ITree<T> {
value: T;
left: ITree<T>;
right: ITree<T>;
}
그러나 본질적인 차이가 존재합니다:
-
타입 별명은 새로운 타입을 생성하지 않으며, 인터페이스는 새로운 타입을 정의
-
임의의 타입에 별명을 붙이는 것은 허가되지만, 임의의 타입에 그것과 동등한 인터페이스를 정의하는 것은 불가능 (예를 들어 기초 타입)
-
타입 별명을 상속 또는 구현할 수 없지만 (다른 타입을 확장 또는 구현할 수도 없음), 인터페이스는 가능
-
타입 별명은 여러 개의 타입을 조합하여 이름 붙은 타입으로 만들 수 있지만, 인터페이스는 이 종류의 조합 (교차, 연합 등) 을 기술할 수 없음
// 타입 조합, 인터페이스는 이 타입을 표현할 수 없음
type LinkedList<T> = T & { next: LinkedList<T> };
interface Person {
name: string;
}
function findSomeone(people: LinkedList<Person>, name: string) {
people.name;
people.next.name;
people.next.next.name;
people.next.next.next.name;
}
应用场景上,二���区别如下:
-
인터페이스: OOP 씬 (상속과 구현이 가능하고, 타입 계층 관계를 유지)
-
타입 별명: 가독성을 추구하는 씬, 인터페이스가 기술할 수 없는 씬 (기초 타입, 교차 타입, 연합 타입 등)
二.리터럴 타입
2 종류의 리터럴 타입이 존재합니다: 문자열 리터럴 타입과 수치 리터럴 타입
문자열
문자열 리터럴도 타입 의미를 가집니다. 예를 들어:
let x: 'string';
// 오류 Type '"a"' is not assignable to type '"string"'.
x = 'a';
// 正确
x = 'string';
열거의 효과를 시뮬레이션하는 데 사용할 수 있습니다:
type Easing = 'ease-in' | 'ease-out' | 'ease-in-out';
class UIElement {
animate(dx: number, dy: number, easing: Easing) {
if (easing === 'ease-in') {}
else if (easing === 'ease-out') {}
else {
// 자동으로"ease-in-out"타입으로 축窄
}
}
}
// 오류 Argument of type '"linear"' is not assignable to parameter of type 'Easing'.
new UIElement().animate(0, 0, 'linear');
서로 다른 문자열 리터럴은 서로 다른 구체적인 타입에 속합니다. 따라서, (필요하다면) 이렇게 오버로드할 수 있습니다:
function createElement(tagName: "img"): HTMLImageElement;
function createElement(tagName: "input"): HTMLInputElement;
function createElement(tagName: string): Element {
return document.createElement(tagName);
}
수치
수치 리터럴도 마찬가지로 타입 의미를 가집니다:
// 주사위의 6 개 점수를 반환
function rollDice(): 1 | 2 | 3 | 4 | 5 | 6 {
// ...
}
단순히 익명 수치 열거 처럼 보이며, 존재 필요성이 없어 보입니다
존재 의의
실제로, 리터럴 타입의 의의는 컴파일 시에 타입 정보를 결합하여「추론」할 수 있다는 것입니다. 예를 들어:
function foo(x: number) {
// 오류 This condition will always return 'true' since the types '1' and '2' have no overlap.
if (x !== 1 || x !== 2) { }
}
function bar(x: string) {
// 오류 This condition will always return 'false' since the types '"1"' and '"2"' have no overlap.
if (x === '1' && x === '2') {
//...
}
}
이 타입 완전성 보충으로 인해, TypeScript 는 코드의 의미를 더욱 세밀하게「이해」(정적 분석) 할 수 있으며,进而에 몇 가지 그다지 직접적이지 않은 잠재적인 문제를 발견할 수 있습니다
Nevertheless, by pairing a type with it's unique inhabitant, singleton types bridge the gap between types and values.
三.열거와 리터럴 타입
연합 열거 라는 특수한 열거가 있다는 것을 알고 있습니다. 그 멤버도 타입 의미를 가집니다. 예를 들어:
// 연합 열거
enum E {
Foo,
Bar,
}
// 열거의 타입 의미
function f(x: E) {
// 오류 This condition will always return 'true' since the types 'E.Foo' and 'E.Bar' have no overlap.
if (x !== E.Foo || x !== E.Bar) {
//...
}
}
이것은 리터럴 타입 중의 예와 매우 비슷합니다:
function f(x: 'Foo' | 'Bar') {
// 오류 This condition will always return 'true' since the types '"Foo"' and '"Bar"' have no overlap.
if (x !== 'Foo' || x !== 'Bar') {
//...
}
}
P.S. 類比起見, 여기서 문자열 리터럴 연합 타입 ('Foo' | 'Bar') 으로 열거 E 를 시뮬레이션합니다. 실제로는 열거 E 는 수치 리터럴 연합 타입 (0 | 1) 과 동등합니다. 상세는 二。수치 열거 참조
타입 각도에서 보면, 연합 열거는 수치/문자열 리터럴로 구성된 열거입니다. 따라서 그 멤버도 타입 의미를 가집니다. 명칭 상에도 이 관계를 표현하고 있습니다: 연합 열거, 즉 수치/문자열 연합
P.S. 열거 멤버 타입과 수치/문자열 리터럴 타입은*단례 타입 (singleton types)*이라고도 불립니다:
Singleton types, types which have a unique inhabitant.
즉, 1 개의 단례 타입 아래에는 1 개의 값만 있습니다. 예를 들어 문자열 리터럴 타입 'Foo' 는 문자열 'Foo' 만 취값할 수 있습니다
四.식별 가능 연합
단례 타입, 연합 타입, 타입 가드, 타입 별명을 결합하여 일종의 패턴을建立할 수 있습니다. 식별 가능 연합 (discriminated unions) 이라고 불립니다
P.S. 식별 가능 연합은 태그 연합 (tagged unions) 또는 [대수 데이터 타입 (algebraic data types)](/articles/타입-haskell 노트 3/#articleHeader6) 이라고도 불립니다. 즉 연산 가능하고, 논리 추론이 가능한 타입
구체적으로는, 식별 가능 연합은 일반적으로 3 부분을 포함합니다:
- 몇 개의 공공 단례 속성을 가진 타입——공공 단례 속성은 식별 가능한 특징 (또는 태그라고 부름)
- 이러한 타입으로 구성된 연합을 가리키는 타입 별명——즉 연합
- 공공 속성에 대한 타입 가드
공공 단례 속성의 타입을 구별하여 부모 타입을 축窄합니다. 예를 들어:
// 1. 몇 개의 공공 단례 속성 (kind) 을 가진 타입
interface Square {
kind: "square";
size: number;
}
interface Circle {
kind: "circle";
radius: number;
}
// 2. 연합 타입을 정의하고, 별명을 붙임
type Shape = Square | Circle;
// 3. 구체적으로 사용 (타입 가드)
function area(s: Shape) {
switch (s.kind) {
// 자동으로 Square 로 축窄
case "square": return s.size * s.size;
// 자동으로 Circle 로 축窄
case "circle": return Math.PI * s.radius ** 2;
}
}
[instanceof 타입 가드](/articles/조합 타입과 타입 가드-typescript 노트 9/#articleHeader6) 에 대한 일종의 보충입니다. 둘 다 복잡한 타입의 호환 관계를 검출하는 데 사용됩니다. 차이는 다음과 같습니다:
-
instanceof 타입 가드: 명확한 상속 관계를 가진 부모 자식 타입에 적용
-
식별 가능 연합 타입 가드: 명확한 상속 관계가 없는 (런타임에
instanceof로 상속 관계를 검출할 수 없는) 부모 자식 타입에 적용
완전성 체크
때때로 연합 타입의 모든 구성 타입을 완전히 커버하고 싶은 경우가 있습니다. 예를 들어:
type Shape = Square | Circle;
function area(s: Shape) {
switch (s.kind) {
case "square": return s.size * s.size;
// 잠재 문제:"circle"을 누락
}
}
never 타입을 통해 이 종류의 보장을 실현할 수 있습니다 (Never 타입이 몇少ない应用场景 중 하나):
function assertNever(x: never) {
throw new Error("Unexpected object: " + x);
}
function area(s: Shape) {
switch (s.kind) {
case "square": return s.size * s.size;
// case "circle": return s.radius * s.radius;
// 오류 Argument of type 'Circle' is not assignable to parameter of type 'never'.
default: return assertNever(s);
}
}
완전히 커버하지 않은 경우, default 분기에 진행하여 s: Shape 를 x: never 에 전달하여 타입 오류를 발생시킵니다 (완전히 커버한 경우, default 는 도달 불가능 분기로, never 오류를 발생시키지 않습니다). 완전성 커버 요구를 만족시킬 수 있지만, 추가로 assertNever 함수를 정의해야 합니다
P.S. Never 타입에 대한 상세 정보는, [기본 타입_TypeScript 노트 2](/articles/기본 타입-typescript 노트 2/#articleHeader3) 참조
此外,还有一种不那么准确,但也有助于检查完整性的方法:开启 --strictNullChecks 选项,并标明函数返回值。利用默认返回 undefined 来保证完整性,例如:
// 오류 Function lacks ending return statement and return type does not include 'undefined'.
function area(s: Shape): number {
switch (s.kind) {
case "square": return s.size * s.size;
}
}
실질적으로는 비공 반환값 검출로, assertNever 처럼 switch 입자에 정확하지 않으며, 비교적 취약합니다 (디폴트 반환값이 있거나, 여러 개의 switch 가 있는 경우, 완전성 체크를 파괴)
아직 댓글이 없습니다