공부해봅시당

[Typescript] 타입스크립트 타입 선언 & 종류 본문

STUDY/Typescript

[Typescript] 타입스크립트 타입 선언 & 종류

tngus 2024. 7. 23. 03:18

우아한 타입스크립트 with 리액트 p.81 <타입스크립트의 타입 계층 구조>
TypeScript의 any , unknown , object , void , undefined , null , never 타입 간의 할당 가능성 요약표


boolean

단순한 참(true) / 거짓(false) 값

let isBoolean: boolean;
isBoolean = true;

let isDone: boolean = false;

 

number

정적 타입이라 해서 C / JAVA 처럼 int, float, double 타입은 없고, Javascipt의 number 자료형을 그대로 사용
16진수, 10진수, 2진수, 8진수 리터럴도 지원

let num: number;
let integer: number = 6;
let float: number = 3.14;

let hex: number = 0xf00d; // 61453
let binary: number = 0b1010; // 10
let octal: number = 0o744; // 484

let infinity: number = Infinity;
let nan: number = NaN;

function plus(num1: number, num2: number): number {
	return num1 + num2;
}

 

더보기

Infinity

무한대를 나타내는 특별한 숫자 값

주로 수학적 연산이나 계산에서 극한값을 다룰 때 유용하게 사용

 

1. 양의 무한대

console.log(Infinity); // Infinity
console.log(Number.POSITIVE_INFINITY); // Infinity

 

2. 음의 무한대

console.log(-Infinity); // -Infinity
console.log(Number.NEGATIVE_INFINITY); // -Infinity

 

 3. 0으로 나누기

console.log(1 / 0); // Infinity
console.log(-1 / 0); // -Infinity

 

 4. number 타입의 일부

- Infinity는 number 타입으로 취급됨

let infinity: number = Infinity;
console.log(typeof infinity); // "number"

 

 5. 비교 연산

- `Infinity` > 모든 유한한 숫자
- `-Infinity` < 모든 유한한 숫자

console.log(Infinity > 1000); // true
console.log(-Infinity < -1000); // true

 

 6. 연산에서의 동작

- Infinity에 어떤 숫자를 더하거나 빼도 결과는 Infinity 또는 -Infinity

console.log(Infinity + 1); // Infinity
console.log(Infinity - 1); // Infinity
console.log(Infinity * 2); // Infinity
console.log(Infinity / 2); // Infinity

 

 

string

