[2] 타입스크립트 기본

Doozuu·2023년 10월 1일
0

TypeScript

목록 보기
6/13

📌 기본타입

기본타입(내장 타입)이란 타입스크립트가 자체적으로 제공하는 타입들을 말한다.



📌 원시타입과 리터럴타입

원시타입

원시 타입(Primitive Type)은 동시에 한개의 값만 저장할 수 있는 타입들을 말한다.
ex. number, string, boolean

  1. number 타입
    number 타입은 자바스크립트에서 숫자를 의미하는 모든 값을 포함하는 타입이다.
    단순 정수 뿐만 아니라 소수, 음수, Infinity, NaN등의 특수한 숫자들도 포함한다.
// number
let num1: number = 123;
let num2: number = -123;
let num3: number = 0.123;
let num4: number = -0.123;
let num5: number = Infinity;
let num6: number = -Infinity;
let num7: number = NaN;

이때 변수의 이름 뒤에 콜론(:)과 함께 변수의 타입을 정의하는 이런 문법을 ‘타입 주석’ 또는 ‘타입 어노테이션’이라고 부른다.

  1. string 타입
    string 타입은 문자열을 의미하는 타입이다.
    단순 쌍따옴표 문자열 뿐만 아니라 작은 따옴표, 백틱, 템플릿 리터럴로 만든 모든 문자열을 포함한다.
// string
let str1: string = "hello";
let str2: string = 'hello';
let str3: string = `hello`;
let str4: string = `hello ${str1}`;
  1. boolean 타입
    boolean 타입은 참과 거짓만을 저장하는 타입이다.
// boolean
let bool1 : boolean = true;
let bool2 : boolean = false;
  1. null 타입
    null 타입은 오직 null 값만 포함하는 타입이다.
// null
let null1: null = null;
  1. undefined 타입
    undefined 타입 역시 null 타입과 마찬가지로 오직 하나의 값 undefined만 포함하는 타입입니다.
// undefined 타입
let unde1: undefined = undefined;

리터럴 타입

타입스크립트에는 string, number 처럼 범용적으로 많은 값을 포함하는 타입 뿐만 아니라 딱 하나의 값만 포함하는 타입도 존재한다.

따라서 다음과 같이 변수의 타입을 숫자 10으로 설정하는 것 또한 가능하다.
이렇게 설정하면 이제 numA에는 10 이외의 값을 저장할 수 없게 된다.

let numA: 10 = 10;

이렇듯 하나의 값만 포함하도록 값 자체로 만들어진 타입을 타입스크립트에서는 ‘리터럴 타입’이라고 부른다.

숫자 값 뿐만 아니라 문자열이나 불리언 타입의 값도 모두 가능하다.

let strA: "hello" = "hello";
let boolA: true = true;
let boolB: false = false;



📌 배열과 튜플

배열은 자바스크립트의 배열과 크게 다르지 않고 튜플은 타입스크립트에서만 특별히 제공되는 타입이다.

배열

  1. 배열 타입 정의 방법
    배열 타입을 정의하기 위해서는 배열요소타입[] 형식으로 작성한다.
let numArr: number[] = [1, 2, 3];
let strArr: string[] = ["hello", "im", "winterlood"];

혹은 Array<배열요소타입> 형태로도 배열의 타입을 정의할 수 있다.
참고로 이렇게 꺽쇠와 함께 타입을 작성하는 문법을 타입스크립트에서는 ‘제네릭’ 이라고 부른다.

let boolArr: Array<boolean> = [true, false, true];
  1. 다양한 타입 요소를 갖는 배열 타입 정의하기
    다양한 타입의 배열 요소를 갖는 배열 타입을 정의해야 할 때에는 소괄호와 바(|) 를 이용해 배열 요소가 둘 중 하나의 타입에 해당하도록 타입을 정의하면 된다.
let multiArr: (number | string)[] = [1, "hello"];

이렇듯 바(|)를 이용해 여러 타입중 하나를 만족하는 타입을 정의하는 문법을 유니온(Union) 타입 이라고 부른다.

  1. 다차원 배열 타입 정의하기
    다음과 같이 []를 연달아 작성해 다차원 배열 타입도 간단하게 정의할 수 있다.
let doubleArr : number[][] = [
  [1, 2, 3], 
  [4, 5],
];

튜플

튜플은 자바스크립트에는 없는 타입스크립트의 특수한 타입으로 길이와 타입이 고정된 배열을 의미한다.
(튜플도 결국 배열이기 때문에 push나 pop으로 고정된 길이를 무시하고 요소를 추가하거나 삭제할 수 있다.)

let tup1: [number, number] = [1, 2];
let tup2: [number, string, boolean] = [1, "hello", true];

튜플을 쓰는 이유

