let anyValue: any;
anyValue = 'abc';
anyValue = 123;
anyValue = true;
console.log(anyValue);
function greetComedian(name: any) {
// 타입 오류 없음
console.log(`Announcing ${name.toUpperCase()}!`);
}
// Runtime Error: name.toUpperCase is not a function
greetComedian({name: 'Authur'});
TypeScript에서 unknown 타입은 진정한 top 타입입니다.
unknown 타입은 모든 타입을 할당할 수 있습니다.
TypeScript는 unknown 타입의 값을 휠씬 더 제한적으로 취급합니다.
위의 두 가지 제한으로 인해 unknown이 any보다 훨씬 안전한 타입으로 사용됩니다.
unknown 타입 값의 속성에 접근하려고 시도하면, TypeScript는 타입 오류를 보고합니다.
function greetComedian(name: unknown) {
// Error: 'name' is of type 'unknown'
console.log(`Announcing ${name.toUpperCase()}!`);
}
function greetComedianSafely(name: unknown) {
if (typeof name === 'string') {
// name: string
console.log(`Announcing ${name.toUpperCase()}!`);
} else {
console.log('Get out');
}
}
greetComedianSafely('White') // 'Announcing WHITE!'
greetComedianSafely(123); // 'Get out'
let anyValue: any;
let unknownValue: unknown;
let str: string;
let num: number;
str = anyValue; // Ok
num = anyValue; // Ok
unknownValue = anyValue; // Ok
str = unknownValue; // Error: Type 'unknown' is not assignable to type 'string'
num = unknownValue; // Error: Type 'unknown' is not assignable to type 'number'
anyValue = unknownValue; // Ok
function isNumberOrString(value: unknown) {
return ['number', 'string'].includes(typeof value);
}
function logValueIfExists(value: number | string | null | undefined) {
if (isNumberOrString(value)) {
// value 타입 : number | string | null | undefined
// Error: 'value' is possibly 'null' or 'undefined'.
value.toString();
} else {
console.log('Value does not exist:', value);
}
}
타입 서술어(type predicate)는 인수가 특정 타입인지 여부를 나타내기 위해 boolean 값을 반환하는 함수를 위한 TypeScript의 특별한 구문입니다. 타입 서술어는 '사용자 정의 타입 가드(user-defined type guard)'라고도 부릅니다.
타입 서술어는 일반적으로 매개변수로 전달된 인수가 매개변수의 타입보다 더 구체적인 타입인지 여부를 나타내는 데 사용됩니다.
타입 서술어는 다음과 같이 서술됩니다.
function typePredicate(input: WideType): input is NarrowType;
function isNumberOrString(value: unknown): value is number | string {
return ['number', 'string'].includes(typeof value);
}
function logValueIfExists(value: number | string | null | undefined) {
if (isNumberOrString(value)) {
// value 타입 : number | string
value.toString();
} else {
// value 타입 : null | undefined
console.log('Value does not exist:', value);
}
}
interface Comedian {
funny: boolean;
}
interface StandupComedian extends Comedian {
routine: string;
}
function isStandupComedian(value: Comedian): value is StandupComedian {
return 'routine' in value;
}
function workWithComedian(value: Comedian) {
if (isStandupComedian(value)) {
// value : StandupComedian
console.log(value.routine);
}
// value : Comedian
console.log(value.routine);
// Error: Property 'routine' does not exist on type 'Comedian'
}
function isLongString(input: string | undefined) : input is string {
return !!(input && input.length > 7);
}
function workingWithText(text: string | undefined) {
if (isLongString(text)) {
// text: string
console.log('Long text:', text.length);
} else {
// text: undefined
console.log('Short text:', text?.length);
// Error: Property 'length' does not exist on type 'never'
}
}
interface Ratings {
audience: number;
critics: number;
}
function getRating(ratings: Ratings, key: string): number {
// Error: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Ratings'.
// No index signature with a parameter of type 'string' was found on type 'Ratings'.
return ratings[key];
}
const ratings: Ratings = { audience: 66, critics: 84 };
// Ok
getRating(ratings, 'audience');
// 허용되지만 사용하면 안 됨
getRating(ratings, 'not vaild');
interface Ratings {
audience: number;
critics: number;
[i: string]: number;
}
function getRating(ratings: Ratings, key: string): number {
// Ok
return ratings[key];
}
const ratings: Ratings = { audience: 66, critics: 84 };
// Ok
getRating(ratings, 'audience');
// Ok
getRating(ratings, 'not vaild');
interface Ratings {
audience: number;
critics: number;
}
function getRating(ratings: Ratings, key: 'audience' | 'critics'): number {
// Ok
return ratings[key];
}
const ratings: Ratings = { audience: 66, critics: 84 };
// Ok
getRating(ratings, 'audience');
// Error: Argument of type 'not vaild' is not assignable to parameter type 'audience' | 'critics'
getRating(ratings, 'not vaild');
interface에 수십 개 이상의 멤버가 있다면, 각 멤버의 키를 union 타입으로 모두 입력하고 최신 상태를 유지해야 하는 것이 번거로울 수 있습니다.
TypeScript에서는 기존에 존재하는 타입을 사용하고, 해당 타입에 허용되는 모든 키의 union 타입을 반환하는 keyof 연산자를 제공합니다.
function getCountKeyof(ratings: Ratings, key: keyof Ratings) {
// Ok
return ratings[key];
}
const ratings: Ratings = { audience: 66, critics: 84 };
getRating(ratings, 'audience');
// Error: Argument of type 'not vaild' is not assignable to parameter type 'keyof Rating'
getRating(ratings, 'not vaild');
const original = {
medium: 'movie',
title: 'Mean Girls'
};
let adaptation: typeof original;
if (Math.random() > 0.5) {
adaptation = {... original, medium: 'play'};
} else {
// Error: Type 'number' is not assignable to type 'string'
adaptation = {... original, medium: 2};
}
Note
- TypeScript의 typeof 연산자와 JavaScript의 typeof 연산자는 다릅니다.
- JavaScript의 typeof 연산자 : 타입에 대한 문자열을 반환하는 런타임 연산자
- TypeScript의 typeof 연산자 : 값의 타입을 반환하는 타입 연산자
const ratings = {
imdb: 8.4,
metacritic: 82,
};
function logRating(key: keyof typeof ratings) {
console.log(ratings[key]);
}
// Ok
logRating('imdb');
// Error: Argument of type '"invalid"' is not assignable to parameter of type '"imdb" | "metacritic"'.
logRating('invalid');
TypeScript는 값의 타입에 대한 타입 시스템의 이해를 재정의하기 위한 구문으로 type assertion(또는 type cast)을 제공합니다.
type assertion은 다른 타입을 의미하는 값의 타입 다음에 as 키워드를 배치하여 작성합니다.
예를 들어 JSON.parse는 의도적으로 top 타입인 any를 반환합니다. JSON.parse에 주어진 특정 문자열 값이 특정한 값 타입을 반환해야 한다는 것을 type assertion을 사용하여 타입 시스템에 알릴 수 있습니다.
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'];
const rawData = ['grace', 'frankie'];
// 타입 : any
JSON.parse(rawData);
// 타입 : string[]
JSON.parse(rawData);
// 타입 : [string, string]
JSON.parse(rawData);
// 타입 : ['grace', 'frankie']
JSON.parse(rawData);
try {
// 오류를 발생시키는 코드
} catch (error) {
console.warn('Oh no!', (error as Error).message);
}
try {
// 오류를 발생시키는 코드
} catch (error) {
console.warn('Oh no!', error instanceof Error ? error.message : error);
}
!
를 사용하여 non-null assertion을 할 수 있습니다.// maybeDate: Date | undefined
let maybeDate = Math.random() > 0.5 ? undefined : new Date();
// maybeDate: Date
maybeDate as Date;
// maybeDate: Date
maybeDate!;
const seasonCounts = new Map([
['I love Lucy', 'Me too'],
['The Golden Girls', 'Yep'],
]);
const maybeValue = seasonCounts.get('I love Lucy');
// Error: 'maybeValue' is possibly 'undefined'.
console.log(maybeValue.toUpperCase());
const maybeValue = seasonCounts.get('I love Lucy')!;
// Ok
console.log(maybeValue.toUpperCase());
type assertion은 any 타입과 마찬가지로 타입 시스템에 필요한 하나의 도피수단입니다.
type assertion은 종종 잘못되기도 합니다. 작성 당시 이미 잘못되었거나 코드베이스가 변경됨에 따라 나중에 달라지기도 합니다.
const seasonCounts = new Map([
['I love Lucy', 'Me too'],
['The Golden Girls', 'Yep'],
]);
const maybeValue = seasonCounts.get('I love Lucy')!;
// Ok
console.log(maybeValue.toUpperCase());
// Runtime TypeError: Cannot read property 'toUpperCase' of undefined.
interface Entertainer {
acts: string[];
name: string;
}
const declared: Entertainer = {
// Error: Property 'acts' is missing in type { name: string } but required in type 'Entertainer'.
name: 'Mons Mabley',
};
// 허용되지만 런타임 시 오류 발생
const asserted = {
name: 'Mons Mabley',
} as Entertainer;
// Runtime Error: Cannot read properties of undefined
console.log(declared.acts.join(', '));
console.log(asserted.acts.join(', '));
// 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.
let myValue = 'Stella' as number;
let myValue = '1337' as unknown as number; // 허용되지만 이렇게 사용하면 안 됨
const assertion은 배열, 원시 타입, 값, 별칭 등 모든 값을 상수로 취급해야 함을 나타내는 데 사용합니다.
as const는 수신하는 모든 타입에 다음 3 가지 규칙을 적용합니다.
배열 리터럴 뒤에 as const사 배치되면 배열이 튜플로 처리되어야 함을 나타냅니다.
// 타입 : (number | string)[]
[0, ''];
// 타입 : readonly [0, '']
[0, ''] as const;
타입 시스템이 리터럴 값을 원시 타입으로 확장하기보다 특정 리터럴로 이해하는 것이 유용할 때 리터럴에 const assertion을 사용합니다.
예를 들어 특정 리터럴을 생성한다고 알려진 함수에서 const assertion을 유용하게 사용할 수 있습니다.
// 타입 : () => string
const getName = () => 'Maria Bamford';
// 타입 : () => 'Maria Bamford'
const getNameCost = () => 'Maria Bamford' as const;
interface Joke {
quote: string;
style: 'story' | 'one-liner';
}
function tellJoke(joke: Joke) {
if (joke.style === 'one-liner') {
console.log(joke.quote);
} else {
console.log(joke.quote.split('\n'));
}
}
const narrowJoke = {
quote: 'If you stay alive for no other reason do it for spite.',
style: 'one-liner' as const,
}
// Ok
tellJoke(narrowJoke);
const wideObject = {
quote: 'Time flies when you are anxious!',
style: 'one-liner',
};
// Error: Argument of type '{ quote: string; style: string; }' is not assignable to parameter of type 'Joke'.
// Types of property 'style' are incompatible.
// Type 'string' is not assignable to type '"story" | "one-liner"'.
tellJoke(wideObject);
function describePreference(preference: 'maybe' | 'no' | 'yes') {
switch (preference) {
case 'maybe':
return 'I suppose...';
case 'no':
return 'No thanks.';
case 'yes':
return 'Yes please!';
}
}
// 타입 : { movie: string, standup: string }
const preferencesMutable = {
movie: 'maybe',
standup: 'yes',
};
// Error:
function describePreference(preference: 'maybe' | 'no' | 'yes') {
switch (preference) {
case 'maybe':
return 'I suppose...';
case 'no':
return 'No thanks.';
case 'yes':
return 'Yes please!';
}
}
// 타입 : { movie: string, standup: string }
const preferencesMutable = {
movie: 'maybe',
standup: 'yes',
};
// Error: Argument of type '{ movie: string; standup: string; }' is not assignable to parameter of type '"maybe" | "no" | "yes"'.
describePreference(preferencesMutable);
// 타입 : { readonly movie: 'maybe', readonly standup: 'yes' }
const preferencesReadonly = {
movie: 'maybe',
standup: 'yes',
} as const;
// Ok
describePreference(preferencesReadonly);
// Error: Cannot assign to 'movie' because it is a read-only property.
preferencesReadonly.movie = 'no';