러닝 타입스크립트 | ch 9. 타입 제한자

doodoo·2023년 3월 13일
0
post-thumbnail

1. top 타입

  • top 타입은 모든 값을 나타낸내는 타입.
  • 즉, 모든 타입은 top 타입에 할당할 수 있다.

any 다시 보기

  • any는 모든 타입이 올 수 있다.
  • 하지만 any는 타입 검사를 수행하지 않도록 명시적으로 지시하기 때문에 안정성이 부족하다는 문제점이 있다.
  • 어떤 값이든 될 수 있음을 나타내려면 unknown 타입이 훨씬 안전하다.

unknown

  • 타입스크립트에서 unknown은 진정한 top 타입이다.
  • 모든 타입을 허용한다는 점에서 any와 유사하지만, 주요 차이점은 unknown 타입의 값을 훨씬 더 제한적으로 취급한다.
    • 타입스크립트는 unknown 타입 값의 속성에 직접 접근할 수 없다.
    • unknown 타입은 top 타입이 아닌 타입에는 할당할 수 없다.
  • 타입스크립트가 unknown 타입에 접근 하려면 타입을 좁혀서 검사를 해야한다. 이때문에 unknownany보다 훨씬 안전하다.
function hello(name: unknown) {
  // typeof로 name의 타입을 검사한다. 
    if(typeof name === 'string') {
        console.log(name.toUpperCase());
    }
  // 타입을 검사하지 않으면 에러가 발생한다. 
    console.log(name.toUpperCase()); // Error: 'name' is of type 'unknown'.
}


2. 타입 서술어

  • 로직을 함수로 감싸면 타입을 좁힐 수 없다.
  • 타입스크립트에는 인수가 특정 타입인지 여부를 나타내기 위해 boolean 값을 반환하는 함수를 위한 구문이 있는데 이를 타입 서술어(사용자 정의 타입 가드)라고 한다.
  • 타입 서술어는 매개변수로 전달된 인수가 매개변수의 타입보다 더 구체적인 타입인지 여부를 나타내는 데 사용된다.
  • 선언 방법: 매개변수의 이름, is 키워드, 특정 타입
function typePredicate(input: WideType): input is NarrowType;
  • 주의할점
    • 타입 서술어는 false 조건에서 타입을 좁히기 때문에 타입 서술어가 입력된 타입 이상을 검사하는 경우 예상치 못한 결과를 얻을 수 있다.
    • 속성이나 값의 타입을 확인하는 것 이상을 수행해 잘못 사용하기 쉬우므로 가능하면 피하는 것이 좋다. 대부분은 간단한 타입 서술어만으로도 충분하다.


3. 타입 연산자

keyof

keyof 연산자는 인터페이스의 모든 키를 유니언 타입으로 생성한다.

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

function students(student: Student, key: keyof Student) {
    return student[key]; // OK 
}

const student = {
    name: "Smith",
    age: 22
}

students(student, "name"); // OK 
students(student, "age"); // OK
students(student, "address"); // Error: Argument of type '"address"' is not assignable to parameter of type 'keyof Person'.

typeof

  • typeof는 제공되는 값의 타입을 반환한다.
  • 자바스크립트의 typeof 연산자와 다르다. 타입스크립트의 typeof 연산자는 타입스크립트에서만 사용되며 컴파일하면 사라진다.
const original = {
    medium: "movie",
    title: "Mean Girls",
}

let adaptation: typeof original;

if(Math.random() > 0.5) {
    adaptation = { ...original, medium: "play" }; // OK 
} else {
    adaptation = { ...original, medium: 2 };
    // Error: Type 'number' is not assignable to type 'string'.
}

keyof typeof

  • typeof는 값의 타입을 검색하고, keyof는 타입에 허용된 키를 검색한다.
  • 타입스크립트는 두 키워드를 함께 연결해 값의 타입에 허용된 키를 검색할 수 있다.
const ratings = {
    imdb: 8.4,
    metacritic: 82,
}

// 인터페이스 생성 대신, keyof typeof 키워드를 사용해 
// 키가 ratings 값 타입의 키 중 하나여야 함을 나타낸다. 
function logRating(key: keyof typeof ratings) {
    console.log(ratings[key]);
}

logRating("imdb"); // OK 
logRating("invalid"); // Error: 
// Argument of type '"invalid"' is not assignable 
// to parameter of type '"imdb" | "metacritic"'.


4. 타입 어서션

  • 타입스크립트는 값의 타입에 대한 타입 시스템의 이해를 재정의하기 위한 구문으로 타입 어서션(type assertion)을 제공한다.
  • 타입 어서션을 사용하면 타입 시스템은 어서션을 따르고 값을 해당 타입으로 처리한다.
  • 타입 어셔션은 자바스크립트로 컴파일하면 사라진다.
  • 구문: 다른 타입을 의미하는 값의 타입 다음에 as 키워드를 작성한다.
const rawData = '["grace", "frankie"]';

// 타입: any 
JSON.parse(rawData);

// 타입: string[]
JSON.parse(rawData) as string[];

// 타입: [string, string]
JSON.parse(rawData) as [string, string];

// 타입: ["grace", "frankie"]
JSON.parse(rawData) as ["grace", "frankie"];

📌 이전에는 item as type 대신 <type>item 같은 구문을 사용했는데, 이 구문은 JSX와 호환되지 않고 .tsx 파일에서도 작동하지 않기 때문에 권장하지 않는다.


포착된 오류 타입 어서션

