Skip to content

9장 정적타입 그리고 타입스크립트 (9월 20일)

김지원 edited this page Sep 20, 2021 · 2 revisions

자바스크립트는 동적 타입 언어이다. 따라서 변수의 타입은 런타임에 결정된다.

EX) 파이썬, PHP

정적 타입 언어는 변수의 타입이 컴파일 타임에 결정된다.

EX) 자바, C++

9.1 타입스크립트란?

타입스크립트는 자바스크립트의 모든 기능을 포함하면서 정적 타입을 지원하는 언어다.

동적 타입 언어

  • 타입에 대한 고민을 하지 않아도 되므로 배우기가 쉽다.
  • 코드의 양이 적을 때 생산성이 높다.
  • 타입 오류가 런타임 시 발견된다.

정적 타입 언어

  • 변수를 선언할 때마다 타입을 고민해야 하므로 진입 장벽이 높다.
  • 코드의 양이 많을 때 동적 타입 언어에 비해 생산성이 높다.
  • 타입 오류가 컴파일 시 발견된다.

정적 타입 언어가 생산성이 높은 이유

정적 타입 언어의 코드는 타입으로 서로 연결되어 있다. 덕분에 연관된 코드 간의 이동이 쉽고, 변수명이나 함수명을 변경하는 등의 리팩터링도 쉽다.

숫자 타입의 변수에 문자열을 입력하면 IDE가 즉시 알려준다.

타입스크립트의 장점

타입스크립트는 다른 경쟁 언어(PureScript,Flow,Elm,ReasonML..)에 비해 큰 생태계를 가지고 있다.

유명한 라이브러리에는 타입스크립트의 타입 정의 파일이 거의 존재한다.

타입스크립트의 다양한 타입

const size: number = 123;
const isBig: boolean = size >=100;
const msg: string = isBig?'크다':'작다';

const values: number[] =[1,2,3];
const values2: Array<number> = [1,2,3];

values.push('a') //타입에러

const data: [string,number] = [msg,size];
data[0].substr(1);
data[1].substr(1); //타입 에러

null과 undefined 타입

자바스크립트에서 값으로 존재하는 null과 undefined는 타입스크립트에서 각각 타입으로 존재한다.

let v1: undefined = undefined
let v2: null = null
v1 = 123 //타입 에러

let v3: number | undefined = undefined
v3 = 123

문자열 리터럴과 숫자 리터럴 타입

타입스크립트에서는 문자열 리터럴과 숫자 리터럴을 타입으로 정의할 수 있다.

let v1: 10|20|30
v1 = 10
v1 = 15 //타입 에러

let v2 : '경찰관'|'소방관'
v2 = '의사' //타입 에러

any 타입

any 타입은 모든 종류의 값을 허용하는 타입이다.

any 타입에는 숫자와 문자열뿐만 아니라 함수도 입력될 수 있다.

let value: any
value = 123
value = '456'
value = () => {}

void 와 never 타입

아무 값도 반환하지 않고 종료되는 함수의 반환 타입은 void 타입으로 정의할 수 있다.

그리고 항상 예외가 발생해서 비정상적으로 종료되거나 무한 루프 때문에 종료되지 않는 함수의 반환

function f1():void{
	console.log('hello');
}
function f2(): never{
	throw new Error('some error');
}
function f3(): never {
	while(true){
		//...
	}
}

object 타입

object 타입은 자바스크립트에서 일반적으로 사용되는 객체의 타입이다.

let v: object
v =  {name: 'abc'}
console.log(v.prop1); //타입 에러

객체의 속성에 대한 정보가 없기 때문에 특정 속성값에 접근하면 타입 에러가 발생한다. 속성 정보를 포함해서 타입을 정의하기 위해서는 뒤에서 설명하는 인터페이스를 사용해야 한다.

교차 타입과 유니온 타입

여러 타입의 교집합과 합집합을 각가 교차 타입과 유니온 타입으로 표현할 수 있다.

교차 타입은 & 기호로 정의하고, 유니온 타입은 | 기호로 정의한다.

let v1: (1|3|5) & (3|5|7)
v1 = 3
v1 = 1 //타입 에러

v1의 타입은 3|5와 같다.

type 키워드로 타입에 별칭 주기

type 키워드를 사용해서 타입에 별칭을 줄 수 있다. 타입 별칭은 타입을 서언할 때 편리하게 사용할 수 있다.

type Width = number | string
let width: Width
width = 100
width = '100px'

열거형 타입

열거형 타입은 enum 키워드를 사용해서 정의한다.

열거형 타입의 각 원소는 값으로 사용될 수 있고, 타입으로 사용될 수도 있다.

enum Fruit {
	Apple,
	Banana,
	Orange
}

const v1: Fruit = Fruit.Apple
const v2: Fruit.Apple | Fruit.Banana = Fruit.Banana

