티스토리 뷰

3.1 타입스크립트만의 독자적 타입 시스템


3.1.1 any 타입

  • 자바스크립트에 존재하는 모든 값을 오류 없이 받을 수 있다. (타입을 명시하지 않은 것과 동일한 효과)
  • any 타입은 자바스크립트의 동적 타이핑으로 돌아가는 것과 비슷한 결과를 가져온다.
  • 지양해야 할 패턴이지만 사용해야 한다면?
    • 개발 단계에서 임시로 값을 지정해야 할 때
    • 어떤 값을 받아올지 또는 넘겨줄지 정할 수 없을 때
    • 값을 예측할 수 없을때 암묵적으로 사용

3.1.2 unknown 타입

  • any 타입과 유사하게 모든 값이 할당될 수 있다.
  • any 제외한 다른 타입으로 선언된 변수에는 unknown 타입 값을 할당할 수 없다.
  • 무엇이 할당될지 모르는 상태의 타입
any unknown
- 어떤 타입이든 any 타입에 할당 가능
- any 타입은 어떤 타입으로도 할당 가능 (단, never 제외)
- 어떤 타입이든 unknown 타입에 할당 가능
- unknown 타입은 any 타입 외에 다른 타입으로 할당 불가능

 

  • unknown 타입은 어떤 타입이 할당 되었는 지 알 수 없음을 나타내기에 변수의 값을 가져오거나 내부 속성에 접근할 수 없다.
  • any와 유사하지만 타입 검사를 강제하고 타입이 식별된 후 사용할 수 있기에 any 보다는 안전하다.
// 할당하는 시점에서는 에러 발생하지 않음.
const unknownFunction: unknown = () => console.log("this is unknown type")

// 여기에서 에러 발생
unknownFunction();

'unknownFunction' is of type 'unknown'.

3.1.3 void 타입

  • 타입스크립트에서는 함수에서 명시적인 반환문을 작성하지 않으면 void 타입이 사용된다.
  • void 타입은 주로 함수 반환 타입으로 사용되지만 함수에 국한된 타입은 아니다.
    • void 타입으로 지정된 변수는 undefined 혹은 null 값만 할당할 수 있다.
    • 명시적인 의미 부여 관점에서는 undefined와 null 타입 키워드를 사용해 지정하는 것이 바람직하다.
  • 타입스크립트 컴파일러가 알아서 함수 타입을 void 로 추론해 준다.

3.1.4 never 타입

  • never 타입은 값을 반환할 수 없는 타입을 말한다.
  • never 타입은 모든 타입의 하위 타입이다. (never 자신을 제외한 어떤 타입도 never에 할당될 수 없다.)
/*
  throw 키워드를 사용하면 에러를 발생시킬 수 있는데, 이는 값을 반환하는 것으로 간주하지 않는다.
  따라서, 특정 함수가 실행중 마지막에 에러를 던진다면 반환타입은 never 이다.
*/
function generateError(res: Response): never {
	throw new Error(res.getMessage());
}

/*
  무한 루프를 실행하는 경우, 값을 반환하지 못한다.
*/
function checkStatus(): never {
	while (true) {
	 // ...
	}
}

3.1.5 Array 타입

  • 배열은 Array 키워드 외에도 []를 사용해서 직접 타입을 명시할 수 있다.
  • 정적 타입의 특성을 살려 명시적인 타입을 선언하여 해당 타입의 원소를 관리하는 것을 강제한다.
const array1: number[] = [1,2,3]
const array2: Array<number> = [1,2,3]

const array3: Array<number | string> = [1, "string"];
const array4: number[] | string[] = [1, "string"];
const array5: (number | string)[] = [1, "string"];
  • 튜플은 배열 타입의 하위 타입으로 길이 제한까지 추가한 타입 시스템이라고 볼 수 있다.
    • 배열의 특정 인덱스에 정해진 타입을 선언하는 것과 같다.