아래와 같이 순서를 잘못 쓴 경우 실수를 빨리 바로잡을 수 있다.

const users: [string, number][] = [
  ["이정환", 1],
  ["이아무개", 2],
  ["김아무개", 3],
  ["박아무개", 4],
  [5, "조아무개"], // 오류 발생
];



📌 객체

객체 타입을 정의하는 방법

방법 1 : object로 정의하기

아래 코드에서 user.id로 특정 프로퍼티에 접근하려고 하면 해당 프로퍼티가 존재하지 않는다는 오류가 발생한다.

그 이유는 타입스크립트의 object 타입은 단순 값이 객체임을 표현하는 것 외에는 아무런 정보도 제공하지 않는 타입이기 때문이다.

let user: object = {
  id: 1,
  name: "이정환",
};

따라서 변수 user에 저장된 객체의 구조를 그대로 타입으로 만들기 위해서는 object가 아닌 객체 리터럴 타입을 이용해야 한다.

방법 2 : 객체 리터럴 타입

객체 리터럴 타입은 다음과 같이 중괄호를 열고 객체가 갖는 프로퍼티를 직접 나열해 만드는 타입이다.

변수의 타입을 객체 리터럴 타입으로 정의하면 이제 타입내에 정의되어있는 프로퍼티에 이상 없이 접근할 수 있게 된다.

let user: {
  id: number;
  name: string;
} = {
  id: 1,
  name: "이정환",
};

user.id;

특수한 프로퍼티 정의하기

1. 선택적 프로퍼티(Optional Property)

특정 프로퍼티를 상황에 따라 생략하도록 만들고 싶다면 해당 프로퍼티를 선택적 프로퍼티로 만들어줘야 한다.
선택적 프로퍼티를 설정하려면 프로퍼티의 이름 뒤에 ? 를 붙여주면 된다.

let user: {
  id?: number; // 선택적 프로퍼티가 된 id
  name: string;
} = {
  id: 1,
  name: "이정환",
};

user = {
  name: "홍길동",
};

2. 읽기전용 프로퍼티(Readonly Property)

특정 프로퍼티를 읽기 전용으로 만들고 싶다면 다음과 같이 프로퍼티의 이름 앞에 readonly 키워드를 붙이면 된다.
읽기전용 프로퍼티를 수정하려고 하면 오류가 발생하기 때문에 이를 통해 의도치 않은 프로퍼티의 수정을 방지할 수 있다.

let user: {
  id?: number;
  readonly name: string; // name은 이제 Readonly 프로퍼티가 되었음
} = {
  id: 1,
  name: "이정환",
};

user.name = "dskfd"; // 오류 발생



📌 타입 별칭과 인덱스 시그니처

타입 별칭(Type Alias)

타입 별칭을 이용하면 다음과 같이 변수를 선언하듯 타입을 별도로 정의할 수 있다.

  • 형식 : type 타입_이름 = 타입
// 타입 별칭
type User = {
  id: number;
  name: string;
  nickname: string;
  birth: string;
  bio: string;
  location: string;
};
...

이렇게 만든 타입 별칭은 다음과 같이 변수의 타입을 정의할 때 타입 주석과 함께 이용할 수 있다.

type User = {
  id: number;
  name: string;
  nickname: string;
  birth: string;
  bio: string;
  location: string;
};

let user: User = {
  id: 1,
  name: "이정환",
  nickname: "winterlood",
  birth: "1997.01.07",
  bio: "안녕하세요",
  location: "부천시",
};

let user2: User = {
  id: 2,
  name: "홍길동",
  nickname: "winterlood",
  birth: "1997.01.07",
  bio: "안녕하세요",
  location: "부천시",
};

인덱스 시그니처(Index Signature)

인덱스 시그니쳐는 객체 타입을 유연하게 정의할 수 있도록 돕는 특수한 문법이다.

아래와 같이 프로퍼티가 여러개 필요할 때 타입 정의에도 각 프로퍼티를 모두 정의하려면 매우 불편하다.

type CountryCodes = {
  Korea: string;
  UnitedState: string;
  UnitedKingdom: string;
  // (... 약 100개의 국가)
  Brazil : string
};

let countryCodes: CountryCodes = {
  Korea: "ko",
  UnitedState: "us",
  UnitedKingdom: "uk",
  // (... 약 100개의 국가)
  Brazil : 'bz'
};

이럴때 인덱스 시그니쳐를 이용하면 다음과 같이 간단하게 타입을 정의할 수 있다.

  • 여기서 [key : string] : string 은 인덱스 시그니쳐 문법으로 "이 객체 타입에는 key가 string 타입이고 value가 string 타입인 모든 프로퍼티를 포함된다" 라는 의미이다.
type CountryCodes = {
  [key: string]: string;
};

