IT/공부 정리

TypeScript

rinix_x 2023. 9. 25. 10:08
반응형

타입스크립트란, 마이크로소프트에서 구현한 JavaScript의 슈퍼셋(Superset) 프로그래밍 언어. 확장자로는 .ts를 사용[1]하며, 컴파일의 결과물로 JavaScript 코드를 출력한다. 최종적으로 런타임에서는 이렇게 출력된 JavaScript 코드를 구동시키게 된다.

 

타입스크립트를 사용하려면 npm이 필요하다. Node.js를 설치하면 npm이 자동으로 설치된다. npm을 설치하고 나면 터미널이나 명령 프롬프트에서

npm i -g typescript

를 입력하면 타입스크립트 컴파일러(타입스크립트 → 자바스크립트 변환기)가 설치된다.

 

타입스크립트에선 함수를 객체처럼 사용할 수 있어. 함수를 변수에 할당하거나, 함수를 반환하거나 하는 등의 작업을 할 수 있다. => 자바스크립트에서 함수는 일급 객체다.

function add(a:number,b:number){
	return a + b
}

보통 타입스크립트에서 함수를 선언할때 매개변수 타입은 지정하고, 반환 타입은 지정하지 않는다.

반환 타입은 타입스크립트가 충분히 추론할 수 있기 때문에 굳이 개발자가 지정할 필요가 없다.

지정하고자 한다면, 아래와 같이 타입 지정 또한 가능하다.

// 매개변수 뒤에 타입을 선언하여 반환 타입을 지정할 수 있다.
function add(a:number, b:number):number {
	return a + b
}

 

자바스크립트에서 함수를 선언할때 함수 표현식, 함수 선언식, 화살표 함수 등을 타입스크립트에서도 동일하게 지원한다.

// 함수 표현식
function greet(name: string){
	return 'hello ' + name
}

// 함수 표현식
const greet2 = function(name: string){
	return 'hello ' + name
}

// 화살표 함수 표현식
const greet3 = (name: string) => {
	return 'hello ' + name
}

// 단축형 화살표 함수 표현식
const greet4 = (name: string) => 'hello ' + name

// 함수 생성자
const greet5 = new Function('name', 'return "hello " + name')

 

선택적 매개변수

타입 선언 시 선택적 타입 지정할 수 있는 것과 같이 함수의 매개변수 또한 선택적 매개변수로 지정할 수 있다.

선택 타입과 같이 매개변수 뒤에 “?”를 붙여주면 선택 매개변수로 지정할 수 있다.

주의할 점은 선택적 매개변수는 필수 매개변수 뒤에 작성해야한다.

function log(message: string, userId?: string ){
	let time = new Date().toLocaleTimeString()
	console.log(time,message,userId || 'Not signed in')
}

log('Page loaded') // 오후 9:40:15 Page loaded Not signed in

log('User signed in','ellen') // 오후 9:40:15 User signed in ellen

선택적 매개변수는 위와 같이 ? 를 사용하여 지정할 수도 있지만, 자바스크립트의 매개변수 기본값을 설정하는 기능을 이용하여 선택적 매개변수로 만들 수 있다.

function log(message: string, userId = 'Not signed in' ){
	let time = new Date().toLocaleTimeString()
	console.log(time,message,userId)
}

log('Page loaded') // 오후 9:40:15 Page loaded Not signed in

log('User signed in','ellen') // 오후 9:40:15 User signed in ellen

위와 같이 기본값을 지정하여 선택적 매개변수로 작성하면 ?를 사용한 방식 보다 간결하고, 가독성을 높일 수 있다.

매개변수의 기본값을 지정하는 경우 매개변수의 작성 순서가 중요하지 않으며, 타입스크립트가 기본값으로 지정한 값을 기반으로 해당 매개변수의 타입을 추론하기 때문에 선택적 매개변수에 대한 타입 지정 또한 걱정하지 않아도 된다.

 

호출 시그니처

타입스크립트는 함수 전체에 대한 타입도 지정할 수 있다.

변수에 string 이라고 타입을 지정할 수 있듯이, 함수도 마찬가지로 타입을 지정해줄 수 있다. 이러한 함수의 타입을 “호출 시그니처” 또는 “타입 시그니처” 라고 한다.

// 단축형 호출 시그니처
type Sum = ( a: number, b: number ) => number

// 전체 호출 시그니처
type Sum = {
	( a: number, b: number ):number
}

type Log = (message: string, userId?: string ) => void

let log: Log = (message, userId = 'Not signed in'){
	let time = new Date().toISOString()
	console.log(time, message, userId)
} 

// log 함수에 대해 Log 타입을 지정해주었기 때문에 함수 선언 시 매개변수의 타입을 지정해주지 않아도 된다.

매개변수에 직접적으로 타입을 지정해주지 않아도 타입스크립트가 문맥상 타입을 추론하는 것을 “문맥적 타입화” 라고 한다.

 

오버로드된 함수 타입

타입스크립트 프로그래밍에서는 오버로드된 함수를 호출 시그니처가 여러 개인 함수라고 소개하고 있다.

예시 “여행 예약 API 함수를 작성해야한다. 단 함수는 왕복 예약과 함께 편도인 경우도 함께 처리할 수 있어야한다.”

더보기
// 오버로드된 함수 타입
type Reserve = {
  (from: Date, to: Date, destination: string): Reservation; // 왕복
	(from: Date, destination: string): Reservation; // 편도
};