let tuple: [number] = [1];
tuple = [1,2] // 불가능
tuple = [1, "string"] // 불가능
  • 리액트는 16.8 버전부터 도입된 hook이라는 요소 중, useState는 튜플 타입을 반환한다.
import { useState } from "react";

// 첫번째 요소는 훅으로 부터 생성 및 관리되는 상태 값
// 두번째 요소는 해당 상태를 조작할 수 있는 setter 를 의미한다.
const [value, setValue] = useState(false);

// 구조분해할당을 사용해 사용자가 자유롭게 이름을 정의할 수 있다.
const [username, setUsername] = useState("");

// 구조분해할당은 배열뿐만 아니라 객체에 대해서도 적용할 수 있다. 
// 하지만, 선언된 속성이름을 통해 값을 가져오므로 튜플보다 유연성은 다소 떨어질 수 있다.

const useStateWithObject = (initialValue: any) => {
 // ...
 return {value, setValue};
}

const { value, setValue } = useStateWithObject(false);
const { value: username, setValue: setUsername } = useStateWithObject('');
  • 스프레드 연산자를 사용하여 특정 인덱스에서 요소를 명확한 타입으로 선언하고 나머지 인덱스에서는 배열처럼 동일한 자료형의 원소를 개수 제한 없이 받도록 할 수 있다.
const httpStatusFromPaths: [number, string, ...string[]] = [
	400,
    "Bad Request",
    "/users/:id",
    "/users/:userId"
]

3.1.6 enum 타입

  • enum 타입은 열거형이라고도 부르는데 기본적인 추론 방식은 숫자 0부터 1씩 늘려가며 값을 할당한다.
  • 주로 문자열 상수를 생성하는 데 사용된다.
  • 열거형을 타입으로 가지는 변수는 해당 열거형이 가지는 모든 멤버를 값으로 받을 수 있다.
    • 타입 안정성, 명확한 의미 전달과 높은 응집력, 가독성
enum ProgramingLanguage {
	Typescript, // 0
	Javascript, //1
	JAVA, // 2
}

enum ProgramingLanguage {
	Typescript = "Typescript", 
	Javascript = "Javascript",
	JAVA = 300,
}
  • const enum 역방향으로의 접근을 허용하지 않기 때문에 자바스크립트에서의 객체에 접근하는 것과 유사한 동작을 보장한다.
enum ProgramingLanguage {
	Typescript = "Typescript", 
	Javascript = "Javascript",
	JAVA = 300,
}

ProgramingLanguage[200]; // undefined 출력하지만 별다른 에러 발생시키지 않는다.

const enum ProgramingLanguage { // 다음과 같이 선언하면 문제 방지할 수 있다.
	Typescript = "Typescript", 
	Javascript = "Javascript",
	JAVA = 300,
}

3.2 타입 조합


3.2.1 교차타입(Intersection)

  • 여러 가지 타입을 결합하여 하나의 단일 타입으로 만들 수 있다.
type ProductItem = {
	id: number;
	name: string;
	type: string;
}

// ProductItem 의 모든 멤버와 discountAmount 까지 멤버로 가지게 된다.
type ProductItemWithDiscount = ProductItem & { discountAmount : number };

3.2.2 유니온 타입(Union)

  • A 또는 B 타입 중 하나가 될 수 있는 타입을 말하며 A | B로 표기한다.

3.2.3 인덱스 시그니처(Index Signatures)

  • 특정 타입의 속성 이름은 알 수 없지만 속성값의 타입을 알고 있을 때 사용한다.
  • 인덱스 시그니처를 선언할 때 다른 속성을 추가로 명시할 수 있는데 인덱스 시그니처에 포함되는 타입이어야 한다.
interface IndexSignatureEx {
	[key: string]: number | boolean;
	length: number;
	isValid: boolean;
	name: string; // 에러 발생
}

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

  • 인덱스드 액세스 타입은 다른 타입의 특정 속성이 가지는 타입을 조회하기 위해 사용된다.
type Example = {
	a: number;
	b: string;
	c: boolean;
}