타입 어서션을 가능한 사용하지 않는 것이 좋다. 그러나 타입 어서션이 유용하고 심지어 필요한 경우가 종종 있다.

  • 오류를 처리할 때 타입 어서션이 매우 유용할 수 있다.
  • try 블록의 코드가 예상과 다른 객체를 발생시킬 수 있기 때문에 catch 블록에서 포착된 오류가 어떤 타입인지 아는 것은 보통 불가능하다. 또, 일부 프로젝트에서는 Error 객체 대신 문자열 또는 다른 값을 발생시키기도 한다.
  • Error 객체를 발생시킨다고 확신하면 타입 어서션을 사용해 포착된 어서션을 오류로 처리할 수 있다.
try {
    // 오류를 발생시키는 코드
} catch(error) {
  // Error 객체라고 가정된 error의 message 속성에 접근한다. 
    console.warn("Oh no!", (error as Error).message);
}

  • 발생된 오류가 예상된 오류 타입인지 확인하기 위해 타입 내로잉을 사용하는 것이 더 안전하다.
try {
    // 오류를 발생시키는 코드
} catch(error) {
  // error가 Error 클래스의 인스턴스인지를 검사해 
  // 콘솔에 Error의 message를 출력할지 error 자체를 출력할지 여부를 확인함 
    console.warn("Oh no!", error instanceof Error ? error.message : error);
}

non-null 어서션

  • null 또는 undefined를 포함할 수 있는 변수에서 null과 undefined를 제거할 때도 타입 어서션이 유용하다.
  • ! 키워드를 사용하면 null과 undefined를 제외한 전체 타입을 작성하지 않아도 된다.
  • 즉, non-null 어서션은 타입이 null 또는 undefined가 아니라고 간주한다.
// 타입 유추: Date | undefined
let maybeDate = Math.random() > 0.5
    ? undefined
    : new Date();

// 타입이 Date라고 간주됨 
maybeDate as Date;

// 타입이 Date라고 간주됨 
maybeDate!;

타입 어서션 주의 사항

  • 타입 어서션은 꼭 필요한 경우가 아니라면 사용하지 않는게 좋다.
  • 사용하는게 안전하다고 확실히 확신할 때만 사용해야 한다.

어서션 vs. 선언

  • 타입 선언을 위해 타입 애너테이션을 사용하는 것과 타입 변경을 위해 타입 어서션을 사용하는 것에는 차이가 있다.
    • 타입 애너테이션을 사용하면 할당 가능성 검사를 수행한다.
    • 타입 어서션은 타입스크립트에 타입 검사 중 일부를 건너뛰도록 명시적으로 지시한다.
  • 변수에 초깃값을 할당한다면 타입 애너테이션을 사용하거나 변수의 타입을 유추하도록 하는 것이 바람직하다.

어서션 할당 가능성

  • 타입 중 하나가 다른 타입에 할당 가능한 경우에만 두 타입 간의 타입 어서션을 허용한다.
// 원시 타입은 서로 관련이 없으므로 하나의 원시타입에서 다른 원시타입으로 전환하는 것은 허용되지 않는다. 
let myValue = "string" as number;
// Error: Conversion of type 'string' to type 'number' may be a mistake 
// because neither type sufficiently overlaps with the other. 
// If this was intentional, convert the expression to 'unknown' first.

  • 하나의 타입에서 값을 완전히 관련 없는 타입으로 전환해야 하는 경우 이중 타입 어서션을 사용한다.
  • 먼저 값을 anyunknown 같은 top 타입으로 전환한 다음, 그 결과를 관련 없는 타입으로 전환한다.
  • 이중 타입 어서션은 위험하고 문제가 될 수 있기 때문에 사용하지 않는 것이 좋다.
// 허용되지만 이렇게 사용하면 안됨
let myValue = "string" as unknown as number; 


5. const 어서션

  • const 어서션은 모든 값을 상수로 취급해야 함을 나타낼 때 사용한다.
  • as const는 다음 세 가지 규칙을 적용한다.
    • 배열은 가변 배열이 아닌 읽기 전용 튜플로 취급한다.
    • 리터럴은 일반적인 원시 타입이 아닌 리터럴로 취급한다.
    • 객체 속성은 읽기 전용으로 간주한다.
// 배열은 가변 배열이 아닌 읽기 전용 튜플로 취급 

// 타입: (number | string)[]
[0, ''];

// 타입: readonly [0, '']
[0, ''] as const;

리터럴에서 원시 타입으로

as const를 사용하면 원시 타입을 좀 더 구체적인 특정 리터럴로 만들 수 있다.

// 타입: () => string
const getName = () => "Maria Bamford";

// 타입: () => "Maria Bamford"
const getNameConst = () => "Maria Bamford" as const;

읽기 전용 객체

  • as const를 사용하면
    • 객체의 속성은 readonly가 된다.
    • 원시 타입 속성은 고유한 리터럴 타입으로 간주된다.
    • 배열은 읽기 전용 튜플이 된다.
  • 즉, const 어서션을 적용하면 모든 멤버에 동일한 const 어서션 로직이 재귀적으로 적용된다.
const risa = {
    name: "Risa", // name: string
    age: 20, // age: number
    arr: ["apple", "banana"] // arr: string[]
};

const risaConst = {
    name: "Risa", // name: "Risa"
    age: 20, // age: 20
    arr: ["apple", "banana"] // arr: readonly ["apple", "banana"]
} as const;

risaConst.name; // OK 
risaConst.age; // OK 

risaConst.name = "Maya"; // Error: Cannot assign to 'name' because it is a read-only property.
risaConst.arr[0] = "orange"; // Error: Cannot assign to '0' because it is a read-only property.

0개의 댓글