let countryCodes: CountryCodes = {
  Korea: "ko",
  UnitedState: "us",
  UnitedKingdom: "uk",
  // (... 약 100개의 국가)
  Brazil : 'bz'
};

한가지 주의할 점은 인덱스 시그니쳐를 사용하면서 동시에 추가적인 프로퍼티를 또 정의할 때에는 인덱스 시그니쳐의 value 타입직접 추가한 프로퍼티의 value 타입이 호환되거나 일치해야 한다. 따라서 다음과 같이 서로 호환되지 않는 타입으로 설정하면 오류가 발생한다.

type CountryNumberCodes = {
  [key: string]: number;
  Korea: string; // 오류!
};



📌 열거형(Enum) 타입

열거형 타입은 자바스크립트에는 존재하지 않고 오직 타입스크립트에서만 사용할 수 있는 특별한 타입이다.
열거형은 다음과 같이 여러개의 값을 나열하는 용도로 사용한다.

enum Role {
  ADMIN,
  USER,
  GUEST,
}

숫자 열거형

enum의 각 멤버에는 다음과 같이 숫자를 할당할 수 있고, enum의 멤버들을 값으로도 활용 할 수 있다.

  • 이렇듯 유저의 권한과 같은 여러개의 멤버를 갖는 값을 숫자로 표기할 때 enum을 이용해 보다 안전하고 직관적으로 관리할 수 있다.
enum Role {
  ADMIN = 0,
  USER = 1,
  GUEST = 2,
}

const user1 = {
  name: "이정환",
  role: Role.ADMIN, // 0
};

const user2 = {
  name: "홍길동",
  role: Role.USER, // 1
};

const user3 = {
  name: "아무개",
  role: Role.GUEST, // 2
};

참고로 다음과 같이 enum 멤버에 숫자 값을 직접 할당하지 않아도 0 부터 1씩 늘어나는 값으로 자동으로 할당된다.

enum Role {
  ADMIN, // 0 할당(자동)
  USER,  // 1 할당(자동)
  GUEST, // 2 할당(자동)
}

const user1 = {
  name: "이정환",
  role: Role.ADMIN, // 0
};

const user2 = {
  name: "홍길동",
  role: Role.USER, // 1
};

const user3 = {
  name: "아무개",
  role: Role.GUEST, // 2
};

자동 할당되는 값은 기본적으로 0부터 시작한다. 만약 이 값을 변경하고 싶다면 다음과 같이 시작하는 위치에 값을 직접 할당해주면 된다. 그럼 자동으로 그 아래의 멤버들은 1씩 증가된 값으로 할당된다.

// enum 타입
// 여러가지 값들에 각각 이름을 부여해 열거해두고 사용하는 타입

enum Role {
  ADMIN = 10, // 10 할당 
  USER,       // 11 할당(자동)
  GUEST,      // 12 할당(자동)
}

const user1 = {
  name: "이정환",
  role: Role.ADMIN, // 10
};

const user2 = {
  name: "홍길동",
  role: Role.USER, // 11
};

const user3 = {
  name: "아무개",
  role: Role.GUEST, // 12
};

특별히 이렇게 멤버의 값이 모두 숫자인 enum을 숫자형 enum 혹은 숫자 열거형 타입이라고 부른다.


문자열 열거형

enum의 멤버에는 숫자 말고도 문자열 값도 할당할 수 있다.

이렇듯 모든 멤버의 값이 문자열 값인 enum을 특별히 문자열 enum 이라고 부른다.

enum Role {
  ADMIN,
  USER,
  GUEST,
}

enum Language {
  korean = "ko",
  english = "en",
}

const user1 = {
  name: "이정환",
  role: Role.ADMIN, // 0
  language: Language.korean,// "ko"
};

enum은 컴파일 결과 객체가 된다.

enum은 컴파일될 때 다른 타입들 처럼 사라지지 않고 자바스크립트 객체로 변환된다.