작은따옴표('), 큰따옴표(") 뿐만 아니라 ES6의 템플릿 문자열도 string 타입에 포함

let red: string = 'Red'; // 작은따옴표

let green: string = "Green"; // 큰따옴표
let yourColor: string = 'Your color is' + green;

let myColor: string = `My color is ${red}.`; // 템플릿 문자열

function strings(str1: string, str1: string): string {
	return str1 + str1;
}

 

array

일반적으로 `대괄호 표기법`과 `제네릭 표기법`을 사용할 수 있음

내부적으로 동작하는 방식이 완전히 동일하고, 같은 함수를 가지기 때문에 취향껏 사용하면 됨

let fruits: string[] = ['Apple', 'Banana', 'Mango']; // 대괄호 표기법
// or
let fruits: Array<string> = ['Apple', 'Banana', 'Mango']; // 제네릭 표기법
// 오직 숫자 아이템만 허용
let nums1:number[] = [100, 101, 102];
let nums2:Array<number> = [100, 101, 102];

// 오직 문자 아이템만 허용
let strs1:string[] = ['apple', 'banana', 'melon'];
let strs2:Array<string> = ['apple', 'banana', 'melon'];

// 오직 불리언 아이템만 허용
let boos1:boolean[] = [true, false, true];
let boos2:Array<boolean> = [true, false, true];

// 모든 데이터 타입을 아이템으로 허용 (any 타입 - 뒤에서 배움)
let someArr1: any[] = [0, 1, {}, [], 'str', false];
let someArr2: Array<any> = [0, 1, {}, [], 'str', false];

// 특정 데이터 타입만 아이템으로 허용 (union 타입 - 뒤에서 배움)
let selects1:(number | string)[] = [102, 'apple'];
let selects3:Array<number | string> = [102, 'apple'];

// 특정 데이터 타입 배열만 허용
let selects2:number[] | string[] = [102, 'apple']; // Error: Type '(string | number)[]' is not assignable to type 'number[] | string[]'
selects2 = [102, 103]; // number로만 이루어진 배열이거나
selects2 = ['apple', 'banana']; // string으로만 이루어진 배열인 경우만 허용
// 나머지 매개변수(스프레드 연산자) 를 이용한 배열 반환 함수
function getArr(...args: number[]): number[] {
   return args;
}
getArr(1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]

 

더보기

스프레드 연산자

배열이나 객체의 요소들을 개별적으로 분리하거나, 함수의 나머지 매개변수를 처리할 때 사용

스프레드 연산자는 세 개의 점(...)으로 표기

 

1. 함수의 나머지 매개변수(Rest Parameters)

함수 호출 시 전달된 인수들을 배열로 수집할 때 사용

특히 가변 길이의 인수를 처리하는 함수에서 유용

// 1. spread parameter 사용
// ...args의 결과가 number 배열
function getSpreadArr(...args: number[]): number[] {
   return args;
}

const result1 = getSpreadArr(1, 2, 3); // [1, 2, 3]
const result2 = getSpreadArr(4, 5);    // [4, 5]
const result3 = getSpreadArr();        // []


// 2. 일반 배열
// 처음부터 number 배열을 넘겨줘야 함
function getBasicArr(args: number[]): number[] {
   return args;
}

const result4 = getBasicArr([1, 2, 3]); // [1, 2, 3]
const result5 = getBasicArr([4, 5]);    // [4, 5]
// const result6 = getArr();       // 오류: 인수가 필요합니다.

 

 

2. 배열의 요소 분해(Spread in Arrays)

스프레드 연산자를 사용해 배열의 요소들을 개별적으로 분해할 수 있음

이를 통해 배열을 복사하거나, 배열을 합칠 수 있음

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

const combinedArr = [...arr1, ...arr2];
console.log(combinedArr); // [1, 2, 3, 4, 5, 6]

const copiedArr = [...arr1];
console.log(copiedArr); // [1, 2, 3]

 

3. 객체의 속성 분해(Spread in Objects)

객체 리터럴(Object Literal, 아래 접은 글에서 자세한 내용 확인)에서 스프레드 연산자를 사용해 객체의 속성들을 개별적으로 분해할 수 있음

객체 복사, 병합 가능

const obj1 = { a: 1, b: 2 }; // 객체 리터럴 { key: value }
const obj2 = { c: 3, d: 4 }; // 객체 리터럴

const combinedObj = { ...obj1, ...obj2 };
console.log(combinedObj); // { a: 1, b: 2, c: 3, d: 4 }

const copiedObj = { ...obj1 };
console.log(copiedObj); // { a: 1, b: 2 }

 

4. 함수 호출 시 인수 분해(Spread in Function Calls)

스프레드 연산자를 사용해 배열을 개별 인수로 분해하여 함수에 전달

const numbers = [1, 2, 3, 4, 5];

function sum(a: number, b: number, c: number, d: number, e: number): number {
   return a + b + c + d + e;
}

const result = sum(...numbers);
console.log(result); // 15

 

 

더보기

객체 리터럴(Object Literal)

객체를 정의하고 생성하는 간단하고 직관적인 방법
중괄호 {} 안에 키-값 쌍을 나열하여 객체 정의

let car = {
    make: "Toyota",
    model: "Camry",
    year: 2020
};


1. 중첩 객체 가능

객체 안에 객체 가능

let employee = {
    name: "Alice",
    position: "Developer",
    address: {
        street: "123 Main St",
        city: "Metropolis",
        zip: "12345"
    }
};

 

2. 함수 포함 가능

let calculator = {
    add: function(a, b) {
        return a + b;
    },
    subtract: function(a, b) {
        return a - b;
    }
};

console.log(calculator.add(5, 3)); // 8
console.log(calculator.subtract(5, 3)); // 2

 

3. 변수 이름과 객체 속성명이 동일할 때는 단축 속성명으로 사용 가능

let name = "Bob";
let age = 25;

let person = {
    name, // name: name
    age   // age: age
};

console.log(person); // { name: "Bob", age: 25 }

 

4. 대괄호 []를 사용해 동적으로 속성명 정의 가능

let propName = "color";
let car = {
    [propName]: "red",
    ["model" + "Name"]: "Mustang"
};

console.log(car); // { color: "red", modelName: "Mustang" }

 

tuple

배열의 서브타입, 배열을 길이제한 했다고 생각하면 편함

=> 크기와 타입이 고정된 배열

// RGB
let rgbColor: [number, number, number] = [255, 255, 0];


// string, number
let x: [string, number]; // 튜플 타입으로 선언

x = ["hello", 10]; // 성공
x = [10, "hello"]; // 오류 (원소 타입이 안맞음)
x = ["hello", 10, 99]; // 오류 (원소 갯수가 안맞음)


// number, string, boolean
let user: [number, string, boolean] = [1234, 'HEROPY', true];
console.log(user[0]); // 1234
console.log(user[1]); // 'HEROPY'
console.log(user[2]); // true


// 2차원 튜플
let users: [number, string, boolean][];
// or
let users: Array<[number, string, boolean]>;

users = [[1, 'Neo', true], [2, 'Evan', false], [3, 'Lewis', true]];


// 타입이 아닌 값 자체를 고정하는 것도 가능
let tuple: [1, number];
tuple = [1, 2];
tuple = [1, 3];

tuple = [2, 3]; // Error - TS2322: Type '2' is not assignable to type '1'.


// 옵셔널을 사용해 있어도 되고, 없어도 되는 값을 표시할 수 있음
const optionalTuple1: [number, number, number?] = [1, 2]; // [1, 2]
const optionalTuple2: [number, number, number?] = [1, 2, 3]; // [1, 2, 3]
Optional(옵셔널, ?로 표기함)
타입스크립트에서 좀 더 유연한 데이터 모델링과 사용자 정의 타입을 지원하기 위한 개념
특정 속성 또는 매개변수가 값이 있을 수도 있고 없을 수도 있는 것을 의미
즉, 선택적 매개변수(옵셔널 파라미터) 또는 선택적 속성(옵셔널 프로퍼티)은 필수적으로 존재하지 않아도 되며 선택적으로 사용될 수 있음을 나타냄
선택적 속성은 해당 속성에 값을 할당하지 않아도 되고, 해당 속성이 없어도 오류가 발생하지 않음

 

유의할 점

튜플은 정해진 타입의 고정된 길이 배열을 표현하긴 하지만, 할당에 국한됨

`.push()`나 `.splice()` 등을 통해 값을 넣는 행위는 막을 수 없음

let tuple: [string, number];
tuple = ['a', 1];
tuple = ['b', 2];

tuple.push(3);
tuple.push('c');
tuple.push(4);
console.log(tuple); // [ 'b', 2, 3, 'c', 4 ];

tuple.push(true); // Error - 그렇다고 해서 튜플에 정의되지 않는 타입을 넣을수는 없음
// push() 함수를 사용하여 요소를 추가하는 경우 처음 할당된 값의 타입과 정확히 일치해야 함
// 위에서는 string과 number로 만들었기 때문에 string이나 number이기만 하다면 계속해서 push() 할 수 있음

 

enum

상수들의 집합

특정 값을 고정하는 독립된 자료형

 

숫자 열거형

정방향 매핑과 역방향 매핑 모두 가능

- 정방향 매핑: 이름을 통해 값을 찾음

- 역방향 매핑: 값을 통해 이름을 찾음

// 상수 집합
enum Avengers { SpiderMan, IronMan, Thor, Hulk }

// 정방향 매핑
let hero1: Avengers = Avengers.SpiderMan; // 0

// 역방향 매핑: 인덱스 번호로 접근 가능
// 시스템에서는 { SpiderMan = 0, IronMan = 1, Thor = 2, Hulk = 3 } 이런식으로 내부적으로 인덱싱이 자동으로 되어있다고 이해하면 됨
let hero2: Avengers = Avengers[0];

// 직접 인덱스 부여 가능
// 직접 인덱스 번호를 정한다면, 시스템에서는 { SpiderMan = 2, IronMan = 3, Thor = 4, Hulk = 5} 
// 이런식으로 내부적으로 인덱싱이 정해진 숫자 이후로 순차적으로 증가함
enum Avengers { SpiderMan = 2, IronMan, Thor, Hulk }

let hero: Avengers = Avengers[2]; // SpiderMan
let hero: Avengers = Avengers[5]; // Hulk

 

문자 열거형

정방향 매핑은 가능하지만 역방향 매핑은 불가능

// 문자열 값으로 초기화 가능
// 반드시 모두 초기화해야 함. 숫자형처럼 자동 증가하지 않음
enum Color {
    Red = 'red',
    Green = 'green',
    Blue = 'blue',
 }
 
 console.log(Color.Red); // red
 console.log(Color['Green']); // green
 console.log(Color['green']); // undefined

더보기

enum 고급 가이드

 

헬퍼 함수

 enum Priority {
   High,
   Medium,
   Low,
}

// 어떤 Enum에 특정 value가 있는지 검사해주는 함수
export function getIsValidEnumValue(enumObject: any, value: number | string) {
   return Object.keys(enumObject) // [ '0', '1', '2', 'High', 'Medium', 'Low' ] 로 변환
      .filter((key) => isNaN(Number(key))) // 숫자가 아닌 것만 필터링, 숫자 키를 걸러냄
      .some((key) => enumObject[key] === value); // 필터링된 키 배열에서 value가 enumObject의 값과 일치하는지 확인
}

const result = getIsValidEnumValue(Priority, Priority.High);
console.log('result: ', result); // true

const result2 = getIsValidEnumValue(Priority, 1);
console.log('result: ', result2); // true

 

enum 매핑

computed property 문법(아래 접은글에서 개념 확인 가능) 사용

enum Priority {
   High,
   Medium,
   Low,
}

//* priority 이넘 타입을 한글로 출력하기 위한 매핑
export const PRIORITY_NAME_MAP = {
   [Priority.High]: '높음',
   [Priority.Medium]: '중간',
   [Priority.Low]: '낮음',
};

class Todo {
   priority;

   constructor(priority: Priority) {
      this.priority = priority;
   }

   getPriority() {
      console.log(`${PRIORITY_NAME_MAP[this.priority]}`); // 한글 매핑 시킨 객체 속성으로 enum 값을 줌
   }
}

const t: Todo = new Todo(Priority.High);
t.getPriority(); // 높음

 

enum 타입 가드

맵드 타입 이용

 

 

 

상수 enum

enum을 사용하면 컴파일 후에도 객체가 남기 때문에 번들 파일이 불필요하게 커질수 있다는 단점이 있음
만일 enum 객체에 빈번하게 접근하지 않는다면, 굳이 컴파일 후에 객체로 남겨 놓을 필요가 없음

이때 사용하는 것이 const enum

 

일반 enum

// 일반 enum
enum Bool {
  True,
  False,
  FileNotFound
}
let value = Bool.FileNotFound;

// 일반 enum이 자바스크립트로 컴파일되면 아래와 같아짐
var Bool;
(function (Bool) {
  Bool[(Bool["True"] = 0)] = "True";
  Bool[(Bool["False"] = 1)] = "False";
  Bool[(Bool["FileNotFound"] = 2)] = "FileNotFound";
})(Bool || (Bool = {}));
let value = Bool.FileNotFound;

 

const enum

const enum은 내부 상수값들이 전부 compile 단계에서 내부 필드를 전부 상수로 변경하기 때문에 런타임에 의존 모듈의 영향을 받지 않게 되며, 코드 크기가 더 적기 때문에 더 선호된다고 함

// const enum
const enum Bool {
  True,
  False,
  FileNotFound
}
let value = Bool.FileNotFound;

// const enum은 자바스크립트로 변환 시, 컴파일 시점에 상수로 치환되어 아래와 같아짐
let value = 2; /* FileNotFound */

 

하지만 const enum은 reverse mapping이 불가능함

// 일반 enum인 경우에는 가능
enum Bool {
  True,
  False,
  FileNotFound
}
let value = Bool.FileNotFound;
let value2 = Bool[3];


// const enum인 경우에는 error
const enum Bool {
  True,
  False,
  FileNotFound
}
let value = Bool.FileNotFound;
let value2 = Bool[3]; // ERROR! - A const enum member can only be accessed using a string literal.

 

따라서 일반 enum은 reverse mapping이 필요한 경우에만 사용하는 것이 좋고, 그 외의 경우에는 const enum으로 사용하는 것이 좋다고 함

실무에서도 대체로 reverse mapping을 사용할 상황이 잘 없기 때문에 const enum이 더 선호됨

 

더보기

computed property 문법

자바스크립트 ES6에서부터 적용

객체를 선언하는 순간에 변수를 활용하여 동적인 프로퍼티명을 할당

즉, 객체의 key 속성명을 표현식(변수, 함수 등)을 통해 지정하는 것

 

변수를 객체 key로

var kk = "id";

var a = {
    name : "super",
    [kk] : 123 // computed property
}

console.log(a[kk]); // 123
console.log(a["id"]); // 123
console.log(a.id); // 123

var mm = "rank";
a[mm] = '#1'; 

console.log(a[mm]); // #1
console.log(a["rank"]); // #1
console.log(a.rank); // #1

 

함수를 객체 key로

함수가 오는 경우에 함수의 return 값은 숫자가 문자열이어야 함

그래야 속성명을 지을 수 있기 때문

function func1(a, b) {
  return a + b;
}

function func2() {
  return 'hello';
}

let obj = {
  [`key${func1(5,8)}`] : 'result is 13',
  [func2()] : 'hi'
}

// obj = {
//   key13 : 'value is 13',
//   hello: 'hi'
// }

 

 

더보기

맵드 타입

타입스크립트의 고급 문법

기존에 정의되어 있는 타입을 새로운 타입으로 변환해주는 문법

interface Obj {
   prop1: string;
   prop2: string;
}

type ChangeType<T> = { 
   [K in keyof T]: number;
};

type Result = ChangeType<Obj>;
/*
{ 
   prop1: number; 
   prop2: number; 
}
*/

 

맵드 타입 문법

 

 

 

 

object

객체 뿐만 아니라 배열, 함수까지 object로 포함

number, string, boolean, bigint, symbol, null, undefined가 아닌 나머지

let userA: { name: string, age: number };
userA = {
  name: 'HEROPY',
  age: 123
};

let userB: { name: string, age: number };
userB = {
  name: 'inpa',
  age: false, // Error
  email: 'inpa@naver.com' // Error
};

 

위 방식처럼 사용할 수도 있지만 보통은 type이나 interface 방식 사용

// type alias
type IUser = {
    name: string,
    age: number
}
 
let userA: IUser = {
    name: 'HEROPY',
    age: 123
};
 
let userB: IUser = {
    name: 'inpa',
    age: false, // Error
    email: 'inpa@naver.com' // Error
};
// interface
interface IUser {
  name: string,
  age: number
}

let userA: IUser = {
  name: 'HEROPY',
  age: 123
};

let userB: IUser = {
  name: 'inpa',
  age: false, // Error
  email: 'inpa@naver.com' // Error
};
더보기

type과 interface의 차이

 

확장 방식

interface는 extends를 통해 확장(상속)

interface Person {
  name: string;
  age: number;
}

interface Student extends Person { // 확장(상속)
  school: string;
}

const jieun: Student = {
  name: 'jieun',
  age: 27,
  school: 'HY'
}

type은 & 기호를 이용해 확장

type Person = {
  name: string,
  age: number
}

type Student = Person & { // 확장(상속)
  school: string
}

const jieun: Student = {
  name: 'jieun',
  age: 27,
  school: 'HY'
}

 

선언적 확장

interface는 선언적 확장이 가능

하지만 타입스크립트가 추구하는 방식이 아니기 때문에 추천하지 않는다고 함

interface Person {
  name: string;
  age: number;
}

interface Person { // 선언적 확장
  gender: string;
}

const jieun: Person = {
  name: 'jieun',
  age: 27,
  gender: 'female'
}

 

type은 선언적 확장 불가능

type Person = {
  name: string;
  age: number;
}

type Person = { // ❗️Error: Duplicate identifier 'Person'.
  gender: string;
}

 

자료형

interface는 원시 자료형 상속 불가능

interface Person {
  name: string;
  age: number;
  gender: string;
}
interface name extends string { // ❌ 불가능
  ...
}

 type은 원시 자료형, 단순 원시값, 튜플, 유니언 타입 등 다양하게 가능

type Name = string; // primitive
type Age = number;
type Person = [string, number, boolean]; // tuple
type NumberString = string | number; // union
type Person = { // 객체
  name: string,
  age: number,
  gender: string
}

 

computed value 사용

interface는 불가능

type Subjects = 'math' | 'science' | 'sociology';

interface Grades {
  [key in Subjects]: string; // ❗️Error: A mapped type may not declare properties or methods.
}

 

type은 가능

type Subjects = 'Math' | 'Science' | 'Sociology';

type Grades = {
  [key in Subjects]: string;
}

const grades1: Grades = {Math: 'good', Science: 'bad', Sociology: 'perfect'};
const grades2: Grades = {Math: ''}; // Error: Science, Sociology 속성이 있어야 함

 

any

자바스크립트에 존재하는 모든 값을 오류 없이 받을 수 있음

 

자바스크립트에서 any 타입과 유사한 예시

타입스크립트의 any와 유사하기는 하지만 해당 개념은 존재하지 않음

let myVariable; // 타입을 명시하지 않음
myVariable = 42; // 숫자 타입
myVariable = "Hello"; // 문자열 타입
myVariable = true; // 불리언 타입

 

 

타입스크립트에서 any 타입

명시적으로 어떤 타입의 값이든 가질 수 있도록 선언 가능

let myVariable: any;
myVariable = 42;
myVariable = "Hello";
myVariable = true;

 

 

피해야 하는 any 타입?

하지만 타입스크립트는 동적 타이핑 특징을 가진 자바스크립트에 정적 타이핑을 하는 것이 주된 목적임

따라서 any 타입은 타입스크립트로 달성하고자 하는 정적 타이핑을 무색하게 만들 수 있음

그렇기 때문에 `any 타입을 변수에 할당하는 것은 지양해야 할 패턴으로 알려져 있음`

`tsconfig.json` 파일에서 `noImplicitAny`옵션을 활성화하면 타입이 명시되지 않은 변수의 암묵적인 any 타입에 대한 경고를 발생시킬 수 있음

 

하지만 어쩔 수 없이 써야할 때도 있는 법

1. 개발 단계에서 임시로 값을 지정해야 할 때

- 원인: 복잡한 구성 요소로 이루어진 개발 과정, 추후 값이 변경될 가능성이 높거나 세부 항목에 대한 타입이 확정되지 않은 경우

- 해결: 임시로 타입을 지정할 때 사용 후 세부 스펙이 나오는 시점에 다른 타입으로 대체

 

2. 어떤 값을 받아올지 또는 넘겨줄지 정할 수 없을 때

- 원인: API 요청 및 응답 처리, 콜백 함수 전달, 타입이 잘 정제되지 않아 파악이 힘든 외부 라이브러리 등

- 해결: any 타입으로 지정하여 유연하게 대응

 

3. 값을 예측할 수 없을 때 암묵적으로 사용

- 원인: 외부 라이브러리나 웹 API의 요청에 따라 다양한 값을 반환하는 API(ex. 브라우저의 Fetch API 등)

- 해결: any 타입으로 지정하여 유연하게 대응

 

결론

그럼에도 any 타입은 지양하는 것이 좋음

타입스크립트의 타입 검사를 무색하게 만들고 잠재적으로 위험한 상황을 초래할 가능성이 커지기 때문

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

 

unknown

TypeScript 3.0 버전부터 추가된 타입

 

any 타입과 유사하게 모든 타입의 값이 할당될 수 있음

하지만 any를 제외한 다른 타입으로 선언된 변수에는 unknown 타입 값을 할당할 수 없음

  any unknown
공통점 - 어떤 타입이든 할당 가능
차이점 - any 타입은 어떤 타입으로도 할당 가능(단 never는 제외) - unknown 타입은 any 외에 다른 타입으로 할당 불가능

 

 

예시를 통해 확인해보자.

 

any 예시

any 타입은 프로퍼티에 접근하기, 메서드 호출하기, 인스턴스 생성하기 등을 할 수 있음

하지만 런타임에서 에러가 발생할 위험이 큼

let anyValue: any
let hi:boolean = anyValue
// 가능 -> 치명적인 실수를 할 가능성이 높아짐

const anytype:any = true
const hi:string = anytype
hi.toUpperCase()
// 가능 -> 현재 코드에서는 가능하지만, 런타임에서는 에러를 발생시키게 됨
// TypeError: hi.toUpperCase is not a function

 

unknown 예시

unknown 타입은 프로퍼티에 접근하기, 메서드 호출하기, 인스턴스 생성하기 등을 할 수 없음

let varr:unknown
let booleanType:boolean = varr 
// error boolean 타입에는 unknown 타입을 할당할 수 없음

let bool:boolean = true
let unkw:unknown = bool
// 반대로 unknown 타입에 boolean 타입을 할당하는 건 할 수 있음

 

아래와 같이 타입의 범위를 좁혀준 경우에는 가능

const unknownArgFunction = (value:unknown) => {
  value.toUpperCase() // unknown 타입에는 메서드를 호출할 수 없습니다.
  if(typeof value === 'string') {
    value.toUpperCase() // string으로 타입을 좁혀준 뒤 메서드를 호출합니다.
  }
}

 

아래와 같이 선언 시점에는 가능하더라도 실행시키는 시점에는 컴파일타임에서 경고를 주며 에러가 발생함

 

결론

따라서 unknown 타입은 휴먼에러를 방지할 수 있기 때문에 any가 사용될 곳에 대체하여 사용하는 방법이 권장됨

 

언제 뭘 사용할까?

any

- any는 포 컴포넌트같이 어떤 값을 받을지 모르는 상황에서 사용
- 이때 unknown을 사용하면 가공할 때 타입 캐스팅을 모두 해야 하는 상황이 생겨서 이럴 때 any를 사용할 수 있다고 함

unknown
- 대체로 unknown 사용
- 타입스크립트 4.4부터 try - catch 에러의 타입이 any에서 unknown으로 변경되어서 에러 핸들링 시에도 unknown 사용
- 강제 타입 캐스팅을 통해 타입을 전환할 때 사용하기도 함 `const env = process.env as unknown as ProcessEnv` -> 하지만 이것도 any와 다를 바 없어서 지양해야 한다고 함

 

null / undefined

기본적으로 null 과 undefined는 다른 모든 타입의 하위 타입

즉, null과 undefined를 아무 여러 타입에 할당 가능

let nullable: null = null;
let undefinedable: undefined = undefined;
/* strick 모드가 아닐경우에만 대입이 가능함 */
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;

 

아래와 같이 tsconfig에서 strictNullChecks 옵션을 false로 주는 경우에는 성공

 

컴파일 옵션인 --stricks 이나 --strictNullChecks를 사용하면, null과 undefined는 오직 any와 각자 자신들 타입에만 할당 가능하게 됨

(예외적으로 void에만 undefined 할당 가능)

이 옵션은 불특정한 많은 일반적인 에러를 방지하는 데 도움을 주니, 가능한 경우 --strictNullChecks를 사용할 것을 권장함

 

void

특정 값을 반환하지 않는 경우에 사용

만약 함수의 return 값을 명시하지 않는다면, 타입스크립트 컴파일러는 함수의 return 값을 void로 추론함

function showModal(type: ModalType): void {
	feedbackSlice.actions.createModal(type);
}

// 화살표 함수로 작성 시
const showModal = (type: ModalType): void => {
	feedbackSlice.actions.createModal(type);
};

 

만일 void를 함수가 아닌 변수 타입으로 정의한다면, void 타입 변수에는 undefined와 null만 할당 가능
즉, void를 타입 변수를 선언하는 것은 유용하지 않다고 보면 됨

let unusable: void = undefined; // void는 undefined 값만 가질 수 있음
unusable = null; // 성공, tsconfig에서 strictNullChecks 컴파일 옵션을 사용하지 않을때만
unusable = void; // Error, void는 type이긴 하지만 value는 아니기 때문에 이렇게는 불가능

 

never

never 타입은 number나 string 처럼 어떠한 자료형 값을 담기 위한 타입이 아님

never 타입은 타입스크립트에서 잘못된 것을 알려주기 위한 키워드로써, 단어 그대로 `절대 발생할 수 없는 타입을 나타낸다`고 보면 됨

 

null이나 undefined처럼 모든 타입에 할당 가능한 하위 타입임

하지만 어떤 타입도(any 타입일지라도) never에 할당불가(never 자신은 제외)

function getError(): never {
   throw new Error('ERROR');
}

let never_type: never;

never_type = 99; // 오류 발생: 숫자 값을 never 타입 변수에 할당할 수 없음

// 함수의 반환 값이 never 타입 이기 때문에 오류가 발생하지 않음
never_type = getError();

 

사용예시 1 - 잘못된 것을 알려주는 경우

const a: [] = [];
a.push(3); // Error. 'number' 형식의 인수는 'never' 형식의 매개 변수에 할당될 수 없습니다.

 

사용예시 2 - 에러를 반환하는 함수 표현식

에러를 반환하는 함수 표현식에서 항상 오류를 발생시키거나 절대 반환하지 않는 반환 타입으로도 쓰일 수 있음

// 에러를 발생시키는 커스텀 never 타입 함수를 생성
function error(message: string): never {
    throw new Error(message); // 함수는 리턴되지 않고 에러를 throw함
}

function fail() {
    return error("Something failed"); // 커스텀 에러함수 호출
}

const result = fail(); // 반환 타입이 never로 추론됨

 

사용예시 3 - 무한히 함수가 실행되는 경우

무한 루프는 함수가 종료되지 않음을 의미하기 때문에 값 반환을 할 수 없음

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

무한루프

 

literal

문자열과 숫자에 한해서 직접 값 자체를 타입으로도 선언 가능

(자바에서의 final 키워드와 유사하다고 생각, 다만 여기서 literal은 개념이고 변수에 붙이는 키워드는 아님)

 

numeric literal types

값 자체가 타입이기 때문에 아래 예시에서는 3이 타입

타입이 number가 아님

const num: 3 = 3;

const num = 3; // 타입 생략

 

아래와 같이 사용하는 것도 가능

// 1 ~ 6 까지 주사위 숫자를 반환 하는 함수 (주사위는 숫자 7이상 이 나올수 없다)
function rollDice(): 1 | 2 | 3 | 4 | 5 | 6 {
    // ...
}

 

string literal types

문자열에 값을 정확하게 지정할때 사용

type Easing = 'ease-in' | 'ease-out' | 'ease-in-out'; // string literal type

function animate(dx: number, dy: number, easing: Easing) {
   if (easing === 'ease-in') {
      // ...
   } else if (easing === 'ease-out') {
   } else if (easing === 'ease-in-out') {
   } else {
      // null이나 undefined를 전달하면 필터링 실행
   }
}

animate(0, 0, 'ease-in');
animate(0, 0, 'uneasy'); // Error. "uneasy"는 여기서 허용하지 않음

 

 

union |

2개 이상의 타입을 허용하는 경우, 이를 유니언이라고 함 (OR의 의미로도 쓰임)

| (파이프 기호)를 통해 타입을 구분하며, 괄호는 단일 타입일 때는 안써도 되지만 배열일 경우 씌워야 함

let union: string | number;
union = 'Hello type!';
union = 123;
union = false; // Error - TS2322: Type 'false' is not assignable to type 'string | number'.


// 배열 타입을 UNION으로 표현할때 괄호로 묶어 표현해야함
// 안그러면 string | number[] 이런식으로 됨
let array: (string | number)[] = ['Apple', 1, 2, 'Banana', 'Mango', 3];
// Or
let array: Array<string | number> = ['Apple', 1, 2, 'Banana', 'Mango', 3];


// 함수 파라미터에 사용
function padLeft(value: string, padding: boolean | number) {
  // ...
}

let indentedString = padLeft("Hello world", true);

 

주의할 점

어느 타입이 들어오든 간에 오류가 안 나는 방향으로 확실하게 타입을 추론함

따라서 Person과 Developer 두 타입에 공통적으로 들어있는 속성인 name만 접근 가능

type Person = {
  name: string;
  age: number;
}

type Developer = {
  name: string;
  skill: string;
}

function introduce(someone: Person | Developer) {
  someone.name; // O 정상 동작
  someone.age; // X 타입 오류
  someone.skill; // X 타입 오류
}

 

함수에서도 return 값을 유니온으로 사용하게 되면 에러가 발생함

function add(x: string | number, y: string | number): string | number {
   return x + y;
}

add(1, 2);
add('1', '2');
add(1, '2');

 

이런 부분은 함수 오버로딩을 통해 해결이 가능하다고 함

더보기

함수 오버로딩

 

intersection &

&(ampersand 기호)를 사용해 2개 이상의 타입을 조합 (AND의 의미로도 쓰임)

type Person = {
   name: string;
   age: number;
};
type Developer = {
   name: string;
   skill: number;
};

const heropy: Person = {
  name: 'Heropy',
  age: 36,
  skill: 11 // Error - { name: string; age: number; skill: number; }' 형식은 'person' 형식에 할당할 수 없습니다. 개체 리터럴은 알려진 속성만 지정할 수 있으며 'person' 형식에 'skill'이(가) 없습니다
};

// 두 타입 별칭을 합쳐 하나의 { name: string, age: number, skill: number } 이라는 타입을 구성
const neo: Person & Developer = {
   name: 'Neo',
   age: 85,
   skill: 11,
};

 

 

인덱스 시그니처(Index Signature)

{[key : K] : T}형식으로 객체가 여러 Key를 가질 수 있으며 Key와 매칭되는 value를 가지는 경우 사용

객체가 <key, vlaue>형식이며 key와 value의 타입을 정확하게 명시해야 하는 경우 사용할 수 있음

 

key가 string인 경우에는 number 값만 할당할 수 있는데, name인 string key에 string value를 할당하려고 해서 에러나 남

interface IndexSignature {
  [key: string]: number;
  name: string;
}

export const IndexSignature: IndexSignature = {
  name: "value",
};

 

여러 타입의 value를 할당하고 싶다면 아래와 같이 정의할 수 있음

type userType = {
  [key: string]: string | number | boolean;
}

let user : userType = {
	'이름' : '또치'
  	'나이' : 38
  	'여자' : true
}

 

아래와 같이 활용 가능

let obj {
    월급 : 200,
    보너스 : 200,
    인센티브 : 100,
    복지포인트 100
}

function totalPay (payment : {[key : string] : number}){
    let total = 0;
    for (const key in payment){
    	total += payment[key];
    }
    return total
}

 

주의

key 타입은 string, number, symbol, Template literal타입만 가능

type userInfoType = "name" | "age" | "address";

type userType = {
  [key in userInfoType]: string;
};

let user: userType = {
  '이름': '또치',
  '나이': '38',
  '주소': '라스베가스'
};

 

Template literal
백틱(```)을 사용하여 문자열을 감싸고, ${} 구문을 통해 변수와 표현식을 문자열 안에 쉽게 삽입하는 방식

const name = "Alice";
const greeting = `Hello, ${name}!`;
console.log(greeting); // "Hello, Alice!"

 

 

인덱스드 엑세스 타입(Indexed Access Types)

다른 타입의 특정 속성이 가지는 타입을 조회하기 위해 사용

interface Person {
  name: string;
  age: number;
  address: {
    street: string;
    city: string;
  };
}

// Person의 name 속성 타입을 NameType 변수에 할당
type NameType = Person['name']; // string

// Person의 address 속성 타입을 AddressType 변수에 할당
type AddressType = Person['address']; // { street: string; city: string; }

// Person의 address 속성의 city 타입을 CityType 변수에 할당
type CityType = Person['address']['city']; // string

const name: NameType = "John";
const city: CityType = "New York";

// address 타입을 사용한 예제
const address: AddressType = {
  street: "123 Main St",
  city: "New York"
};

console.log(name); // "John"
console.log(city); // "New York"
console.log(address); // { street: "123 Main St", city: "New York" }

 

인덱스에 사용되는 타입 또한 그 자체로 타입이기 때문에 유니온 타입, keyof, 타입 별칭 등의 표현 사용 가능

// 인터페이스 정의
interface User {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user';
  address: {
    street: string;
    city: string;
  };
}

// 인덱스드 엑세스 타입을 사용하여 특정 속성의 타입을 추출
type UserNameType = User['name']; // string
type UserEmailType = User['email']; // string
type UserRoleType = User['role']; // 'admin' | 'user'


// User의 address 속성의 타입을 추출
type AddressType = User['address']; // { street: string; city: string; }
type CityType = User['address']['city']; // string


// 유니온 타입 정의
type UserKeys = 'name' | 'email' | 'role';

// 타입 별칭을 사용하여 유니온 타입 정의
type UserInfoType = User[UserKeys]; // string | 'admin' | 'user'


// keyof를 사용하여 User 타입의 모든 키를 유니온 타입으로 추출
type AllUserKeys = keyof User; // 'id' | 'name' | 'email' | 'role' | 'address'

// User의 모든 속성 타입을 유니온 타입으로 추출
type UserValueTypes = User[AllUserKeys]; // number | string | 'admin' | 'user' | { street: string; city: string; }

// UserValueTypes를 사용한 예제
let someUserValue: UserValueTypes;

someUserValue = 1; // number
console.log(someUserValue); // 1

someUserValue = 'Alice'; // string
console.log(someUserValue); // Alice

someUserValue = 'admin'; // 'admin' | 'user'
console.log(someUserValue); // admin

someUserValue = { street: '123 Main St', city: 'Wonderland' }; // { street: string; city: string; }
console.log(someUserValue); // { street: '123 Main St', city: 'Wonderland' }


// 함수에서 인덱스드 엑세스 타입을 사용하는 예제
function getUserProperty<T extends AllUserKeys>(user: User, key: T): User[T] {
  return user[key];
}

// 사용 예제
const user: User = {
  id: 1,
  name: 'Alice',
  email: 'alice@example.com',
  role: 'admin',
  address: {
    street: '123 Main St',
    city: 'Wonderland'
  }
};

// getUserProperty 함수 사용
const userName: UserNameType = getUserProperty(user, 'name'); // Alice
const userEmail: UserEmailType = getUserProperty(user, 'email'); // alice@example.com
const userRole: UserRoleType = getUserProperty(user, 'role'); // admin
const userCity: CityType = getUserProperty(user, 'address').city; // Wonderland

console.log(userName); // Alice
console.log(userEmail); // alice@example.com
console.log(userRole); // admin
console.log(userCity); // Wonderland

 

배열의 요소 타입을 조회하기 위해 인덱스트 엑세스 타입을 사용하는 경우도 있음

배열 타입의 모든 요소는 전부 동일한 타입을 가지며 배열의 인덱스는 숫자 타입임

따라서 number로 인덱싱하여 배열 요소를 얻은 다음에 typeof 연산자를 붙여주면 해당 배열 요소의 타입을 가져올 수 있음

// PromotionList 배열 정의
const PromotionList = [
  { type: "product", name: "chicken" },
  { type: "product", name: "pizza" },
  { type: "card", name: "cheer-up" },
];

// ElementOf 타입 정의: T 배열의 요소 타입을 추출
type ElementOf<T> = T[number];

// PromotionList 배열의 요소 타입을 PromotionItemType 타입으로 정의
// typeof PromotionList는 배열 타입 [{type: string, name: string}, ...]을 나타냄
// typeof PromotionList[number]는 배열 요소 타입 {type: string, name: string}을 나타냄
type PromotionItemType = ElementOf<typeof PromotionList>;

// 이제 PromotionItemType은 {type: string, name: string} 타입

 


출처

https://xionwcfm.tistory.com/394

 

typescript any 와 unknown의 차이

😀 unknown은.. TypeScript 3.0 버전부터 추가된 타입입니다. unkwon은 any와 마찬가지로 모든 타입에 값이 할당될 수 있는 마치 any와 비슷하게 동작하는 타입입니다. 그렇다면 왜 unknown 타입이 필요한 걸

xionwcfm.tistory.com

https://inpa.tistory.com/entry/TS-%F0%9F%93%98-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%83%80%EC%9E%85-%EC%84%A0%EC%96%B8-%EC%A2%85%EB%A5%98-%F0%9F%92%AF-%EC%B4%9D%EC%A0%95%EB%A6%AC

 

📘 타입스크립트 타입 선언 & 종류 💯 총정리

타입 - Boolean 단순한 참(true) / 거짓(false) 값 let isBoolean: boolean; isBoolean = true; let isDone: boolean = false; 타입 - Number 정적 타입이라 해서 C / JAVA 처럼 int, float, double 타입은 없고, Javascipt의 number 자료형을

inpa.tistory.com

https://inpa.tistory.com/entry/TS-%F0%9F%93%98-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-enum-%EC%A0%95%EB%A6%AC

 

📘 타입스크립트 Enum 타입 정복하기

고급 타입 Enum enum은 C, Java와 같은 언어를 다뤄봤으면 한번쯤 들어보는 흔하게 쓰이는 타입으로 특정 값(상수)들의 집합을 의미한다. 타입스크립트의 튜플 타입이 특정 타입이나 값을 고정하는

inpa.tistory.com

https://inpa.tistory.com/entry/JS-%F0%9F%93%9A-computed-property-%EA%B0%9D%EC%B2%B4-Key%EB%A5%BC-%EB%B3%80%EC%88%98%EB%A1%9C

 

📚 computed property 문법 (객체 key를 변수로)

자바스크립트 computed property 기존에는 자바스크립트 객체를 만들때 정해진 문자열 이름의 속성명을 사용해왔다. ​하지만 이제 ES6에서는 computed property를 사용하여 객체를 선언하는 순간에 변수

inpa.tistory.com

https://inpa.tistory.com/entry/TS-%F0%9F%93%98-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-Mapped-types-%EC%99%84%EB%B2%BD-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0

 

📘 타입스크립트 Mapped types 완벽 이해하기

타입스크립트 맵드 타입 타입스크립트의 고급 타입인 맵드 타입(mapped type)이란 기존에 정의되어 있는 타입을 새로운 타입으로 변환해 주는 문법을 의미 한다. 예를 들어 인터페이스에 있는 모든

inpa.tistory.com

https://velog.io/@wlwl99/TypeScript-type%EA%B3%BC-interface%EC%9D%98-%EC%B0%A8%EC%9D%B4

 

TypeScript - type과 interface의 차이

TypeScript에서 type과 interface는 비슷한 역할을 하는 것 같은데, 어떤 경우에는 type을 사용하는 게 좋고 어떤 경우에는 interface를 사용하는 게 좋은지 궁금해서 알아보았다.extends 키워드를 이용해서

velog.io

https://velog.io/@ahsy92/TypeScript-%EC%9D%B8%EB%8D%B1%EC%8A%A4-%EC%8B%9C%EA%B7%B8%EB%8B%88%EC%B2%98

 

[TypeScript] 인덱스 시그니처

인덱스 시그니쳐(Index Signature)는 {key : T : U}형식으로 객체가 여러 Key를 가질 수 있으며 Key와 매칭되는 value를 가지는 경우 사용한다.인덱스 시그니처는 객체가 <key, vlaue>형식이며 key와 value의 타입

velog.io

 

'STUDY > Typescript' 카테고리의 다른 글

[Typescript] 타입 활용하기  (0) 2024.08.06
[Typescript] 타입 확장하기 & 좁히기  (0) 2024.07.30
[Typescript] 값과 타입  (1) 2024.07.24
[Typescript] 타입  (1) 2024.07.20
[Typescript] Typescript를 시작하기 전에  (1) 2024.07.19