명시적으로 원소의 값 입력하기

enum Fruit {
	Apple,
	Banana ,
	Orange = 5,
}
console.log(Fruit.Apple, Fruit.Banana, Fruit.Orange) //0,5,6
  • 열거형 타입의 첫 번째 원소에 값을 할당하지 않으면 자동으로 0이 할당된다.
  • 열거형 타입의 각 원소에 숫자 또는 문자열을 할당할 수 있다. 명시적으로 값을 입력하지 않으면 이전 원소에서 1만큼 증가한 값이 할당된다.
  • 다른 타입과 달리 열거형 타입은 컴파일 후에도 관련된 코드가 남는다.
  • 열거형 타입은 객체로 존재한다.
  • 열거형 타입의 각 원소는 이름과 값이 양방향으로 매핑된다
console.log(Fruit.Banana) //5
console.log(Fruit['Banana']) //5
console.log(Fruit[5]) //Banana

** 같은 값을 넣었을 경우 **

enum Fruit {
	Apple,
	Banana = 5,
	Orange = 5,
}
console.log(Fruit.Apple, Fruit.Banana, Fruit.Orange) //0,5,5
console.log(Fruit[5]) //Orange

열거형 타입의 값으로 문자열 할당하기

enum Language {
	korean = 'ko',
	English = 'en',
	Japanese = 'jp'
}

열거형 타입의 원소에 문자열을 할당하는 경우에는 단방향으로 매핑된다.

이는 서로 다른 원소의 이름 또는 값이 같을 경우 충돌이 발생하기 때문이다.

열거형 타입을 위한 유틸리티 함수

열거형 타입을 자주 사용한다면 몇 가지 유틸리티 함수를 만들어서 사용하는게 좋다.

열거형 타입에 존재하는 값인지 검사하는 함수

function isValidEnumValue(enumObject: any, value: number | string) {
	if (typeof value === 'number') {
		return !!enumbObject[value];
} else {
		return (
			Object.keys(enumObject)
			.filter(key => isNaN(Number(key)))
			.some(key => enumbObject[key] === value)
		);
	}
}
  1. 값이 숫자이면 양방향으로 매핑됐는지 검사한다.
  2. 값이 문자열이면 양방향 매핑에 의해 생성된 키를 제거하고 해당 값이 존재하는지 검사한다.

상수 열거형 타입

열거형 타입은 컴파일 후에도 남아 있기 때문에 번들 파일의 크기가 불필요하게 커질수 있다.

열거형 타입의 객체에 접근하지 않는다면 굳이 컴파일 후에 객체로 남겨 놓을 필요는 없다.

상수(const) 열거형 타입을 사용하면 컴파일 결과에 열거형 타입의 객체를 남겨 놓지 않을 수 있다.

const enum Fruit {
	Apple,
  Banana,
  Orange,
}
const fruit: Fruit = Fruit.Apple

열거형 타입을 상수로 정의하면 열거형 타입의 객체를 사용할 수 없다

console.log(Fruit) //타입 에러

함수 타입

함수의 타입을 정의하기 위해서는 매개변수 타입과 반환 타입이 필요하다.

콜론을 이용해서 매개변수 타입과 반환 타입을 정의할 수 있다.

function getInfoText(name: string, age: number): string {
	const nameText = name.substr(0,10)
	const ageText = age>=35 ? 'senior' : 'junior' 
	return `name: ${nameText}, age: ${ageText}`
}

const v1: string = getInfoText('mike', 23)
const v2: string = getInfoText(mike,'23') //타입 에러

자바스크립트에서 함수는 일급이므로 함수를 변수에 저장할 수 있다.

함수를 저장할 변수의 타입은 다음과 같이 화살표 기호를 이용한다.

const getinfoText : (name:string, age:number) => string = function(name, age) {
...
}

선택 매개변수

선택 매개변수는 반드시 입력하지 않아도 되는 매개변수다.

매개변수 이름 오른쪽에 물음표 기호를 입력하면 선택 매개변수가 된다.

function getInfoText(name: string, age: number, language?: string):string }
....
}

language에 해당하는 인수는 입력하지 않아도 괜찮다. 입력하는 경우에는 반드시 정의된 타입을 만족하는 값을 입력해야 한다.

선택 매개변수 오른쪽에 필수 매개변수가 오면 컴파일 에러가 발생한다.

function getInfoText(name: string, language?: string, age:number):string{..}

에러 없이 구현하려면 undefiend를 이용한다.

function getInfoText(name: string, language: string|undefined, age:number):string{..}

매개변수의 개수가 많은 경우에는 비구조화 문법을 이용해서 명명된 매개변수로 작성하는게 좋다.

매개변수의 기본값 정의하기

function getInfoText(
	name: string,
	age = 15,
	language = 'korean'
): string {
...
}

타입 오른쪽에 = 기호를 사용해서 매개변수의 기본값을 정의할 수 있다.

