[타입스크립트] 타입스크립트의 기본 타입

[타입스크립트] 타입스크립트의 기본 타입

💡 tl;dr


  • 지원 타입 목록
  • 타입 선언 (Type Declaration)
  • 타입 추론 (Type Inference)
  • 타입 단언 (Type Assertions)
  • 타입 가드 (Type Guards)



지원 타입 목록


  • Typescript는 Javascript의 타입 외에도 고유의 타입이 추가로 제공된다.
Type JS TS Description
boolean ✅ ✅ true와 false
null ✅ ✅ 값이 없다는 것을 명시
undefined ✅ ✅ 값을 할당하지 않은 변수의 초기값
number ✅ ✅ 숫자(정수와 실수, Infinity, NaN)
string ✅ ✅ 문자열
symbol ✅ ✅ 고유하고 수정 불가능한 데이터 타입이며 주로 객체 프로퍼티들의 식별자로 사용(ES6에서 추가)
object ✅ ✅ 객체형(참조형)
array ✅ 배열
tuple ✅ 고정된 요소수 만큼의 타입을 미리 선언후 배열을 표현
enum ✅ 열거형. 숫자값 집합에 이름을 지정한 것
any ✅ 타입 추론(type inference)할 수 없거나 타입 체크가 필요없는 변수에 사용, var 키워드로 선언한 변수와 같이 어떤 타입의 값이라도 할당 가능
void ✅ 일반적으로 함수에서 반환값이 없을 경우 사용
never ✅ 결코 발생하지 않는 값



타입 선언 (Type Declaration)


  • 타입스크립트는 일반 변수, 매개 변수(Parameter), 객체 속성(Property) 등에 : TYPE과 같은 형태로 타입을 지정할 수 있다.
  • TypeScript는 JavaScript의 모든 데이터 타입을 허용한다.



원시 타입 (primitive type)


1
2
3
let foo_s: string = 'text'    // 문자열
let foo_n: number = 0; // 숫자형
let foo_b: boolean = true; // 논리형


이 때 만약 지정된 타입이 아닌 다른 타입의 데이터를 할당하려고 시도하면 에러를 발생한다.

1
2
3
4
5
6
// 문자열로 선언하고 숫자를 대입하면 에러 발생
let foo_s: string = 0; // error

// 숫자값이 들어가고 이후에 문자열을 대입하면 에러 발생
let foo_n: number = 1;
foo_n = 'text'; // error Type 'text' is not assignable to type 'number'.



배열 (Array)


배열은 다음과 같이 두 가지 방법으로 타입을 선언할 수 있다.

1
let list: number[] = [1, 2, 3];
1
let list: Array<number> = [1, 2, 3];



튜플 (Tuple)


  • 튜플 타입을 사용하면, 요소의 타입과 개수가 고정된 배열을 표현할 수 있다.
  • 요소들의 타입이 모두 같을 필요는 없다


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 튜플 타입으로 선언
let x: [string, number];

// 초기화
x = ["hello", 10]; // 성공

// 잘못된 초기화
x = [10, "hello"]; // 오류

// 정해진 인덱스에 위치한 요소에 접근하면 해당 타입이 나타난다.
console.log(x[0].substring(1)); // 성공
console.log(x[1].substring(1)); // 오류, 'number'에는 'substring' 이 없다.

// 정해진 인덱스 외에 다른 인덱스에 있는 요소에 접근하면, 오류가 발생하며 실패한다.
x[3] = "world"; // 오류, '[string, number]' 타입에는 프로퍼티 '3'이 없다.

console.log(x[5].toString()); // '[string, number]' 타입에는 프로퍼티 '5'가 없다.



열거형 (Enum)


  • 열거형(enum)은 숫자값 집합에 이름을 지정한 것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum Color1 {Red, Green, Blue};
let c1: Color1 = Color1.Green;

console.log(c1); // 1

enum Color2 {Red = 1, Green, Blue};
let c2: Color2 = Color2.Green;

console.log(c2); // 2

enum Color3 {Red = 1, Green = 2, Blue = 4};
let c3: Color3 = Color3.Blue;

console.log(c3); // 4



