[우아한 타입스크립트(with 리액트)] 3장. 고급 타입

Rachel·2024년 4월 4일
0
post-thumbnail

우아한형제들 웹프론트엔드개발그룹, 『우아한 타입스크립트 with 리액트』, 한빛미디어(2023) p.80 ~ 118

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

출처: 한 입 크기로 잘라먹는 타입스크립트

1. any 타입

타입을 명시하지 않은 것과 동일한 효과.

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

어쩔 수 없이 any 타입을 사용해야 할 때 (되도록 사용 x)

  1. 개발 단계에서 임시로 값을 정해야 할 때
  2. 어떤 값을 받아올지 또는 넘겨줄지 정할 수 없을 때
  3. 값을 예측할 수 없을 때 암묵적으로 사용

2. unknown 타입

무엇이 할당될지 아직 모르는 상태의 타입

  • any 타입과 유사하게 모든 타입의 값이 할당될 수 있지만, unknown 타입은 any 타입 외에 다른 타입으로 할당 불가능

  • unknown 타입으로 선언된 변수는 값을 가져오거나 내부 속성에 접근할 수 없다.

  • any 타입과 유사하지만 타입 검사를 강제하고 타입이 식별된 후에 사용할 수 있기 때문에 any 타입보다 더 안전하다.
    -> 데이터 구조를 파악하기 힘들 때 any 타입 대신 unknown 타입으로 대체해서 사용하는 방법이 권장됨

3. void 타입

자바스크립트에서는 함수에서 명시적인 반환문을 작성하지 않으면 기본적으로 undefined가 반환된다.
타입스크립트에서 함수가 어떤 값을 반환하지 않는 경우에는 void를 지정하여 사용한다.
(void 타입으로 지정된 변수는 undefined 또는 null 값만 할당할 수 있다.)

4. never 타입

값을 반환할 수 없는 타입.

  • 에러를 던지는 경우
    throw
  • 무한히 함수가 실행되는 경우
    무한 루프
  • never 타입은 모든 타입의 하위 타입 = never 자신을 제외한 어떤 타입도 never 타입에 할당될 수 없다 (any도 x)

5. Array 타입

자바스크립트에서도 Object.prototype.toString.call(...) 연산자를 사용해 확인 가능.
-> 객체의 타입을 알아내는 함수, 객체의 인스턴스까지 알려줌.

  • 대괄호([])를 이용해 직접 타입을 명시할 경우 배열보다 더 좁은 범위인 튜플(Tuple)을 가리킨다.
  • 대괄호 안에 선언하는 타입의 개수가 튜플이 가질 수 있는 원소의 개수를 나타냄.
    = 튜플은 배열의 특정 인덱스에 정해진 타입을 선언하는 것과 같다.
    -> 각 배열 원소의 명확한 의미와 쓰임을 보장할 때 더욱 안전하게 사용할 수 있다.
const [value, setValue] = useState(false);

✏️ 튜플의 유용한 쓰임새 - 리액트 useState는 튜플 타입 반환
첫 번째 요소 = 훅으로부터 생성 및 관리되는 상태 값
두 번째 요소 = 해당 상태를 조작할 수 있는 세터를 의미

useState API는 배열 원소의 자리마다 명확한 의미를 부여하기 때문에 컴포넌트에서 사용하지 않는 값에 접근하는 오류를 방지할 수 있다. 또한 구조 분해 할당을 사용해 사용자가 자유롭게 이름 정의 가능.

  • 스프레드 연산자를 사용해 특정 인덱스에서 요소를 명확한 타입으로 선언하고, 나머지 인덱스에서는 배열처럼 동일한 자료형의 원소를 개수 제한 없이 받도록 할 수 있음 ([number, string, ...string[]])

옵셔널(optional) = ?
특정 속성 또는 매개변수가 값이 있을 수도 있고 없을 수도 있는 것을 의미
필수 X, 선택 o

6. enum 타입