type IndexedAccess = Example["a"] // number
type IndexedAccess2 = Example["a" | "b"] // number | string
  • 배열의 요소 타입을 조회하기 위해 사용하는 경우도 있다.
const PromotionList = [
  { type: "product", name: "chicken" },
  { type: "product", name: "pizza" },
  { type: "card", name: "cheer-up" },
];

// type PromotionItemType = { type: string; name: string }
type PromotionItemType = typeof PromotionList[number];

3.2.5 맵드 타입(Mapped Types)

  • 다른 타입을 기반으로 한 타입을 선언할 때 사용하는 문법이다.
    • 인덱스드 시그니처 문법을 사용해서 반복적인 타입 선언을 줄일 수 있다.
    type Example = {
        a: number;
        b: string;
        c: boolean;
    }
    
    type Subset<T> = {
        [k in keyof T]? : T[k];
    }
    
    const aExample: Subset<Example> = {a : 3};
    const bExample: Subset<Example> = {b : "hello"};
  • 맵드 타입에서는 매핑할 때는 readonly (읽기 전용)와?(선택적 매개변수/옵셔널 파라미터) 수식어로 적용할 수 있다.
    • 해당 수식어를 제거할 수도 있는데, 기존 타입에 존재하던 readonly 나? 앞에 - 을 붙여주면 된다.

3.2.6 템플릿 리터럴 타입(Template Literal Types)

  • 템플릿 리터럴 문자열을 사용하여 문자열 리터럴 타입을 선언할 수 있는 문법
type Stage = 
| "init"
| "select-image"

type StageName = `${Stage}-stage`;

3.2.7 제네릭(Generic)

  • 함수, 타입, 클래스 등에서 내부적으로 사용할 타입을 미리 정해두지 않고, 실제로 그 값을 사용할 때 외부에서 타입을 지정하여 사용하는 방식
  • 변수명으로 T(Type), E(Element), K(Key), V(Value) 등.. 사용
  • 제네릭을 사용할 때 어떤 타입이든 될 수 있다는 개념을 알아야 한다.
function exampleFunc<T>(arg: T): T[] {
	return new Array(3).fill(arg);
}

exampleFunc("hello") // T는 string 으로 추론된다.

interface TypeWithLength {
	length: number;
}

function exampleFunc2<T extends TypeWithLength>(arg: T): number {
	return arg.length;
}

3.3 제네릭 사용법


 

  • 제네릭의 장점은 다양한 타입을 받게 함으로써 코드를 효율적으로 재사용할 수 있는 것이다.
    • 어떤 함수의 매개변수 나 반환 값에 다양한 타입을 넣고 싶을 때 제네릭 사용
    • 함수의 매개변수와 반환 타입을 미리 선언하는 호출 시그니처를 사용할 때도 사용
    • 외부에서 입력된 타입을 클래스 내부에 적용
    • 제네릭 타입은 여러 타입을 상속받을 수 있으며 타입 매개 변수를 여러 개 둘 수도 있다.
      • <Key extends string>으로 제약하면 제네릭의 유연성을 잃어버린다.
      • <Key extends string | number> 유니온 타입을 상속해서 선언할 수 있다.
    • 유니온 타입으로 T가 여러 타입을 받게 할 수 있지만, 타입 매개변수가 여러 개일 때는 하나 더 추가하여 선언한다.
      • <T, K>
  • 보통 API 응답값의 타입을 지정할 때 많이 활용된다.
export interface MobileResponse<Data> {
	data: Data;
	statusCode: string;
	statusMessage?: string;
}
  • 제네릭을 굳이 사용하지 않아도 되는 타입에서 사용하면 코드 길이만 늘어나고 가독성을 해칠 수 있다.
  • any를 사용하게 되면 제네릭의 장점과 타입 추론 및 타입 검사를 할 수 있는 이점을 누릴 수 없게 된다.
  • 제네릭이 과하게 사용되면 가독성을 해치기에 의미 단위로 분할해서 사용해야 한다.