목차
2.13 객체 간에 대입할 수 있는지 확인하는 법을 배우자
: 어떤 객체가 더 넓은(추상적인)가만 확인하자
-> 좁은 객체가 넓은 객체에 대입 가능
잉여 속성 검사가 이루어 지지 않는 변수를 대입할 때, 가능 여부를 따져봐야함
interface A {
name : string;
}
interface B {
name : string;
age : number;
}
const aObj = {
name : 'zero'
}
const bObj = {
name : 'nero',
age : 32,
}
const aToA : A = aObj;
const bToA : A = bObj;
const aToB : B = aObj;
const bToB : B = bObj;
- A타입이 B타입보다 넓은, 추상적인 타입
- B타입은 더 좁은, 구체적인 타입 : name과 age 속성이 꼭 있어야함
- 집합 관계로 보면,
- {name:string} & {age:number} -> 교집합 관계에 따라 {name:string, age : number}
합집합은? {name:string} | {age:number} 이면
- {name:string, age : number} 또는 {name:string} 또는 {age:number}에 대입 가능? --> 다 불가능
- 합집합은 각각의 집합이나 교집합보다 넓기 때문
interface A {
name : string;
}
interface B {
age : number;
}
function test() : A|B {
if (Math.random() > 0.5){
return {
age : 28,
}
}
return {
name : 'zero'
}
}
const target1 : A & B = test()
const target2 : A = test()
const target3 : B = test()
- 아래의 3줄의 코드 모두에서 에러
- 더 좁은 타입의 변수에 합집합 대입 시도
배열과 튜플
let a : ['hi', 'readonly'] = ['hi', 'readonly'];
let b : string[] = ['hi', 'normal'];
a=b;
b=a;
- 튜플 < 배열
- 따라서 튜플 a는 배열 b에 대입 가능
- 반대는 위와 같이 오류
배열과 튜플에 readonly가 붙으면?
let a : readonly string[] = ['hi', 'readonly'];
let b : string[] = ['hi', 'normal'];
a=b;
b=a;
- readonly가 붙은 배열은 더 넓은 타입
- 따라서 readonly가 붙어 넓어진 a가 일반 배열 b에 들어갈 수 없음
readonly 튜플과 일반 배열은?
let a : readonly ['hi', 'readonly'] = ['hi', 'readonly'];
let b : string[] = ['hi', 'normal'];
a=b;
b=a;
- a = b --> 배열이 튜플보다 넓은 타입이므로 에러
- b = a --> 위처럼 튜플이 배열보다 좁은 타입이긴하지만, readonly가 붙으면 일반 배열보다 넓은 타입
--> 두 경우 모두에서 에러
속성이 동일한 두 객체, 하지만 한 객체는 옵셔널인 경우? -> 옵셔널 객체가 더 넓은 객체
type Optional = {
a? : string;
b? : string;
};
type Mandatory = {
a : string;
b:string;
};
const o : Optional = {
a : 'hello',
};
const m : Mandatory = {
a:'hello',
b:'world',
};
const o2 : Optional = m
const m2 : Mandatory = o
- 옵셔널 : 기존 타입에 undefined가 유니언된 것
- 일반 객체에 옵셔널 객테를 대입할 경우 에러 확인
배열과 다르게 객체에 readonly는 서로 대입 가능
type ReadOnly = {
readonly a : string;
readonly b : string;
};
type Mandatory = {
a : string;
b:string;
};
const o : ReadOnly = {
a : 'hello',
b:'world',
};
const m : Mandatory = {
a:'hello',
b:'world',
};
const o2 : ReadOnly = m
const m2 : Mandatory = o
- 위와 같이 해도 오류 없음
2.13.1 구조적 타이핑 (structural typing)
: 객체를 어떻게 만들었든(인터페이스의 이름이 달라도), 구조가 같으면 같은 객체로 인식하는 것
interface Money {
amount : number;
unit : string;
}
interface Liter {
amount : number;
unit : string;
}
const liter : Liter = { amount : 1, unit : 'liter'};
const circle : Money = liter;
- 두 인터페이스는 이름을 제외하고 모두 같음 -> 같은 타입으로 인식
2.13의 첫번째 코드도 구조적 타이핑임
interface A {
name : string;
}
interface B {
name : string;
age : number;
}
const aObj = {
name : 'zero'
}
const bObj = {
name : 'nero',
age : 32,
}
const aToA : A = aObj;
const bToA : A = bObj;
const aToB : B = aObj;
const bToB : B = bObj;
- 인터페이스 B는 A의 모든 조건을 충족
- B는 구조적 타이핑 관점에서 A 인터페이스임
- 반대의 경우 A는 age 속성이 없으므로 B가 아님
즉, 완전히 구조가 같아야만 동일한 것 아님, B가 A라고 해서 A가 B인 것도 아님
매핑된 객체 타입의 경우에도 구조적 타이핑 특성 O
type Arr = number[];
type CopyArr = {
[key in keyof Arr] : Arr[key];
}
const copyArr : CopyArr = [1,3,9];
- CopyArr은 객체 타입임에도 숫자 배열이 대입 가능 = CopyArr 타입에 존재하는 속성들을 숫자 배열이 갖고 있다! -> 구조적 동일
더 간단한 코드 예
type SimpleArr = { [key : number] : number, length : number};
const simpleArr : SimpleArr = [1,2,3]
- 숫자 배열은 SimpleArr 객체 타입에 속성을 갖고 있음
- 숫자 배열은 구조적으로 SimpleArr
서로 대입하지 못하게 하는 방법 (구분하는 방법)
interface Money {
__type : 'money';
amount : number;
unit : string;
}
interface Liter {
__type : 'liter';
amount : number;
unit : string;
}
const liter : Liter = {amount : 1, unit:'liter', __type: 'liter'}
const circle : Money = liter;
- __type과 같이 구별할 수 있는 속성 추가
- 속성 이름이 꼭 __type이 아니어도 됨
- __type같은 속성을 브랜드 속성이라함
- 브랜딩 : 브랜드 속성을 사용하는 것
2.14 제네릭으로 타입을 함수처럼 사용하자
: <>을 사용해 타입 간의 중복을 제거
js에서 함수를 사용해 중복을 제거한 예시
const personFactory = (name, age) ({
type : 'human',
race : 'yellow',
name,
age,
})
const person1 = personFactory('zero', 28);
const person2 = personFactory('nero', 32);
- js의 함수처럼 타입스크립트에서도 중복을 제거할 수 있음
interface Person<N, A> {
type: 'human',
race : 'yellow',
name : N,
age : A,
}
interface Zero extends Person<'zero',28>{}
interface Nero extends Person<'nero',32>{}
- 제네릭 표기 : <>
- <> 안에 타입 매개변수(type parameter) 넣음
- 선언한 제네릭 사용 : Person<'zero',28>처럼 매개변수에 대응하는 실제 타입 인수(type argument) 삽입
배열 선언 : Array<string>
- Array의 타입이 아래 코트 꼴로 선언돼 있기 때문에 Array<string> 형태로 선언 가능
interface Array<T> {
[key : number] : T,
length : number,
//기타 속성들
}
타입 매개변수의 개수 = 타입 인수의 개수
- 일치하지 않는 경우 에러
인터페이스뿐만 아니라 클래스와 타입 별칭, 함수도 제네릭 가능
- 타입 별칭
type Person<N, A> {
type: 'human',
race : 'yellow',
name : N,
age : A,
}
type Zero = Person<'zero', 28>;
type Nero = Person<'nero', 28>;
- 클래스
class Person<N, A> {
name: N;
age : A;
constructor (name: N,age : A){
this.name = name;
this.age = age
}
}
- 함수
- 선언문이나 표현식이냐에 따라 제네릭 표기 위치가 다름
const personFactoryE = <N, A> (name: N,age : A) => (
{type : 'human',
race : ' yellow',
name,
age,}
);
function personFactoryD<N, A> (name: N,age : A) {
return ({type : 'human',
race : ' yellow',
name,
age,})
};
type과 interface 간에 교차 사용도 가능
interface IPerson<N, A> {
type : 'human',
race : ' yellow',
name : N,
age : A,
}
type TPerson<N, A>= {
type : 'human',
race : ' yellow',
name : N,
age : A,
}
type zero = IPerson<'zero', 28>;
interface Nero extends TPerson<'nero', 32>{}
타입 매개변수에 기본값(default) 넣어주기
interface Person<N=string, A=number> {
type : 'human',
race : ' yellow',
name : N,
age : A,
}
type Person1 = Person;
type Person2 = Person<number>;
type Person3 = Person<number, boolean>;
추론으로 타입을 알 수도 있음
interface Person<N=string, A=number> {
type : 'human',
race : 'yellow',
name : N,
age : A,
}
const personFactoryE = <N,A = unknown>(name: N, age : A) : Person<N,A> => ({
type : 'human',
race : 'yellow',
name,
age,
});
const zero = personFactoryE('zero', 28);
- 넣은 인수의 타입으로 추측
--> 실제로 직접 넣지 않는 경우가 더 많음
상수 타입 매개변수
function values<T>(inital : T[]) {
return {
hasValue(value : T) {return inital.includes(value)}
};
}
const savedValues = values(["a","b","c"])
savedValues.hasValue("x");
- T = ["a","b","c"]
- ["a","b","c"]는 string[]이기 때문에 T는 string
- "x" 전달 가능 이유
상수 타입을 이용해 "a"|"b"|"c" 같이 요소의 유니온으로 추론되게 만들기
function values<const T>(inital : T[]) {
return {
hasValue(value : T) {return inital.includes(value)}
};
}
const savedValues = values(["a","b","c"])
savedValues.hasValue("x");
- 타입 매개변수 앞에 const 추가
2.14.1 제네릭에 제약 걸기
: extents 문법으로 사용 (상속과 사용법이 다름)
interface Example <A extends number, B = string>{
a: A,
b: B,
}
type Usecase1 = Example<string, boolean>;
type Usecase2 = Example<1, boolean>;
type Usecase3 = Example<number>;
- 타입 매개변수 A는 숫자 타입이어야 한다는 의미
- 따라서 string을 넣으면 오류
- number보다 구체적인 1 리터럴 타입
- 제약보가 더 구체적인 타입은 입력 가능
하나의 타입 매개변수가 다른 타입 매개변수의 제약이 될 수 있음
interface Example <A , B extends A>{
a: A,
b: B,
}
type Usecase1 = Example<string, number>;
type Usecase2 = Example<string, 'hello'>;
type Usecase3 = Example<number, 123>;
- A의 타입에 따라 B의 타입이 구체적으로 정해짐
타입 매개변수 != 제약
interface V0 {
value : any;
}
const returnV0 = <T extends V0>() : T => {
return {value : 'test'};
}
- T는 정확히 V0가 아님
- T는 V0에 대입할 수 있는 모드 타입
- 따라서 예를 들어 {value : string, another: string}과 같은 좁은 범위의 타입도 T가 될 수 있음
function onlyBoolean< T extends boolean> (arg : T = false) : T {
return arg;
}
- T가 never도 될 수있기 때문에 에러
해결 : 제네릭 제거
function onlyBoolean (arg : true | false = true) : true | false {
return arg;
}
2.15 조건문과 비슷한 컨디션널 타입이 있다
: 조건에 따라 다른 타입이 되는 타입(conditional type)
type A1 = string;
type B1 = A1 extends string ? number : boolean; // type B1 = number
type A2 = number;
type B2 = A2 extends string ? number : boolean; // type B2 = boolean
- express는 삼항연산자(?)와 같이 사용
- 특정 타입이 다른 타입의 부분집합일 때 참
명시적으로 extends하지 않아도 됨
interface X {
x : number
}
interface XY {
x : number;
y : number;
}
interface YX extends X {
y : number
}
type A = XY extends X ? string : number; //type A = string
type B = YX extends X ? string : number; //type B = string
- XY는 명시적으로 extends하지 않았음에도 A도 string
- XY 타입이 X에 대입 가능하기 때문
타입 검사로 이용
type Result1 = 'hi' extends string ? true : false; //type Result1 = true
type Result2 = [1] extends [string] ? true : false; //type Result2 = false
- [1] 은 [number] or number[]
never와 함께 사용
type Start =string | number;
type New = Start extends string | number? Start[] : never; //Start[]
let n: New = ['hi'];
n = [123];
- 위 코는 그냥 그냥 type New = Start[]라 하면됨
- 보통은 제네릭과 쓸 때 never가 의미 있음
type ChooseArray<A> = A extends string ? string[] : never;
type StringArray = ChooseArray<string>; //string[]
type Never = ChooseArray<number>; //never
- never은 모든 타입 대입 가능 -> 모든 타입을 extends 가능
type Result = never extends string ? true : false; //true
- 키가 never이면 제거됨 -> 컨디셔널 타입과 같이 사용
type OmitByType<O,T> = {
[K in keyof O as O[K] extends T? never:K] : O[K];
};
type Result = OmitByType<{
name : string,
age : number,
married : boolean,
rich : boolean,
}, boolean>
- OmitByType 타입 : 특정 타입인 속성을 제거하는 타입
- 예제 -> 불린타입 속성 제거
- 키가 never이면 해당 속성은 제거됨
중첩
type ChooseArray<A> = A extends string ? string[] : A extends boolean ? boolean[] : never;
type StringArray = ChooseArray<string>;
type BooleanArray = ChooseArray<boolean> //boolean[]
type Never = ChooseArray<number> //never
인덱스 접근 타입으로 컨디셔널 표현하기
type A1 = string;
type B1 = A1 extends string ? number : boolean; // number
type B2 = { // number
't' : number;
'f' : boolean;
} [A1 extends string ? 't' : 'f']
2.15.1 컨디셔널 타입 분배법칙
- string | number 타입이 있는데, string[]을 얻고 싶은 경우 => 컨디셔널 + 제네릭
type Start = string | number;
type Result<Key> = Key extends string ? Key[] : never;
let n : Result<Start> = ['hi']; //string[]
- 검사하려는 타입이 제네릭이면서 유니언이면 분배법칙 실행
- Result<string | number> = Result<string> | Result<number>
- string[] | never -> string[]
(주의) 불린 타입과 사용할 경우
type Start = string | number | boolean;
type Result<Key> = Key extends string|boolean ? Key[] : never;
let n : Result<Start> = ['hi']; //string[] | false[] | true[]
n=[true]
- boolean을 false or true로 인식
분배법칙 막기
type IsString<T> = T extends string? true : false;
type Result = IsString<'hi' | 3> // boolean
- false 아닌 boolean이 나옴 <- 분배법칙 때문
type IsString<T> = [T] extends [string]? true : false;
type Result = IsString<'hi' | 3> // false
- 배열로 제네릭 감싸면 분배법칙 X
never도 분배법칙 대상
- 유니언(아니지만)으로 생각하는게 좋음
type R<T> = T extends string ? true : false;
type RR = R<never> // never
- never가 되면서 분배법칙
- never는 공집합 -> 공집합에 분배법칙 ? 아무것도 실행하지 않는 것 => 결과 never
type IsNever<T> = [T] extends string ? true : false;
type T = IsNever<never> // true
type F = IsNever<'never'> // false
- []로 막아서 해결
제네릭과 컨디셔널을 같이 사용할 경우 조심할 사항
function test<T>(a:T) {
type R<T> = T extends string? T: T;
const b: R<T> = a; //오류 : Type 'T' is not assignable to type 'R<T>'. 대입불가
}
- 예상 : R<T>는 T타입이 될것 -> b는 R<T>니까 얘도 T-> 그럼 a 대입 가능
- 문제 : R<T>가 T타입이 될거란 생각
- 제네릭이 들어있는 컨디셔널 타입을 판단할 때 값을 판단을 뒤로 미룸
- 변수 b에 매개변수 a 대입할때까지 판단 X
- 해결 : 배열로 감싸기
function test<T extends ([T] extends [string] ? string : never)>(a:T) {
type R<T> = [T] extends string? T: T;
const b: R<T> = a; //오류 : Type 'T' is not assignable to type 'R<T>'.
}
2.16 함수와 메서드를 타이핑하자
매개변수에 옵셔널
function example(a:string, b?: number, c = false) {}
example('hi', 123, true);
example('hi', 123);
example('hi');
- 기본값이 제공된 경우 자동으로 옵셔널
...문법 : 나머지 매개변수 문법
function example1(a: string, ...b: number[]){}
example1('hi',123,4,56) //b=[123,4,56]
function example2(...a: string[], b: number)
- example2 오류
- 매개변수의 마지막자리에만 ...문법 위치
매개변수 자리에 전개문법
function example3(...args : [number, string, boolean]){}
example3(1,'123',false)
function example4(...args : [a : number, b: string, c: boolean]) {}
- example3 매개변수의 이름을 자동 할당
- example4 이름을 직접 정한 방법
구조분해 할당
function destructuring ({prop : {nested}}) {}
destructuring ({prop : {nested : 'hi'}})
- 에러 발생으로 타이핑 필요
function destructuring ({prop : {nested : string}}) {}
destructuring ({prop : {nested : 'hi'}})
- 이렇게 하면 타입 표기한게 아닌 nested 속성을 string 변수로 이름을 바꾼 것
function destructuring ({prop : {nested}} : {prop : {nested : string}}) {}
destructuring ({prop : {nested : 'hi'}})
- 위와 같이 표기
this를 사용하는 경우 -> 명시적 표기 / 안하면 any 추론 및 에러 발생
function example1(){
console.log(this) //any 타입 에러
}
function example2(this : Window) {
console.log(this) //this:Window
}
function example3(this : Document, a : string, b:'this'){}
example3('hello', 'this') //this가 Document 타입일 수 없음
example3.call(document, 'hello', 'this')
- this는 매개변수의 첫번째 자리에 -> 다른 매개변수는 한 자리씩 밀림
- this는 실제 매개변수가 아님
- call 메서드 등을 이용해 this 값을 명시적으로 지정
메서드에서 this 사용
- this가 바뀔 수 있을 때 명시적으로 타이핑
type Animal = {
age : number;
type : 'dog'
};
const person = {
name: 'zero',
age : 28,
sayName() {
this;
this.name; //(property) name : string
},
sayAge(this: Animal) {
this; //this : Animal
this.type; //(property) type : "dog"
}
};
person.sayAge.bind({age : 3,type : 'dog' })
타입 스크립트에서는 함수를 생성자로 사용 불가/ 대신 class 사용
- 강제로 만들 수 있지만, 부자연스러운 방법 (굳이 정리 X)
2.17 같은 이름의 함수를 여러 번 선언할 수 있다
두 문자를 합치거나 두 숫자를 더하는 함수
function add (x: string | number,y: string | number) : string| number {
return x + y
}
add (1,2) //3
add('1','2')//12
add(1, '2') //12 원하지 않는데 됨
add('1', 2) //12 원하지 않는데 됨
- x+y 오류
- 다른 타입 간의 +는 원하지 않는데 됨
(해결) 오버로딩 : 호출할 수 있는 함수의 타입을 미리 여러 개 타이핑하는 기법
function add (x:number,y: number) : number
function add (x: string, y: string) : string
function add (x: any,y: any) : string| number {
return x + y
}
add (1,2) //3
add('1','2')//12
add(1, '2') //12 원하지 않는데 됨
add('1', 2) //12 원하지 않는데 됨
- any를 명시적으로 사용하는 처음이자 마지막 사례
- 사용되는 건 아님
- 오류 : 두 오버로딩 중 어디에도 해당 X
- of 뒤의 숫자가 타입 스크립트가 인식하는 오버로딩의 개수
선언 순서도 타입 추론에 영향
function example (x: string) : string
function example (x: string| null) : number
function example (x: string| null) : string| number {
if ( x ) {
return 'string'
} else {
return 123;
}
}
const result = example('what'); // const result = string
- what은 스트링으로 첫번째와 두번째 오버로딩 모두에 해당
- 여러 오버로딩에 해당되는 경우 제일 먼저 선언된 오버로딩에 해당 -> string
- 순서 바꾸면
function example (x: string| null) : number
function example (x: string) : string
function example (x: string| null) : string| number {
if ( x ) {
return 'string'
} else {
return 123;
}
}
const result = example('what'); //number
- number로 타입 변화
- 오버로딩 순서
- 좁은 타입부터 넓은 타입 순으로
인터페이스 오버로딩
interface Add {
(x: number, y: number) : number;
(x: string, y: string) : string;
}
const add : Add = (x: any, y: any) => x + y;
add(1,2) //3
add('1','2') //12
add(1,'2') //해당 오버로딩 없음 오류
add('1',2) //해당 오버로딩 없음 오류
타입 별칭으로 오버로딩 표현
- 각각의 함수 타입을 선언한 뒤 & 연산자로 하나로 묶음
type Add1 = (x: number, y: number) => number;
type Add2 = (x: string, y: string) => string;
type Add = Add1 & Add2;
const add: Add = (x: any, y:any) => x+y
add(1,2) //3
add('1','2') //12
add(1,'2') //해당 오버로딩 없음 오류
add('1',2) //해당 오버로딩 없음 오류
(주의) 지나친 오버로딩 활용, 유니언이나 옵셔널 매개변수를 활용할 수 있는 경우는 오버로딩 사용 X
'typescript' 카테고리의 다른 글
2.23 ~ 2.27 (0) | 2024.10.13 |
---|---|
2.18 ~ 2.22 (1) | 2024.10.06 |
2.8 ~ 2.12 (별칭, 인터페이스, 객체 속성, 집합, 상속) (0) | 2024.09.20 |
기본 문법 익히기 (2.7) - 타입스크립트의 타입 (1) | 2024.09.20 |
기본 문법 익히기 (2.4 ~ 2.6) (1) | 2024.09.13 |