모든 타입 (Any)


  • Any는 모든 타입을 의미한다.
  • 일반적인 자바스크립트 변수와 동일하게 어떤 타입의 값도 할당할 수 있다.
  • 외부 자원을 활용해 개발할 때 불가피하게 타입을 단언할 수 없는 경우, 유용할 수 있다.


1
2
3
4
5
6
7
let any: any = 123;
any = 'Hello world';
any = {};
any = null;

// 다양한 값을 포함하는 배열을 나타낼 때에도 사용 가능
const list: anyArray[] = [1, true, 'Anything!'];


  • 강한 타입 시스템의 장점을 유지하기 위해 Any 사용을 엄격하게 금지하려면, 컴파일 옵션 "noImplicitAny": true를 통해 Any 사용 시 에러를 발생시킬 수 있다.



알 수 없는 타입 (Unknown)


  • Any와 같이 최상위 타입인 Unknown은 알 수 없는 타입을 의미한다.
  • Any와 같이 Unknown에는 어떤 타입의 값도 할당할 수 있지만, Unknown을 다른 타입에는 할당할 수 없다.
  • 일반적인 경우 Unknown은 타입 단언(Assertions)이나 타입 가드(Guards)를 필요로 한다.


1
2
3
4
5
6
7
let a: any = 123;
let u: unknown = 123;

let v1: boolean = a; // 모든 타입(any)은 어디든 할당할 수 있다.
let v2: number = u; // 알 수 없는 타입(unknown)은 모든 타입(any)을 제외한 다른 타입에 할당할 수 없다.
let v3: any = u; // OK!
let v4: number = u as number; // 타입을 단언하면 할당할 수 있다.



객체 (Object)


  • 기본적으로 typeof 연산자가 "object"로 반환하는 모든 타입
  • 컴파일러 옵션에서 엄격한 타입 검사(strict)를 true로 설정하면, null은 포함하지 않음.
  • object 타입을 쓰면 Object.create 같은 API 가 더 잘 나타난다.


1
2
3
4
5
6
7
8
9
declare function create(o: object | null): void;

create({ prop: 0 }); // 성공
create(null); // 성공

create(42); // 오류
create("string"); // 오류
create(false); // 오류
create(undefined); // 오류



Void


  • void는 어떤 타입도 존재할 수 없음을 나타내기 때문에, any의 정반대로 작동한다.
  • void는 보통 함수에서 반환 값이 없을 때 반환 타입을 표현하기 위해 쓰인다.


1
2
3
function warnUser(): void {
console.log("This is my warning message");
}


  • void를 타입 변수를 선언하는 것은 유용하지 않은데, 그 변수에는 null 또는 undefined만 할당할 수 있기 때문이다. ( --strictNullChecks를 사용하지 않을 때만 해당 )
1
2
let unusable: void = undefined;
unusable = null; // 성공 (`--strictNullChecks` 을 사용하지 않을때만)



Null과 Undefined


  • 기본적으로 Null과 Undefined는 모든 타입의 하위 타입으로, 다음과 같이 각 타입에 할당할 수 있다.
  • 심지어 서로의 타입에도 할당 가능하다.
1
2
3
4
5
6
7
8
let num: number = undefined;
let str: string = null;
let obj: { a: 1, b: false } = undefined;
let arr: any[] = null;
let und: undefined = null;
let nul: null = undefined;
let voi: void = null;
// ...

컴파일 옵션 "strictNullChecks": true를 사용하면 Null과 Undefined가 서로의 타입을 할당할 수 없다.

단, Void에는 Undefined을 할당할 수 있다.

1
let voi: void = undefined; // ok



Never


  • Never는 절대 발생하지 않을 값을 나타내며, 어떠한 타입도 적용할 수 없다
1
2
3
function error(message: string): never {
throw new Error(message);
}


  • 보통 빈 타입 배열을 타입으로 선정할 경우 에러로 볼 수 있다.
1
2
3
const never: [] = [];
never.push(3); // Error - TS2345:
// Argument of type '3' is not assignable to parameter of type 'never'.



유니언 (Union)


  • 2개 이상의 타입을 허용하는 경우
  • |(vertical bar)를 통해 타입을 구분
  • ()는 선택사항이다.


1
2
3
4
5
let union: (string | number);
union = 'Hello type!';
union = 123;
union = false; // Error - TS2322:
// Type 'false' is not assignable to type 'string | number'.



