타입스크립트 심화 기능
제네릭
- 유니온 타입 등을 남발하다보면 결국 any와 다름없어지는 상황이 발생한다.
- C# 및 Java와 같은 언어에서 널리 사용되는 문법
- 대규모 소프트웨어를 구축할 수 있는 가장 유연한 기능을 제공
- 와 같은 꺽쇠 괄호를 사용한다.
- 다형성을 구현 가능하게 한다!
/**
* 제네릭
*/
//이럴 바엔 any가 낫겠어...
//function getInfo(msg: string | number | number[] | string[] ) {
function getInfo(msg: any ) {
return msg
}
console.log(getInfo('Word'))
console.log(getInfo(123))
console.log(getInfo([1, 2]))
console.log(getInfo(['C', 'T']))
제네릭 사용
- Type을 변수처럼 받을 수 있다
/**
* 제네릭 사용
*/
function getInfoUnion(msg: string | number | string[] | number[]): string | number | string[] | number[] {
return msg
}
function getInfoAny(msg: any): any {
return msg
}
// 타입을 변수처럼 받을 수 있다
// function 함수명<타입 변수 지정>(msg: 타입 인자): 타입 반환 {
function getInfo<T>(msg: T): T {
return msg
}
console.log(getInfo<string>('Word'))
console.log(getInfo<number>(123))
console.log(getInfo<number[]>([1, 2]))
console.log(getInfo<string[]>(['C', 'T']))
함수에서의 제네릭
/**
* 함수에서의 제네릭
*/
type TypeGetInfoFn = <T>(msg: T) => T;
interface InterfaceGetInfoFn {
<T>(msg: T): T;
}
// function 함수명<타입 변수 지정>(msg: 타입 인자): 타입 반환 {
function getInfo<T>(msg: T): T {
return msg;
}
const TypeAliases:TypeGetInfoFn = getInfo;
const TypeIntercae:InterfaceGetInfoFn = getInfo;
//튜플이랑 비슷!
function getInfoMultiple<T1, T2>(msg: [T1, T2]): [T1, T2] {
return msg
}
getInfoMultiple<string, number>(['Str', 123])
클래스에서의 제네릭
/**
* 클래스에서의 제네릭
*/
class Animal<T, U> {
name: T
genderType: U
constructor(name: T, genderType: U) {
this.name = name
this.genderType = genderType
}
}
type GenderType = 'M' | 'F'
const dog = new Animal<string, GenderType>('강아지', 'M')
const cat = new Animal<GenderType, string>('F', '고양이')
제네릭 타입 좁히기
- 제네릭 타입은 자칫 잘못하면 any와 다를게 없어질 수 있다
- 이럴 경우 extends 키워드를 이용하여 범위를 좁혀줄 수 있다
/**
* 제네릭 타입 좁히기 (extends)
*/
function printMessage<T extends string | number>(msg: T) {
return msg
}
function printArr<T extends string[] | number[]>(msg: T) {
return msg.toString()
}
printMessage('Hello')
printMessage(123)
// Error!
// printMessage<string[]>(['H', 'E', 'L', 'L', 'O'])
printArr(['H', 'E', 'L', 'L', 'O'])
printArr([1, 2, 3, 4])
// Error!
// printArr<boolean[]>([true, false])
고급타입 다루기
인덱스 타입 다루기
- 객체의 index에도 타입을 지정할 수 있다
- 꼬일 수 있는 상황이 많기 때문에 주의해서 사용해야한다!
/**
* Index 타입 다루기
*/
interface Todo {
id: number
content: string
}
// Indexed Access Type
type TodoId = Todo['id']
interface StrInterface {
[index: number]: string
name: string
age: number
// [index: string]: number
}
const Person: StrInterface = {
99: '구구',
name: '장',
age: 99,
22: '댜',
5345: 'ㄹㅈㄷ'
// genderType: 'M'
}
조건부 타입 다루기
- 타입에 삼항 연산자 형식으로 조건을 걸 수 있다
/**
* 조건부 타입 다루기
*
* SomeType extends OtherType ? TrueType : FalseType;
*/
interface Person {
name: string
}
interface Me extends Person{
age: number
}
// 조건을 걸 수 있다
type Ex1 = Me extends Person ? string : number
class Galaxy {
type = 'AOS'
}
class IPhone {
type = 'IOS'
}
// Generic과 함께 사용하면 편리함
type MyDevice<T> = T extends 'IPhone' ? IPhone : Galaxy
let myPhone: MyDevice<'Galaxy'>
let myPhone2: MyDevice<'IPhone'>
let myPhone3: MyDevice<'Xiaomi'>
type IsNumberType = 123 extends number ? true : false
type IsNumberType2 = number extends 123 ? true : false
const isNumberType: IsNumberType = true
const isNumberType2: IsNumberType2 = false
맵드 타입 다루기
- 기존 타입에서 새로운 다른 타입으로 타입을 정의할 수 있다
- 인덱스 서명이나 조건부 타입과 함께 사용할 수 있다
- in 키워드를 사용한다
/**
* (Mapped) 맵드 타입 다루기
*
* { T in V: T }
*/
// 예시
const newNum = [1, 2, 3].map((num) => num ** 2)
type Person = {
name: string
age: number
gender: string
}
type Dict<T> = {
[key: string]: T
}
const newPerson: Dict<Person> = {}
//새로운 type 생성
newPerson.me
type NewRecord = {
[key in 'you' | 'we']: Person
}
function printNewPerson(newPerson: NewRecord) {
newPerson.we
newPerson.you
newPerson.me //정의되지 않은 타입
}
선언 병합
- 타입스크립트의 특이한 개념
- 컴파일러가 동일한 이름으로 선언된 두 개의 다른 선언을 하나의 정의로 병합한다.
- - 원래의 기능이 모두 포함되며, 여러 선언을 병합할 수 있다.
- TypeScript에서 선언은 네임스페이스, 타입, 또는 값의 세 그룹 중 적어도 하나의 엔터티를 만든다.
- 네임스페이스는 점으로 구분된 표기법을 사용하여 엑세스하는 이름을 포함하는 영역을 생성한다.
선언 타입 | 네임스페이스 | 타입 | 값 |
네임스페이스 | X | X | |
클래스 | X | X | |
열거형 | X | X | |
인터페이스 | X | ||
타입별칭 | X | ||
변수 | X | ||
함수 | X |
/**
* 선언 병합
*/
interface Cat {
name: string
}
interface Cat {
age: number
}
//위의 선언이 병합되었다
const animal: Cat = {
name: 'cat',
age: 99
}
interface Person {
name: string
age: number
}
const Person = {
name: 'Jang',
age: 99
}
//인터페이스까지 포함되어 있다
export {
Person
}
class Car {
static createCar(): Car {
return {
name: 'Car'
}
}
}
namespace Car {
function createCar(): Car {
return Car.createCar()
}
}
interface Car {
name: string
brandType?: string
}
//위의 3개가 다 합쳐진다 - 조심!
export {
Car
}
타입 추론
- 명시적으로 타이핑(Typing - 타입 선언) 하지 않았을 때 TypeScript가 해당 코드의 타입을 해석해 나가는 방식
- const -> 리터럴 타입으로 추론 / let -> 기본 타입으로 추론
일반적인 타입 추론(Best common type)
- 여러 식에서 타입을 유추해 가장 일반적인 타입을 계산
- 각 후보 타입을 고려하고 다른 모든 후보 타입과 호횐되는 타입을 선택하는 알고리즘
문맥상 타이핑(Contextual Typing)
- 간혹 타입 추론이 반대로 동작하는 경우가 있다 -> 이것을 문맥상 타이핑이라고 부른다.
- 문맥상 타입 지정은 표현식 타입이 위치에 의해 암시적인 경우 발생한다.
/**
* 타입 추론
*/
let str = 'string'
const str2 = 'string'
type Obj = {
name: string
}
interface IObj {
name: string
}
const div = document.querySelector('div')
div?.addEventListener('click', function(event: MouseEvent) {
event.mouse
})
타입 단언
- TypeScript가 알 수 없는(추론할 수 없는) 타입에 대한 정보가 있을 때 타입 단언을 사용하여 보다 구체적인 타입을 지정할 수 있다.
- 하지만 컴파일 단계에서 제거되기 때문에 런타임 검사가 생략 된다 -> 타입 단언이 잘못되어도 null이나 예외를 발생시키지 않음
- as 키워드 혹은 <>(JSX가 아닌 경우) 구문으로 사용 가능
- 주로 DOM 요소나 event에 사용
/**
* 타입 단언
*
* 남용하면 any와 마찬가지가 된다
*/
const div = document.querySelector('.ts-wrapper') as HTMLDivElement
// const div2 = <HTMLDivElement>document.querySelector('.ts-wrapper')
const str = 'Hello' as unknown as number
never
- 일부 함수의 반환이 없는 경우를 명시
- void와는 조금 다르다!
- 예외를 던지거나 실행이 종료될 때 사용
/**
* never
*/
function someError(message: string): never {
throw new Error(message)
}
function someFunc(param: string | number) {
if (typeof param === 'string') {
} else if (typeof param === 'number') {
} else {
// some never
}
}
// 유니언 타입에서 제거가 됨
type StrUnion = never | string
// 교차 타입에서는 덮어씀
type NeverInter = never & string
keyof
- 객체 타입의 키를 (문자열 or 숫자) 유니언 타입으로 생성
- 리터럴 집합으로 만들어진 유니언 타입을 만들 때 유용하다.
- 맵드 타입과 사용시에 더욱 유연하게 활용 가능
/**
* keyof 타입 연산자
*/
interface Size {
width: number
height: number
}
type S = keyof Size // = 'width' | 'height'
function getSize(size: S) {
return size
}
getSize(1111)
getSize('width22')
getSize('width')
getSize('height')
const SizeType = {
width: 'Px1',
height: 'Px2'
} as const
type SizeTypeValue = typeof SizeType[keyof typeof SizeType]
빈 값 관리하기
- Non-null Assertion Operator
- (!.) 으로 사용한다.
- 값이 null이 아님을 개발자가 강제로 보장함 -> 에러 발생 가능!
/**
* Non-null Assertion Operator (!.)
* and
* Optional chaining (?.)
*/
let nil = null
let un
function func():void {}
function func2():undefined {
return undefined
}
function func3():null {
return null
}
console.log(func())
console.log(func2())
console.log(func3())
// ---cut---
interface Home {
foods?: string[]
books?: string[]
}
const MyHome:Home = {}
// 값이 있음을 TS에 개발자가 알린다 - 에러 발생 가능!
MyHome.foods!.push('banana')
MyHome.books?.push('Clean Code')
Type Aliases vs Interfaces
- 각자 다른 개념이지만 비슷한 점도 많고 다른 점도 있어 알아둘 것이 많다.
- 둘 다 많이 사용되는 키워드(취향의 차이), 서로 비슷하게 대체도 가능함
- 정답이 있는 것이 아니므로, 팀 & 개인의 규칙이나 선호도에 따라 다른 방식으로 사용 가능!
- 비교표 수시로 찾아서 확인해보자.
- 항상 최신 자료를 확인해야한다!!
/**
* Type Aliases vs Interfaces
*/
interface IAnimal {
name: string
}
interface IBear extends IAnimal {
honey: boolean
}
type TAnimal = {
name: string
}
// interface는 Type Aliases를 확장할 수 있다.
interface IBear2 extends TAnimal {
honey: boolean
}
// Error - extends는 interface만 받을 수 있다
// type TBear extends IAnimal = {}
// type TBear extends TAnimal = {}
type TBear = TAnimal & {
honey: boolean
}
// 위의 중복선언 인터페이스와 선언 병합됨
interface IAnimal {
age: number
}
const AnimalImpl: IAnimal = {
name: 'AnimalImpl',
age: 99
}
// type alias는 중복 선언이 불가능하다 (선언 병합 지원 X)
// type TAnimal = {
// age: number
// }
class Dog implements IAnimal {
name = 'Dog'
age = 99
}
class Dog2 implements TAnimal {
name = 'Dog'
age = 99
}
type CatType = 'City' | 'Forest' & {
meow(): string
}
//기본 타입도 취급 가능
type S = string
type N = number
type B = boolean
// type CatType = 'City' | 'Forest'
// 인터페이스는 객체 위주
// interface CatType {
// 'City' | 'Forest' & {
// meow(): string
// }
// }
class Cats implements CatType {
}
'Dev > JS Family, HTML, CSS' 카테고리의 다른 글
[TypeScript] 타입스크립트 입문(5) - 타입 시스템 (0) | 2022.09.17 |
---|---|
[React] 리액트 심화 - SPA, React Router, 비동기 프로그래밍 (0) | 2022.09.16 |
[TypeScript] 타입스크립트 입문(3) - 인터페이스, 타입 가드, 열거형, 유니온 타입 (0) | 2022.09.12 |
[TypeScript] 타입스크립트 입문 (2) - 기본 타입, 클래스 (0) | 2022.09.09 |
[TypeScript] 타입스크립트 입문(1) (2) | 2022.09.04 |