age의 인수를 입력하지 않으면 15가 기본값으로 사용된다.

타입을 입력하지 않아도 매개변수의 기본값을 정의할 수 있다.

나머지 매개변수

나머지 매개변수의 타입을 정의하는 코드이다.

나머지 매개변수는 배열로 정의할 수 있다.

function getInfoText(name: string, ...rest: string[]):string{
...
}

this 타입

함수의 this 타입을 정의하지 않으면 기본적으로 any 타입이 사용된다.

any 타입은 가급적 사용하지 않는게 좋으므로 this 타입을 정의해 두는게 좋다.

this 타입을 정의하지 않은 코드

function getParam(index: number): string{
	const params = this.splt(',')1️⃣
	if(index < 0 || params.length <= index) {
		return ''
	}
	return this.split(',')[index]
}

1️⃣ split이라고 쓰려 했는데 splt로 오타를 냈다. this 타입이 any가 되었기 때문에 컴파일 에러가 발생하지 않는다.

함수의 this 타입은 다음과 같이 첫번째 매개변수 위치에서 정의할 수 있다.

this 타입을 정의한 코드

function getParam(this: string, index: number): string{
	const params = this.splt(',') //타입 에러
...
}

원시 타입에 메서드 추가하기

원시 타입에 메서드를 등록할 때는 인터페이스를 이용한다.

문자열 타입에 메서드 추가하기

interface String{
	getParam(this: string, index: number):string
}
String.prototype.getParam = getParam
console.log('asdf, 1234, ok'.getParam(1))

인터페이스를 이용해서 이미 존재하는 문자열 타입에 getParam 메서드를 추가한다.

문자열의 프로토타입에 우리가 작성한 함수를 등록한다.

이제 문자열에 등록된 getParam 메서드를 호출할 수 있다.

함수 오버로드: 여러 개의 타입 정의하기

자바스크립트는 동적 타입 언어이므로 하나의 함수가 다양한 매개변수 타입과 반환 타입을 가질 수 있다. 함수 오버로드를 사용하면 하나의 함수에 여러 개의 타입을 정의할 수 있다.

add 함수를 만들어 다음과 같은 일을 처리하고 싶다고 가정해 보자

  • 두 매개변수 모두가 문자열이면 문자열을 반환한다.
  • 두 매개변수가 모두 숫자이면 숫자를 반환한다.
  • 두 매개변수를 서로 다른 타이브로 입력하면 안된다.
function add(x:number|stirng, y:number|string):number|string {
	if (typeof x === 'number' && typeof y === 'number') {
		return x+y
	} else {
		const result = Number(x) + Number(y)
		return result.toString()
		}
}
const v1: number = add(1,2) //타입 에러
console.log(add(1,'2'))
  1. 모든 매개변수와 반환 값의 타입은 문자열이거나 숫자이다.
  2. 모든 매개변수가 숫자이면 반환값도 숫자이지만 타입 에러가 발생한다.

v1의 타입을 number라고 정의해놨는데 add는 반환값이 number 또는 string이여서 에러 발생

  1. 두 매개변수의 타입이 달라도 타입 에러가 발생하지 않는다.

이는 1번에서 함수의 타입을 구체적으로 정의하지 못했기 때문이다.

다음과 같이 함수 오버로드를 사용하면 이 조건을 만족하는 함수 타입을 정의 할 수 있다.

function add(x:number, y:number): number
function add(x:string, y: string): string
function add(x:number|stirng, y:number|string):number|string {
...
}

const v1: number = add(1,2)
console.log(add(1, '2')) //타입 에러
  1. 매개변수와 반환 타입의 모든 가능한 조합을 정의한다.
  2. 실제 구현하는 쪽에서 정의한 타입은 함수 오버로드의 타입 목록에서 제외된다.
  3. 두 매개변수의 타입이 숫자이면 반환타입도 숫자이므로 타입 에러가 발생하지 않는다.
  4. 두 매개변수의 타입이 다르면 타입 에러가 발생한다.

명명된 매개변수

function getInfoText({
	name,
	age = 15,
	language,
	}: {
		name: string
		age?: number
		language?: string
}) :string {
		const nameText ...
}
  1. 우선 모든 매개변수의 이름을 정의한다. 매개변수의 기본값이 있다면 여기서 같이 정의한다.
  2. 앞에 나열된 모든 매개변수에 대한 타입을 정의한다. 명명된 매개변수의 타입을 다른 코드에서도 재사용하려면 다음과 같이 인터페이스를 사용한다.
interface Param {
	name: string
	age?: number
	language?: string
}

function getInfoText({name,age=15,language}:Param):string{
...
}

인터페이스

인터페이스로 함수 타입 정의하기

interface GetInfoText {
	(name: string, age: number): string
}
const getInfoText: GetInfoText = function(name, age) {
	....
}