var Role;
(function (Role) {
    Role[Role["ADMIN"] = 0] = "ADMIN";
    Role[Role["USER"] = 1] = "USER";
    Role[Role["GUEST"] = 2] = "GUEST";
})(Role || (Role = {}));
var Language;
(function (Language) {
    Language["korean"] = "ko";
    Language["english"] = "en";
    Language["japanese"] = "jp";
})(Language || (Language = {}));
const user1 = {



📌 any와 unknown

any 타입

any 타입은 타입스크립트에서만 제공되는 특별한 타입으로 타입 검사를 받지 않는 특수한 치트키 타입이다.

예를 들어, 다음과 같이 아주 범용적으로 사용되어야 하는 변수가 하나 있다고 가정하자.

let anyVar = 10;
anyVar = "hello"; // 오류 발생!

이 변수의 값을 숫자로 초기화 한 후에 문자열 타입의 값으로 저장해야 한다면 any 타입을 이용해 오류를 막고 값을 자유롭게 바꿀 수 있다.

let anyVar: any = 10;
anyVar = "hello";

anyVar = true;
anyVar = {};

anyVar.toUpperCase();
anyVar.toFixed();
anyVar.a;

이렇듯 any 타입은 어떠한 타입 검사도 받지 않기 때문에 아무 타입의 값이나 범용적으로 담아 사용할 수 있고, 또 다양한 타입의 메서드도 마음대로 호출해서 사용해도 문제가 되지 않는다.

또 any 타입의 값은 어떤 타입으로 정의된 변수던 문제 없이 다 할당할 수 있다. 따라서 다음과 같이 number 타입의 변수 num에 any 타입의 값 anyVar를 할당해도 문제가 발생하지 않는다.

let anyVar: any = 10;
anyVar = "hello";

let num: number = 10;
num = anyVar;

any 타입은 타입 검사를 받지 않는 타입이므로 모든 타입스크립트의 문법과 규칙으로부터 자유롭지만 그만큼 위험한 타입이다. 따라서 any 타입을 많이 사용하면 많은 부분에서 타입 검사가 제대로 이루어지지 않기에 위험한 코드가 생산된다.

따라서 정말 어쩔 수 없는 경우를 제외하고는 any 타입을 사용하지 않는것을 강력히 권장한다.


Unknown 타입

unknown 타입은 any 타입과 비슷하지만 보다 안전한 타입이다.

unknown 타입의 변수는 다음과 같이 어떤 타입의 값이든 다 저장할 수 있다.

let unknownVar: unknown;

unknownVar = "";
unknownVar = 1;
unknownVar = () => {};

그러나 반대로는 안된다. unknown 타입의 값은 어떤 타입의 변수에도 저장할 수 없다.

let num: number = 10;
(...)

let unknownVar: unknown;
unknownVar = "";
unknownVar = 1;
unknownVar = () => {};

num = unknownVar; // 오류 !

또 unknown 타입의 값은 어떤 연산에도 참여할 수 없으며, 어떤 메서드도 사용할 수 없다.

let unknownVar: unknown;
(...)

unknownVar * 2 // 오류!

즉, unknown 타입은 오직 값을 저장하는 행위밖에 할 수 없게 된다.

만약 위와 같이 unknown 타입의 값을 number 타입의 값처럼 취급하고 곱셈 연산을 수행하게 하고 싶다면 다음과 같이 조건문을 이용해 이 값이 number 타입의 값임을 보장해줘야 한다.

if (typeof unknownVar === "number") {
	// 이 조건이 참이된다면 unknownVar는 number 타입으로 볼 수 있음
  unknownVar * 2;
}

참고로 타입스크립트에서는 위 코드처럼 조건문을 이용해 특정 값이 특정 타입임을 보장할 수 있게 되면 해당 값의 타입이 자동으로 바뀐다. 이를 타입 좁히기라고 한다.

따라서 특정 변수가 당장 어떤 값을 받게 될 지 모른다면 any 타입으로 정의하는 것 보단 unknown 타입을 이용하는게 훨씬 안전한 선택이다.



📌 void와 never

void 타입

void 타입은 아무런 값도 없음을 의미하는 타입이다.

보통은 다음과 같이 아무런 값도 반환하지 않는 함수의 반환값 타입을 정의할 때 사용한다.

function func2(): void {
  console.log("hello");
}

다음과 같이 변수의 타입으로도 당연히 void 타입을 지정할 수 있다.
그러나 void 타입의 변수에는 undefiend 이외의 다른 타입의 값은 담을 수 없다. 그 이유는 void 타입이 undefiend 타입을 포함하는 타입이기 때문이다.

그런데 만약 이때 tsconfig.json에 엄격한 null 검사(strictNullChecks) 옵션을 해제(False)로 설정하면 특별히 이때에는 void 타입의 변수에 null 값도 담을 수 있게 된다.

let a: void;
a = undefined;
// "strictNullChecks: false" 일 경우
a = null;

never 타입

never 타입은 불가능을 의미하는 타입이다.

보통 다음과 같이 함수가 어떠한 값도 반환할 수 없는 상황일 때 해당 함수의 반환값 타입을 정의할 때 사용한다.

function func3(): never {
  while (true) {}
}

무한 루프 외에도 다음과 같이 의도적으로 오류를 발생시키는 함수도 never 타입으로 반환값 타입을 정의할 수 있다.

function func4(): never {
  throw new Error();
}

변수의 타입을 never로 정의하면 any를 포함해 그 어떠한 타입의 값도 이 변수에 담을 수 없게 된다.

profile
모든게 새롭고 재밌는 프론트엔드 새싹

0개의 댓글