// 오버로드 된 함수
let reserve: Reserve = (from, to, destination) => {
  return `${destination} : ${from} ~ ${to}`;
};
  1. 우선 왕복과 편도 각 목적에 맞게 타입을 작성
  2. Reserve 타입은 이제 두개의 호출 시그니처를 가지게 되었다.
  3. Reserve가 타입으로 지정된 reserve 함수는 두개의 호출 시그니처를 가진 함수가 되었다.
  4. 여러개의 호출 시그니처를 가지는 함수는? ⇒ 오버로드된 함수

reserve 함수의 선언부를 보면 “is not assignable to type Reserve” 라는 에러발생

→ 왕복에 해당하는 타입만 Reserve의 호출 시그니처로 지정했을 경우 타입스크립트는 reserve 함수의 매개변수 타입은 from: Date, to: Date, destination: string 일 것이라고 추론할 수 있었을 것이다. 하지만 호출 시그니처가 두개가 되면서, 타입스크립트는 타입 추론을 할 수 없다.

 

⇒ 함수 선언부에 어떻게 실행될 것인지 알려주는 작업은 간단히 생각해서 호출 시그니처들을 모두 만족하도록 매개변수의 타입을 지정해주면 된다.

let reserve: Reserve = (from: Date, toOrDestination: Date | string, destination?: string) => {
		if(toOrDestination instanceOf Date && destination !== undefined){
// 왕복
	}
	if(typeof toOrDestination === 'string'){

	}
};

함수의 오버로드 정리

  1. 필요한 타입들을 모두 작성한다.
  2. 작성한 타입들을 모두 만족하도록 함수를 선언한다.
  3. 용도에 따라 함수를 호출하여 사용할 수 있다

 

다형성

타입스크립트에는 제네릭이 존재한다. 제네릭은 타입을 매개변수화 하는 것.

무슨 타입인지 확정할 수 없으니, 해당 타입이 지정된 함수가 호출될때 들어오는 데이터의 타입으로 타입이 확정되도록 하는 기능.

type Filter = {
	<T>(array:T[], f:(item:T)=> boolean):T[]
}

제네릭을 사용할때에는 위와 같이 <>를 사용하여 제네릭 타입임을 선언한다. 아무 위치에서나 선언할 수 있는 것이 아닌, 한정된 위치에서만 선언할 수 있다.

 

제네릭 타입의 선언 위치

// 1. 전체 호출 시그니처의 개별 시그니처 한정 제네릭 => 함수 호출 시 타입 한정
type Filter = {
	<T>(array: T[], f:(item:T)=> boolean): T[]
}

// 2. 전체 호출 시그니처 한정 제네릭 => 함수 선언 시 타입 한정
type Filter<T> = {
	(array: T[], f:(item:T)=> boolean): T[]
}

// 3. 1번과 동일하지만 전체 호출 시그니처로 작성한 타입이 아닌 단축 호출 시그니처 => 함수 호출 시 타입 한정
type Filter = <T>(array: T[], f:(item:T)=> boolean)=> T[]
 
// 4. 2번과 동일하지만 전체 호출 시그니처로 작성한 타입이 아닌 단축 호출 시그니처 => 함수 선언 시 타입 한정
type Filter<T> = (array: T[], f:(item:T)=> boolean)=> T[]

// 5. 시그니처 한정 함수 호출 시그니처 => 함수 호출 시 타입 한정 
function filter<T>(array: T[], f:(item:T)=> boolean): T[]{}

 

한정된 다형성

제네릭이면서도 가능한 타입의 제한을 두는 방법

type TreeNode = {
	value: string
}

// LeafNode는 TreeNode의 타입과 LeafNode 안에 작성한 타입을 모두 가진다.
type LeafNode = TreeNode & {
	isLeaf: true
}

// InnnerNode는 TreeNode의 타입과 InnnerNode 안에 작성한 타입을 모두 가진다.
type InnnerNode = TreeNode & {
	children: [TreeNode] | [TreeNode, TreeNode]
}

TreeNode의 서브타입을 인자로 받아 해당 인자 타입에 해당하는 값을 반환하는 함수를 작성하고 싶다면 아래와 같이 작성해줄 수 있다

function mapNode<T extends TreeNode>(node: T, f: (value: string) => string ):T {
	return {...node, value: f(node.value)}
}

위 코드에서 제네릭인 T는 TreeNode를 상속 받는다. 타입을 상속 받게 함으로서 상속 받은 타입으로 제네릭 타입으로 지정될 수 있는 타입의 범위를 제한할 수 있다.

 

타입 제한을 사용하는 이유는

  1. TreeNode의 서브타입이 들어올 것으로 예상하지만 타입을 한정하지 않고 T만 사용하는 경우, 사용자가 string, number를 넣어도 타입 에러는 발생하지 않을 것이며, node.value가 string.value와 같이 접근하게 되므로 에러가 발생.
  2. 애초부터 제너릭을 사용하지 않고 T를 TreeNode로 지정해버리게 되면 기존의 함수 목적과는 달리 반환 타입이 TreeNode로 반환된다.

이러한 문제들을 방지하기 위해 타입 제한을 두며, 타입 제한을 하게 되면 사용자는 보다 명확한 사용법을 유추할 수 있고

함수가 하는 일과 반환하는 값이 보다 명확하기 때문에 개발자는 코드 리딩 시간을 단축 시킬 수 있다.

만약 타입 제한을 여러개 두고 싶다면 아래 코드와 같이 작성할 수 있다.

type HasSides = {numberOfSides: number};
type SidesHaveLength = {sideLength: number};

function longPerimeter<Shape extends HasSides & SidesHaveLength>(s: Shape):Shape{
	console.log(s.numberOfSides * s.sideLength)
	return s
}

위의 TreeNode의 서브타입들에서 &를 사용하여 TreeNode와 작성 타입을 합쳐준 것과 같이 작성해주면 여러개의 타입 제한을 가지도록 할 수 있다.

반응형