인터섹션 (Intersection)


  • &(ampersand)를 사용해 2개 이상의 타입을 조합하는 경우
  • 새 타입을 생성하지 않고 기존 타입들을 조합할 수 있어 유용하나, 자주 사용되진 않음.
  • 유니언이 Or라면, 인터섹션은 And에 해당


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
// 기존 타입들이 조합 가능하다면 인터섹션을 활용할 수 있다.
interface IUser {
name: string,
age: number
}
interface IValidation {
isValid: boolean
}
const heropy: IUser = {
name: 'Heropy',
age: 36,
isValid: true // Error - TS2322:
// Type '{ name: string; age: number; isValid: boolean; }'
// is not assignable to type 'IUser'.
};
const neo: IUser & IValidation = {
name: 'Neo',
age: 85,
isValid: true
};

// 혹은 기존 타입(IUser, IValidation)과 비슷하지만,
// 정확히 일치하는 타입이 없다면 새로운 타입을 생성해야 한다.
interface IUserNew {
name: string,
age: number,
isValid: boolean
}
const evan: IUserNew = {
name: 'Evan',
age: 36,
isValid: false
};



함수 (Function)


  • 화살표 함수를 이용해 타입을 지정할 수 있다.
  • 인수의 타입과 반환 값의 타입을 입력한다.


1
2
3
4
5
6
7
8
9
10
11
// myFunc는 2개의 숫자 타입 인수를 가지고, 숫자 타입을 반환하는 함수.
let myFunc: (arg1: number, arg2: number) => number;
myFunc = function (x, y) {
return x + y;
};

// 인수가 없고, 반환도 없는 경우.
let yourFunc: () => void;
yourFunc = function () {
console.log('Hello world~');
};



타입 추론 (Type Inference)


  • 명시적으로 타입 선언이 되어있지 않은 경우, 타입스크립트는 타입을 추론해 제공한다.
  • 타입스크립트가 타입을 추론하는 경우
    • 초기화된 변수
    • 기본값이 설정된 매개 변수
    • 반환 값이 있는 함수
1
2
3
4
5
6
7
8
// 초기화된 변수 `num`
let num = 12;

// 기본값이 설정된 매개 변수 `b`
function add(a: number, b: number = 2): number {
// 반환 값(`a + b`)이 있는 함수
return a + b;
}


  • 타입 추론은 엄격하지 않은 타입 선언( strict: false 등 )과는 다르다.
  • 따라서 타입 추론을 활용하여 ‘모든 곳에 타입 명시’를 피하는 편이 코드 가독성에는 더 좋다.



타입 단언 (Type Assertions)


  • 개발자가 컴파일러보다 값의 유형을 더 잘 알고 있을 때 이를 명시하는 방법
  • 컴파일러에게 내가 뭘 하고 있는지 알아 라고 말하는 것
  • 다른 언어의 타입 변환(캐스팅)과 유사하지만 데이터를 재구성하거나 검사하지 않음
    • 캐스팅 : 이미 선언된 타입이 런타임에서 변하는 것
  • 런타임에 영향을 미치지 않고, 온전히 컴파일러만 사용
  • 타입 스크립트는 개발자가 필요한 어떤 검사를 수행했다고 인지


타입 단언에는 두 가지 형태가 있다. 하나는 “angle-bracket“ 문법이다.

1
2
3
let someValue: any = "this is a string";

let strLength: number = (<string>someValue).length;


다른 하나는 as - 문법이다.

1
2
3
let someValue: any = "this is a string";

let strLength: number = (someValue as string).length;

위 두 예제는 동일하므로 기호에 따라 선택하여 사용하다.

그러나 만약 TypeScript를 JSX와 함께 사용할 때는 as - 스타일의 문법만 허용된다.



타입 가드 (Type Guards)


타입을 매번 보장하기 위해 타입 단언을 여러 번 사용해야 하는 경우가 있다.


1
2
3
4
5
6
7
let pet = getSmallPet();

if ((pet as Fish).swim) {
(pet as Fish).swim();
} else if ((pet as Bird).fly) {
(pet as Bird).fly();
}


  • 타입 가드는 타입스크립트가 추론 가능한 특정 범위(scope)에서 타입을 보장할 수 있다.
  • NAME is TYPE 형태의 타입 서술어를 반환 타입으로 명시한 함수다.
  • 여기서 NAME은 반드시 현재 함수의 매개변수 이름이어야 한다.


