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

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

💡 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과 같은 형태로 타입을 지정할 수 있다.
  • TypeScriptJavaScript의 모든 데이터 타입을 허용한다.



원시 타입 (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를 사용하면 NullUndefined가 서로의 타입을 할당할 수 없다.

단, 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;

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

그러나 만약 TypeScriptJSX와 함께 사용할 때는 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();
}


TypeScriptpet이 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}'.`);
}


  • 그러나 타입이 원시 값인지 확인하는 함수를 정의하는 것은 너무나 귀찮은 일이다.
  • 다행히도 TypeScripttypeof를 타입 가드로 인식하기 때문에 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

댓글