#2. [Typescript] 기본 문법
정적 타입
타입스크립트는 변수, 함수 인자, 함수 리턴, 클래스등을 선언할때 타입을 지정하고, 해당 타입만 할당할 수 있는 정적 타입 언어입니다.
타입으로 코딩 계약을 할 수 있기 때문에 실수할 소지가 줄어듭니다.
1
2
const user = 'Kim'; // javascript
const user: string = 'Kim'; // typescript
지원하는 타입은 다음과 같습니다.
항목 | 내용 |
---|---|
boolean |
참/거짓 |
null |
값이 할당되지 않았다는 의미로 개발자가 할당한 값 |
undefined |
값이 할당되지 않아 자바스크리브에서 초기화한 값 |
number |
숫자 |
string |
문자열 |
symbol |
고유하고 수정불가능한 타입 |
object |
개체 |
array |
배열 |
tuple |
데이터 집합인 고정 크기 배열 |
enum |
열거형 |
any |
어떤 타입도 할당 가능합니다. 이걸 사용하면, 타입스크립트를 사용하는 의미가 퇴색하므로, 최대한 사용안하시는게 좋습니다. |
void |
반환값이 없음 |
never |
결코 발생하지 않는값. 예를 들어 무조건 예외를 발생시키는 함수의 리턴값등 |
변수 선언
변수명 뒤에 : 변수 타입
을 작성합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const val: string = 'Kim'; // 일반 변수
const arr1: number[] = [1, 2, 3]; // 배열
const arr2: Array<number> = [1, 2, 3];
const tuple: [string, number] = ['Kim', 10]; // 튜플
enum Color {Red, Green, Blue}; // 열거형
const color: Color = Color.Red;
expect(val).toBe('Kim');
expect(arr1[0]).toBe(1);
expect(arr1[1]).toBe(2);
expect(arr1[2]).toBe(3);
expect(arr2[0]).toBe(1);
expect(arr2[1]).toBe(2);
expect(arr2[2]).toBe(3);
expect(tuple[0]).toBe('Kim');
expect(tuple[1]).toBe(10);
expect(color).toBe(Color.Red);
여러 타입 지원
|
을 이용하여 여러 타입을 지원할 수 있습니다. 예를 들어 다음처럼 string
또는 null
을 저장할 수 있습니다.
1
2
3
let name: string | null = null;
name = 'Kim';
expect(name).toBe('Kim');
Non-null 단언 연산자 !
정적 타입 검사를 하다보면 null
이 아닌 상황임에도, 타입이 달라서 할당할 수 없다는 타입 오류가 발생할 수 있습니다.
이러한 경우 !
을 사용하여 null
이 아님을 단언할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
let a: number | null = null; // 일반 변수
const b = 10;
if (b === 10) { // 상수 연산이므로 a는 null이 아닙니다.
a = 10;
}
// const c: number = a; // (X) 컴파일 오류. Type 'number | null' is not assignable to type 'number'
const c: number = a!; // (O) !으로 null이 아님을 단정합니다.
expect(c).toBe(10);
함수 선언
인자명 뒤에 : 인자 타입
을 작성하고, 괄호 뒤에 : 리턴 타입
을 작성합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 함수 선언
function add1(a: number, b: number): number {
return a + b;
}
// 함수 표현식
const add2 = function(a: number, b: number): number {
return a + b;
}
// 화살표 함수
const add3 = (a: number, b: number): number => a + b;
expect(add1(1, 2)).toBe(3);
expect(add2(1, 2)).toBe(3);
expect(add3(1, 2)).toBe(3);
타입 추론
초기값으로부터 타입을 추론할 수 있다면, 굳이 타입을 명시할 필요는 없습니다. 타입스크립트가 추론하는게 더 정확하므로, 어지간하면 타입 명시없이 사용하는게 좋습니다.(C++도 이와 비슷하게 auto
를 선호합니다. auto의 장점을 참고하세요.)
1
2
3
const val = 'Kim'; // val은 string 타입입니다.
expect(typeof val).toBe('string');
클래스
다음은 자바스크립트의 클래스 선언입니다. constructor()
를 정의하여 name
등 클래스의 속성들을 설정하는데요,
1
2
3
4
5
6
7
8
9
10
11
12
13
// javascript
class User {
constructor(name) {
this.name();
}
getName() {
return this.name;
}
}
const user = new User('Kim');
user.getName();
타입스크립트는 클래스의 속성을 사전에 선언해야 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// typescript
class User {
name: string; // 속성을 사전에 선언합니다.
constructor(name: string) {
this.name = name;
}
getName() {
return this.name;
}
}
const user = new User('Kim');
expect(user.getName()).toBe('Kim');
타입스크립트에서는 특별히 public
, protected
, private
접근 제한자와 readOnly
, static
을 제공하며, extends
를 이용하여 상속할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class User {
private name: string; // 다른 개체에서 사용 불가
protected address: string; // 상속한 개체에서 사용
public phone: string; // 외부에서 사용
readonly gender: string; // 읽기 전용
static staticData: string; // 정적 변수. User.staticData로 접근합니다.
constructor(name: string, gender: string) {
this.name = name;
this.gender = gender; // 읽기 전용은 생성자에서만 설정할 수 있습니다.
}
getName() {
return this.name;
}
// 정적 함수입니다. User.staticMethod()로 접근합니다.
static staticMethod(): number {
return 10;
}
}
// User를 상속합니다.
class Derived extends User {
constructor(name: string, gender: string) {
super(name, gender);
}
// getDerivedName() {
// return this.name; // (X) 컴파일 오류. private는 접근할 수 없습니다.
// }
getAddress() {
return this.address; // protected에 접근합니다.
}
// setGender(gender:string) {
// this.gender = gender; // (X) 컴파일 오류. readonly는 수정할 수 없습니다.
// }
}
const derived = new Derived('Kim', 'man');
derived.phone = '123-4567'; // public에 접근할 수 있습니다.
// expect(derived.name).toBe('Kim'); // (X) 컴파일 오류. private는 접근할 수 없습니다.
expect(derived.getName()).toBe('Kim');
// expect(derived.address).toBe(''); // (X) 컴파일 오류. protected는 접근할 수 없습니다.
expect(derived.phone).toBe('123-4567'); // public은 접근할 수 있습니다.
// staticMethod는 클래스명으로 접근합니다.
expect(User.staticMethod()).toBe(10);
expect(Derived.staticMethod()).toBe(10);
// expect(derived.staticMethod()).toBe(10); // (X) 컴파일 오류. 클래스명으로 접근해야 합니다.
형변환
as
나 <>
로 형변환할 수 있습니다. 형변환은 타입에 기반한 코딩 계약 을 위반하기 때문에 사용하지 않는 것이 좋습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Base {
name:string;
constructor(name: string) {
this.name = name;
}
}
// 상속했습니다.
class Derived extends Base {
constructor(name: string) {
super(name);
}
}
const derived = new Derived('Kim');
const base1:Base = derived as Base; // 형변환
const base2:Base = <Base>derived; // 형변환
expect(derived instanceof Derived).toBe(true);
expect(base1 instanceof Base).toBe(true);
expect(base2 instanceof Base).toBe(true);
// Base로 형변환했지만, 여전히 Derived입니다.
expect(base1 instanceof Derived).toBe(true);
expect(base2 instanceof Derived).toBe(true);
추상 클래스
abstract
를 이용하여 추상 클래스와 추상 메서드를 작성할 수 있습니다. new
로 생성할 수 없으며 꼭 상속해서 추상 메서드를 구현해서 사용해야 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
abstract class Shape {
// 추상 클래스입니다.
width: number;
height: number;
constructor(w: number, h: number) {
this.width = w;
this.height = h;
}
getWidth(): number {
return this.width;
}
getHeight(): number {
return this.height;
}
abstract Draw(): number[]; // 추상 메서드는 자식 개체에서 구현해야 합니다.
}
class Rectangle extends Shape {
constructor(w: number, h: number) {
super(w, h);
}
// 추상 메서드를 구현합니다.
Draw(): number[] {
return [this.width, this.height];
}
}
// const shape = new Shape(10, 20); // (X) 컴파일 오류. 추상 클래스는 생성할 수 없습니다.
const rectangle = new Rectangle(10, 20);
expect(rectangle.Draw()[0]).toBe(10);
expect(rectangle.Draw()[1]).toBe(20);
인터페이스
개체의 속성 규약을 정할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
interface IUser {
id: number;
name: string;
getName(): string;
}
// 인터페이스에서 선언한 속성들이 구현되어야 합니다.
const user: IUser = {
id: 1,
name: 'Kim',
// this 사용시에는 화살표 함수를 사용하지 않습니다.
getName: function () {
return this.name;
},
};
expect(user.id).toBe(1);
expect(user.name).toBe('Kim');
expect(user.getName()).toBe('Kim');
인터페이스로 선언되지 않았더라도, 속성이나 메서드가 동일하면 대입할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface IUser {
id: number;
name: string;
}
// id와 name 속성이 있는 개체입니다.
const user1 = {
id: 1,
name: 'Kim',
};
// IUser 타입은 아니지만 속성이 같으므로 대입됩니다.
const user2: IUser = user1;
expect(user1 === user2).toBe(true);
함수의 인자와 리턴타입 규약을 정할 수 있습니다.
1
2
3
4
5
6
7
8
9
interface IAdd {
(a: number, b: number): number; // 숫자 2개를 전달받아 숫자를 리턴하는 함수
}
const addFunc: IAdd = (a: number, b: number): number => {
return a + b;
};
expect(addFunc(1, 2)).toBe(3);
클래스의 규약을 정할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface IUser {
id: number;
name: string;
getName(): string; // 메서드는 추상으로 선언됩니다.
}
class User implements IUser {
id: number;
name: string;
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
getName() {return this.name;}
}
const user = new User(0, 'Kim');
expect(user.getName()).toBe('Kim');
인터페이스는 상속해서 확장할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface IPerson {
name: string;
}
// 인터페이스를 상속합니다.
interface IUser extends IPerson {
id: number;
}
const user: IUser = {
id: 1,
name: 'Kim',
};
expect(user.id).toBe(1);
expect(user.name).toBe('Kim');
다중 상속도 지원합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface IPerson {
name: string;
}
interface ISalary {
salary: number;
}
// 다중 상속해서 만든 인터페이스입니다.
interface IEmployee extends IPerson, ISalary {}
const employee: IEmployee = {
name: 'Kim',
salary: 100,
};
expect(employee.name).toBe('Kim');
expect(employee.salary).toBe(100);
타입 별칭
사용자 정의 타입을 선언할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type User = {
id: number;
name: string;
};
// 개체를 초기값을 주어 생성합니다.
const user1: User = {
id: 1,
name: 'Kim',
};
expect(user1.id).toBe(1);
expect(user1.name).toBe('Kim');
// 빈 개체를 User로 생성한 후 값을 대입합니다.
const user2: User = {} as User;
user2.id = 2;
user2.name = 'Lee';
expect(user2.id).toBe(2);
expect(user2.name).toBe('Lee');
특정 값으로만 구성된 타입을 만들 수도 있습니다.
1
2
3
4
5
type Week = '월' | '화' | '수' | '목' | '금';
const week1:Week = '월';
//const week2:Week = '토'; // (X) 컴파일 오류. '토'는 Week 타입이 아닙니다.
expect(week1).toBe('월');
선택적 속성
속성은 선택적으로 사용될 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface IUser {
id: number;
name: string;
addr?: string; // addr은 없을 수도 있습니다.
}
// addr이 없어도 IUser에 대입됩니다.
const user: IUser = {
id: 1,
name: 'Kim',
};
expect(user.id).toBe(1);
expect(user.name).toBe('Kim');
제네릭
제네릭은 인스턴스화 되는 시점에 타입이 결정되는 함수나 클래스입니다. 사용시 <>
안에 타입을 지정하면 됩니다.
다음은 함수 제네릭입니다. T
의 추론이 가능하다면 <>
을 생략할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
// T 타입의 .length를 리턴합니다.
function getLength<T>(params: T[]): number {
return params.length;
}
// 함수 인스턴스화시 타입이 결정됩니다.
expect(getLength<number>([1, 2])).toBe(2);
expect(getLength<string>(['a', 'b'])).toBe(2);
// 추론 가능하면 <>을 생략할 수 있습니다.
expect(getLength([1, 2])).toBe(2);
expect(getLength(['a', 'b'])).toBe(2);
다음은 클래스 제네릭을 이용하여 임의 타입을 배열로 관리하는 Queue입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 임의 타입을 배열로 관리하는 Queue입니다.
class Queue<T> {
protected data: T[] = [];
push(item: T) {
this.data.push(item);
}
pop(): T | undefined {
return this.data.shift();
}
}
// number를 사용하는 Queue입니다.
const q1 = new Queue<number>();
q1.push(0);
q1.push(1);
expect(q1.pop()).toBe(0);
// string을 사용하는 Queue입니다.
const q2 = new Queue<string>();
q2.push('a');
q2.push('b');
expect(q2.pop()).toBe('a');
제네릭 타입 제약
다음과 같이 제네릭 타입에서 타입의 세부 속성을 사용하는 경우, 해당 속성이 없다며 컴파일 오류가 납니다.
1
2
3
4
5
// T 타입의 .length를 리턴합니다.
function getLength<T>(params: T): number {
return params.length; // (X) 컴파일 오류. T에 length가 없다며 컴파일 오류가 납니다.
}
expect(getLength<number[]>([1, 2])).toBe(2);
이러한 경우 해당 타입을 extends
를 이용하여 한정할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// length를 제공합니다.
interface ICanLength {
length: number;
}
// extends로 T를 한정할 수 있습니다.
function getLength<T extends ICanLength>(params: T): number {
return params.length;
}
// 함수 인스턴스화시 타입이 결정됩니다.
expect(getLength<number[]>([1, 2])).toBe(2);
expect(getLength<string[]>(['a', 'b'])).toBe(2);
// 추론 가능하면 <>을 생략할 수 있습니다.
expect(getLength([1, 2])).toBe(2);
expect(getLength(['a', 'b'])).toBe(2);
제네릭에서 여러 타입 지원
|
을 이용하여 여러 타입을 지원할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
interface IUser {
id: number;
name: string;
}
interface IProduct {
id: number;
price: number;
}
// | 로 여러개 타입을 사용할 수 있습니다.
// IUser와 IProduct를 모두 지원합니다.
function getId<T extends IUser | IProduct>(params: T): number {
return params.id;
}
const user: IUser = {
id: 0,
name: 'Kim',
};
const product: IProduct = {
id: 1,
price: 1000,
};
expect(getId(user)).toBe(0);
expect(getId(product)).toBe(1);
댓글남기기