열거형. 일종의 구조체를 만드는 타입 시스템.

  • 명명한 각 멤버의 값을 스스로 추론. 기본적인 추론 방식은 숫자 0부터 1씩 늘려가며 값을 할당하는 것.
  • 각 멤버에 명시적으로 값을 할당할 수 있다.
  • 주로 문자열 상수를 생성하는데 사용됨 -> 응집력있는 구조체를 만들 수 있다.
  • 열거형은 그 자체로 변수 타입으로 지정할 수 있다.
    -> 열거형을 타입으로 가지는 변수는 해당 열거형이 가지는 모든 멤버를 값으로 받을 수 있다.
enum ItemStatusType {
  DELIVERY_HOLD = "DELIVERY_HOLD", // 배송 보류
  DELIVERY_READY = "DELIVERY_READY", // 배송 준비 중
  DELIVERING = "DELIVERING", // 배송 중
  DELIVERED = "DELIVERED", // 배송 완료
}

const checkItemAvailable = (itemStatus: ItemStatusType) => {
  switch (itemStatus) {
    case ItemStatusType.DELIVERY_HOLD:
    case ItemStatusType.DELIVERY_READY:
    case ItemStatusType.DELIVERING:
      return false;
    case ItemStatusType.DELIVERED:
    default:
      return true;
  }
};

-> 타입 안정성(명시하지 않은 다른 문자열은 받을 수 x), 명확한 의미 전달(타입이 다루는 값이 무엇인지 명확)과 높은 응집력(아이템 상태에 대한 값을 모아놓은 것), 가독성(명확, 쉽게 이해)이 좋다.

  • 할당된 값을 넘어서는 범위(undefined)로 역방향 접근을 막기 위해 const enum으로 선언하기도 함.
    -> 숫자 상수로 관리되는 열거형은 선언한 값 이외의 값을 할당하거나 접근할 때 이를 방지하지 못함.
    -> 🌟 문자열 상수 방식으로 선언한 열거형은 미리 선언하지 않은 멤버로 접근을 방지하므로 권장.

✏️ 열거형의 문제
열거형은 타입 공간과 값 공간에서 모두 사용됨
-> TS -> JS로 변환시 즉시 실행 함수(IIFE) 형식으로 변환된다.
하지만 일부 번들러에서 트리쉐이킹 과정 중 즉시 실행 함수로 변환된 값을 사용하지 않는 코드로 인식하지 못하는 경우가 발생할 수 있다.

해결 방법
const enum 사용
as const assertion을 사용해 유니온 타입으로 동일한 효과를 얻는다.


3.2 타입 조합

1. 교차 타입(Intersection) = &

여러가지 타입을 결합하여 하나의 단일 타입으로 만들 수 있다.
= 기존에 존재하는 다른 타입들을 합쳐서 해당 타입의 모든 멤버를 가지는 새로운 타입을 생성하는 것

2. 유니온 타입(Union) = |

타입 A 또는 타입 B 중 하나가 될 수 있는 타입
= 주로 특정 변수가 가질 수 있는 타입을 전부 나열하는 용도로 사용됨

3. 인덱스 시그니처(Index Signature) = [Key: K]: T

특정 타입의 속성 이름은 알 수 없지만 속성 값의 타입을 알고 있을 때 사용하는 문법
[KEY: K]: T꼴로 명시해주면 되는데 이는 해당 타입의 속성 키는 모두 K 타입이어야 하고 속성 값은 모두 T 타입을 가져야 한다는 의미

  • 인덱스 시그니처를 선언시 다른 속성을 추가로 명시해줄 수 있는데 이때 추가로 명시된 속성은 인덱스 시그니처에 포함되는 타입이어야 함
interface IndexSignatureEx {
  [key: string]: number;
}

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

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

type Example = {
  a: number;
  b: string;
  c: boolean;
};

type IndexedAccess = Example[keyof Example]; // number | string | boolean
type IndexedAccess2 = Example["a" | "b"]; // number | string