1
2
3
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}


  • pet is Fish는 위 예제의 타입 서술어다.
  • 이를 활용하여 앞선 예제는 아래와 같이 깔끔해진다
1
2
3
4
5
6
7
// 이제 'swim'과 'fly'에 대한 모든 호출이 허용된다
if (isFish(pet)) {
pet.swim();
}
else {
pet.fly();
}


TypeScript가 pet이 if문 안에서 Fish라는 것을 알고 있을뿐만 아니라, else문 안에서 Fish가 없다는 것을 알고 있으므로, Bird를 반드시 가지고 있어야한다.


위 방식뿐 아니라 제공 가능한 타입가드들이 더 있다.
typeof, in, instanceof 연산자를 직접 사용하는 타입 가드다.


in 연산자 사용하기


  • in 연산자는 타입을 좁히는 작용을 한다.
  • n in x에서, n은 문자열 리터럴 혹은 문자열 리터럴 타입이고 x는 유니언 타입이다.
  • “true“ 분기에서는 선택적 혹은 필수 프로퍼티 n을 가지는 타입으로 좁힌다.
  • “false“ 분기에서는 선택적 혹은 누락된 프로퍼티 n을 가지는 타입으로 좁혀진다.
1
2
3
4
5
6
function move(pet: Fish | Bird) {
if ("swim" in pet) {
return pet.swim();
}
return pet.fly();
}


typeof 타입 가드


  • 유니언 타입을 사용하는 버전의 padLeft 코드 예제다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function isNumber(x: any): x is number {
return typeof x === "number";
}

function isString(x: any): x is string {
return typeof x === "string";
}

function padLeft(value: string, padding: string | number) {
if (isNumber(padding)) {
return Array(padding + 1).join(" ") + value;
}
if (isString(padding)) {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}


  • 그러나 타입이 원시 값인지 확인하는 함수를 정의하는 것은 너무나 귀찮은 일이다.
  • 다행히도 TypeScript는 typeof를 타입 가드로 인식하기 때문에 typeof x === "number"를 함수로 추상할 필요가 없다.
  • 즉, 타입 검사를 인라인으로 작성할 수 있다.


1
2
3
4
5
6
7
8
9
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}


  • typeof 타입 가드는 두 가지 다른 형식인 typeof v === "typename" 와 typeof v !== "typename"이 있다.
  • 여기서 typename은 “number“, “string“, “boolean“, “symbol“ 중 하나여야 한다.
  • TypeScript에서 위에 없는 다른 문자열과 비교하는 것을 막지는 않지만, 타입 가드의 표현식으로 인지되지 않는다.


instanceof 타입 가드


  • instanceof 타입 가드는 생성자 함수를 사용하여 타입을 좁히는 방법이다.
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
interface Padder {
getPaddingString(): string
}

class SpaceRepeatingPadder implements Padder {
constructor(private numSpaces: number) { }
getPaddingString() {
return Array(this.numSpaces + 1).join(" ");
}
}

class StringPadder implements Padder {
constructor(private value: string) { }
getPaddingString() {
return this.value;
}
}

function getRandomPadder() {
return Math.random() < 0.5 ?
new SpaceRepeatingPadder(4) :
new StringPadder(" ");
}

// 타입은 'SpaceRepeatingPadder | StringPadder' 다.
let padder: Padder = getRandomPadder();

if (padder instanceof SpaceRepeatingPadder) {
padder; // 타입은 'SpaceRepeatingPadder'으로 좁혀진다.
}
if (padder instanceof StringPadder) {
padder; // 타입은 'StringPadder'으로 좁혀진다.
}


instanceof의 오른쪽은 생성자 함수여야 하며, TypeScript는 다음과 같이 타입을 좁힌다.

  • 함수의 prototype 프로퍼티 타입이 any가 아닌 경우
  • 타입의 생성자 시그니처에서 반환된 유니언 타입일 경우

위 예제와 같은 순서대로 진행된다.



참고



[타입스크립트] 타입스크립트의 기본 타입

https://sklubmk.github.io/2021/08/05/f3a2b4b29950/

Author

Jinki Kim

Posted on

2021-08-05

Updated on

2021-08-05

Licensed under

댓글