[우타스] TS만의 독자적 타입 시스템

함민혁·2024년 8월 26일
0

우타스

목록 보기
4/5

들어가기전에

자바스크립트에 any타입이 있는가?
any타입은 타입스크립트에만 존재하는 독자적인 타입 시스템으로 간주됨

하지만 any타입의 개념은 이미 자바스크립트에서 사용됨. 이름 그대로 어떤 타입이든 매핑할 수 있는 성질을 가지고 있고, 이건 원래 자바스크립트의 사용방식과 일치하기 때문

지금 알아볼 모든 타입 시스템은 타입스크립트에만 존재하는 키워드이지만 그 개념은 자바스크립트에 기인한 타입시스템이라는 점을 인지하자

any타입

any타입은 타입스크립트로 달성하고자 하는 정적 타이핑을 무색하게 만듦
any타입은 지양해야할 패턴.회피하는 것이 좋은 습관

tsconfig.json에서 noImplicitAny옵션을 활성화하면 any타입에 대한 경고를 발생시킬 수 있음

하지만 어쩔 수 없이 사용해야할 때가 있음

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

세부항목에 대해 아직 타이빙 확정되지 않은 경우

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

type FeedbackModalParams = {
    show: boolean;
    content: string;
    cancelButtonText?: string;
    confirmButtonText?: string;
    beforeOnClose?: () => void;
    action?: any;
  };

acition속성이 any로 선언된 것을 볼 수 있음. action속성은 모달 차을 그릴 때 실행될 함수를 의미함
모달창을 화면에 그릴 때 다양한 범주의 액션에 따라 읹자의 개수나 타입을 일일이 명시하기 힘들 수 있음

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

그래도 지양해라~

unknown 타입

any와 유사하게 모든 타입의 값이 할당'될' 수 있음
그러나 any를 제외한 다른 타입으로 선언된 변수에는 unknown 타입 값을 할당'할' 수 없음

let unknownValue: unknown;

unknownValue = 100; // any 타입과 유사하게 숫자이든
unknownValue = "hello world"; // 문자열이든
unknownValue = () => console.log("this is any type"); // 함수이든상관없이할당이가능하지만

let someValue1: any = unknownValue; // (O) any 타입으로 선언된 변수를 제외한 다른 변수는 모두 할당이 불가
let someValue2: number = unknownValue; // (X)
let someValue3: string = unknownValue; // (X)

무엇이 할당될지 아직 모르는 상태의 타입을 말함 -> any와 똑같아보일 수 있음
왜 unknown이 추가된걸까?

// 할당하는 시점에서는 에러가 발생하지 않음
const unknownFunction: unknown = () => console.log("this is unknown type");

// 하지만 실행 시에는 에러가 발생; Error: Object is of type 'unknown'.ts (2571)
unknownFunction();

보면 할당할때는 에러가 안생기는데 실행하면 에러가 생김. 이거말고도 객체 내부에 접근하는 모든 시도에서 에러가 발생할거임.

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

any타입과 비교를 해보자

any타입은 어떤 값이든 허용됨.
any타입을 모두 허용하기 때문에 할당하는 시점에 any로 사용을 하고, 나중에 실행 시에 타입을 특정해주지 않아도 에러가 발생하지 않는다.
결국 깜빡하고 타입 지정을 누락할 경우 어떤 값이든 전달될 수 있기 때문에 런타임에 버그가 발생할 가능성이 높아진다는 얘기임

이런 상황을 보완하기 위해 등장한 타입이 unknown타입이다. any타입과 유사하지만 타입검사를 강제하고 타입이 식별된 후에 사용 가능하기 때문에 any타입보다 안전함.

결론 : 데이터 구조 파악이 힘든 경우 any보다는 unknown타입을 써라!

void타입

함수에 전달되는 매개변수의 타입과 반환하는 타입을 지정해야 함
타입스크립트에서 함수가 어떤 값을 반환하지 않는 경우에는 void를 지정하여 사용해야됨

일반적으로 함수 자체를 다른 함수의 인자로 전달하는 경우가 아니라면 void타입은 잘 명시하지 않음
왜냐면 반환문 없을때 알아서 void로 추론해주기 때문에

never타입

값을 반환할 수 없는 타입을 말함
값을 반환하지 않는 것, 값을 반환할 수 없는 것을 명확히 구분해야됨

에러를 던지는 경우