-> Example["a" | "b"]와 Example[a | b]는 동일한 결과를 가져옴. '' 생략 가능

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

type ElementOf<T> = (typeof T)[number];

type PromotionItemType = ElementOf<PromotionList>;

배열 타입의 모든 요소는 전부 동일한 타입을 가지며 배열의 인덱스는 숫자 타입이다. 따라서 number로 인덱싱하여 배열 요소를 얻은 다음에 typeof 연산자를 붙여주면 해당 배열 요소의 타입을 가져올 수 있다.

5. 맵드 타입(Mapped Types)

다른 타입을 기반으로 한 타입을 선언할 때 사용하는 문법.

  • 맵드 타입에서 매핑할 때는 readonly와 ?를 수식어로 적용할 수 있다. 제거도 가능.
  • as 키워드를 사용하여 키를 재지정할 수도 있음.

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

자바스크립트의 템플릿 리터럴 문자열을 사용하여 문자열 리터럴 타입을 선언할 수 있는 문법.

type Food = "apple" | "grape" | "bannana" | "donut";
type FoodName = `today-${Food}`

7. 제네릭(Generic)

일반화된 데이터 타입.
정적 언어에서 다양한 타입 간에 재사용성을 높이기 위해 사용하는 문법.

함수, 타입, 클래스 등에서 내부적으로 사용할 타입을 미리 정해두지 않고 타입 변수를 사용해서 해당 위치를 비워 둔 다음에, 실제로 그 값을 사용할 때 외부에서 타입 변수 자리에 타입을 지정하여 사용하는 방식을 말함.
-> 재사용성이 크게 향상됨.

보통 타입 변수명으로 T(Type), E(Element), K(Key), V(Value)

type ExampleArrayType<T> = T[];

const array1: ExampleArrayType<string> = ["치킨", "피자", "우동"];
  • 제네릭은 any처럼 아무 타입이나 무분별하게 받는 게 아니라, 배열 생성 시점에 원하는 타입으로 특정할 수 있다.
    -> 특정한 타입에서만 존재하는 멤버를 참조하려고 하면 안된다. (length 등)

✏️ 제네릭 tsx에서 사용시 주의점
화살표 함수에 제네릭 사용시 제네릭의 꺾쇠괄호와 태그의 꺾쇠괄호를 혼동하여 문제가 생길 수 있다.
-> extends 키워드를 사용해 컴파일러에게 특정 타입의 하위 타입만 올 수 있음을 확실히 알려주면 된다.
보통 제네릭 사용시 function 키워드로 선언하는 경우가 많다.


3.3 제네릭 사용법

1. 함수의 제네릭

어떤 함수의 매개변수나 반환 값에 다양한 타입을 넣고 싶을 때 제네릭을 사용할 수 있음.

2. 호출 시그니처의 제네릭

호출 시그니처 = 타입스크립트의 함수 타입 문법으로 함수의 매개변수와 반환 타입을 미리 선언하는 것을 말함.

3. 제네릭 클래스

외부에서 입력된 타입을 클래스 내부에 적용할 수 있는 클래스.

4. 제한된 제네릭

타입 매개변수에 대한 제약 조건을 설정하는 기능.

5. 확장된 제네릭

제네릭 타입은 여러 타입을 상속받을 수 있으며 타입 매개변수를 여러 개 둘 수도 있다.
제네릭의 유연성을 잃지 않으면서 타입을 제약해야 할 때는 타입 매개변수에 유니온 타입을 상속해서 선언하면 된다.

6. 제네릭 예시

현업에서 가장 많이 제네릭을 활용할 때 -> API 응답 값의 타입을 지정할 때

주의

  • 제네릭을 굳이 사용하지 않아도 되는 타입
  • any 사용
  • 가독성을 고려하지 않은 사용

읽어주셔서 감사합니다. 잘못된 내용이나 추가로 알면 좋은 내용 있으면 댓글로 남겨주세요 🙂

profile
기존 블로그: https://hi-rachel.tistory.com

0개의 댓글