자바스크립트에서는 런타임에 의도적으로 에러를 발생시키고 캐치할 수 있음. throw 키워드를 사용하면 에러를 발생시키는데, 이는 값을 반환하는 거로 간주하지 않아서 타입은 never임

무한히 함수가 실행되는 경우

말그대로 무한루프니까 값을 반환을 못하니 타입이 never

never타입은 모든 타입의 하위 타입임. 즉, never자신을 제외한 어떤 타입도 never타입에 할당될 수 없음
심지어 any타입도 안됨

따라서 타입스크릡트에서는 조건부 타입을 결정할 때 특정 조건을 만족하지 않는 경우에 엄격한 타입 검사 목적으로 never타입을 명시적으로 사용하기도 함

Array타입

배열 타입을 가리키는 Array키워드는 자바스크립트에서도 확인할 수 있음.
근데 왜 소개하냐. 엄밀히 말하면 JS는 배열을 객체에 속하는 타입으로 분류함. 즉, JS는 배열을 단독으로 자료형으로 국한하지 않음
타입스크립트에서 Array라는 타입을 사용하기 위해서는 타입스크립트의 특수한 문법을 함꼐 다뤄야 함

타입스크립트는 배열의 크기까지 제한하지는 않지만 정적 타입의 특성을 살려 명시적인 타입을 선언하여 해당 타입의 원소를 관리하는 것을 강제

배열 타입 선언에는 두가지 방식이 있음

const array: number[] = [1,2,3];
const array: Array<number> = [1,2,3]; // 제네릭

차이점은 딱히 없음 개인의 선호,팀 컨벤션 따르거나 혼용하면 됨

만약!..숫자형과 문자열 등 여러 타입을 모두 관리해야 하는 배열을 선언하려면 유니온 타입을 사용

const array1: Array<number | string) = [1,"string"]
const array2: number[] | string[] = [1,"string]

const array3: (numver | array)[] = [1,"string"]

튜플은 길이까지 제한하여 원소 개수와 타입을 보장함. ex) let tuple: [number] = [1]

튜플의 쓰임새

리엑트 hook요소 중 useState는 튜플 타입을 반환함. 첫 원소는 훅으로부터 생성 및 관리되는 상태 값을 의미, 두번째 원소는 해당 상태를 조작할 수 있는 세터를 의미함.
배열 원소의 자리마다 명확한 의미를 부여하기 때문에 컴폰넌트에서 사용하지 않는 값에 접근하는 오류를 방지해줌.
구조분해 할당을 하니까 사용자가 자유롭게 이름도 정의할 수 있음

구조분해 할당은 배열말고 객체에 대해서도 적용 가능. 물론 객체의 경우 사전에 선언된 속성의 이름을 통해 값을 가져오므로 튜플보다 유언성은 다소 떨어짐

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

const [value,setValue] =useStateWithObject(false); // 정의된 속성이름으로 가져와야됨
const [vaue: userName, setValue: setUsername} = useStateWithObject('') //정 하고싶으면 일차적으로 먼저 접근 후 다른 이름 지정해야됨

enum 타입

열거형이라고도 부르는 타입스크립트에서 지원하는 트구샇ㄴ 타입
일종의 구조체를 만드는 타입 시스템
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;
  }
};

itemStatus의 타입이 문자열로 지정된 경우와 비교해보자

타입 안정성
ItemStatusType에 명시되지 않은 다른 문자열은 인자로 받을 수 없음. 따라서 타입 안정성이 우수함.

명확한 의미 전달과 높은 응집력
ItemStatusType 타입이 다루는 값이 무엇인지 명확함.

열거형은 관련이 높은 멤버를 모아 문자열 상수처럼 사용하고자 할 때 유용하게 쓰임

역방향 접근을 막기 위해 const enum으로 선언함, but 숫자 상수로 관리된느 열거형은 다른 값을 할당하거나 접근할 때 못 막음. 반면 문자열은 접근을 막아줌

따라서 문자열 상수 방식으로 열거형을 사용하는 것이 안전함

const enum NUMBER {
  ONE = 1,
  TWO = 2,
}
const myNumber: NUMBER = 100; // NUMBER enum에서 100을 관리하고 있지 않지만 이는 에러를 발생시키지 않는다

const enum STRING_NUMBER {
  ONE = "ONE",
  TWO = "TWO",
}
const myStringNumber: STRING_NUMBER = "THREE"; // Error

.
.
.

🫠

출처: 우아한 타입스크립트 with 리엑트

profile
Born to be FE developer 🧑🏻‍💻

0개의 댓글