타입스크립트를 설치하면, 다음 두가지를 실행할 수 있다.
편집기를 통해 특정 시점의 추론된 타입 값을 알 수 있다.
타입 : 할당 가능한 값들의 집합, 범위
interface Person {
name: string;
}
interface Lifespan {
birth: Date;
death?: Date;
}
type PersonSpan = Person & Lifespan;
const person: PersonSpan = {
name: "Alan Turing",
birth: new Date("1912/06/23"),
death: new Date("1954/06/07"),
}; // OK
타입 연산자는 인터페이스의 속성이 아닌, 값의 집합(타입의 범위)에 적용
된다. 그리고 추가적인 속성을 가지는 값도 여전히 그 타입에 속한다.
Person와 LifeSpan의 인터섹션은 Person의 범위와 LifeSpan의 범위의 인터섹션이다. 그래서 Person과 Lifespan을 둘 다 가지는 값은 인터섹션(&)에 속하게 된다.
name, birth, death 보다 더 많은 속성을 가지는 값도 PersonSpan 타입에 속한다.
type PersonSpan = Person | Lifespan; // Type is never
유니온 타입에 속하는 값은 어떤 키도 없기에 유니온에 대한 keyof는 공집합(never)이다.
타입스크립트에서는 PersonSpan에 Person이 올지 Lifespan이 올지 모르니까 두 타입의 공통 속성에만 접근가능하다.
keyof (A&B) = (keyof A) | (keyof B)
keyof (A|B) = (keyof A) & (keyof B)
상속을 구현할때는 extends 키워드를 사용한다.
타입스크립트의 심벌(symbol)은 타입 공간이나 값 공간 중의 한 곳에 존재한다.
iinterface Cylinder { // 타입
radius: number;
height: number;
}
const Cylinder = (radius: number, height: number) => ({radius, height}); // 값
function calculateVolume(shape: unknown) {
if (shape instanceof Cylinder) {
shape.radius
// ~~~~~~ Property 'radius' does not exist on type '{}'
}
}
instanceof는 자바스크립트의 런타임 연산자이고, 값에 대해서 연산을 한다.
한 심벌이 타입인지 값인지 확인하기 위해서는 문맥으로 파악해야한다.
일반적으로 type이나 interface, 타입선언(:), 단언문(as) 다음에 오는 심벌은 타입인 반면, const나 let에 쓰이는 거나 = 다음에 나오는 것은 값이다.
class와 enum은 상황에 따라서 타입과 값 두가지 모두 가능한 예약어이다.
typeof는 타입에서 쓰일 때와 값에서 쓰일 때 다른 기능을 한다.
// 타입의 관점 : 값을 읽어서 타입을 반환
type T1 = typeof p; // 타입은 Person
type T2 = typeof email; // 타입은 (p: Person, subject: string, body: string) => Response
// 값의 관점 : 자바스크립트 런타임의 typeof 연산자, 대상 심벌의 런타임 타입을 가리키는 문자열을 반환
const v1 = typeof p; // 값은 "object"
const v2 = typeof email; // 값은 "function"
타입 선언 | 타입 단언 | |
---|---|---|
타입 부여 | const alice : Person = { name : ‘Alice’ } | const bob = { name : ‘bob’ } as Person |
타입 결과 | Person | Person |
타입 체커 | 할당되는 값이 해당 인터페이스를 만족하는지 검사 | 강제로 타입을 지정한 거라 타입 체크 오류를 무시함 |
타입 속성 추가 | 불가능, 잉여 속성 체크 동작 | 가능 |
사용법 | 화살표함수의 리턴 타입 명시할때 | 개발자가 판단하는 타입이 더 정확할 때 |
ex. DOM타입 |
객체 리터럴
을 할당할때 타입스크립트는 해당 타입의 속성이 있는지, 그 외의 속성은 없는지 확인(p.61)래퍼객체(wrapper object) 1. Number 2. String 3. Boolean
자바스크립트는 기본형을 String 객체로 래핑(wrap)하고, 메서드를 호출하고, 마지막에 래핑한 객체를 버린다.
타입스크립트는 기본형과 객체 레퍼 타입을 별도로 모델링한다.
기본형 타입을 사용하자!
잉여 속성 체크 : 객체 리터럴을 변수에 할당하거나 함수에 매개변수로 전달할 때 “그 외의 속성은 없는지” 확인
interface Room {
numDoors: number;
ceilingHeightFt: number;
}
const r: Room = {
numDoors: 1,
ceilingHeightFt: 10,
elephant: "present",
// ~~~~~~~~~~~~~~~~~~ Object literal may only specify known properties,
// and 'elephant' does not exist in type 'Room'
};
잉여 속성 체크 ≠ 할당 가능 검사 이다. 위 예제 코드를 실행하면 런타임에서는 오류가 발생하지 않는다.
잉여 속성 체크는 구조적 타이핑 시스템에서 허용되는 속성의 오타 같은 실수를 잡는데 효과적이다.
interface LineChartOptions {
logscale?: boolean;
invertedYAxis?: boolean;
areaChart?: boolean;
}
const opts = { logScale: true };
const o: LineChartOptions = opts;
// ~ Type '{ logScale: boolean; }' has no properties in common
// with type 'LineChartOptions'
타입 단언(as)을 사용하면 잉여 속성 체크가 되지 않는다.
function rollDice1(sides: number): number {
/* COMPRESS */ return 0; /* END */
} // 문장
const rollDice2 = function (sides: number): number {
/* COMPRESS */ return 0; /* END */
}; // 표현식
const rollDice3 = (sides: number): number => {
/* COMPRESS */ return 0; /* END */
}; // 표현식
함수 표현식을 사용하면 함수의 매개변수부터 반환값까지 전체를 함수 타입으로 선언하여 함수 표현식에 재사용할 수 있다.
function add(a: number, b: number) {
return a + b;
}
function sub(a: number, b: number) {
return a - b;
}
function mul(a: number, b: number) {
return a * b;
}
function div(a: number, b: number) {
return a / b;
}
type BinaryFn = (a: number, b: number) => number; // 하나의 타입구문만 사용
const add: BinaryFn = (a, b) => a + b;
const sub: BinaryFn = (a, b) => a - b;
const mul: BinaryFn = (a, b) => a * b;
const div: BinaryFn = (a, b) => a / b;
매개변수나 반환 값에 타입을 명시하기보다는 함수 표현식 전체에 타입 구문을 적용하는 것이 더 좋다.
// 매개변수에 타입 명시
async function checkedFetch(input: RequestInfo, init?: RequestInit) {
const response = await fetch(input, init);
if (!response.ok) {
// Converted to a rejected Promise in an async function
throw new Error("Request failed: " + response.status);
}
return response;
}
// ✨ 함수 표현식 전체에 타입 구문 적용
declare function fetch(
input: RequestInfo,
init?: RequestInit
): Promise<Response>;
const checkedFetch: typeof fetch = async (input, init) => {
const response = await fetch(input, init);
if (!response.ok) {
throw new Error("Request failed: " + response.status);
}
return response;
};
인덱스 시그니처는 인터페이스와 타입에서 모두 사용할 수 있다. 또한 함수 타입도 인터페이스나 타입으로 정의할 수 있다.
// 타입
type TState = {
name: string,
capital: string,
};
// 인터페이스
interface IState {
name: string;
capital: string;
}
// 인덱스 시그니처
type TDict = { [key: string]: string };
interface IDict {
[key: string]: string;
}
// 함수 타입
type TFn = (x: number) => string;
interface IFn {
(x: number): string;
}
const toStrT: TFn = (x) => "" + x; // OK
const toStrI: IFn = (x) => "" + x; // OK
인터페이스는 타입을 확장할 수 있으며 타입은 인터페이스를 확장할 수 있다.
interface IState {
name: string;
capital: string;
}
interface IState {
population: number;
}
const wyoming: IState = {
name: "Wyoming",
capital: "Cheyenne",
population: 500_000,
}; // OK
같은 코드를 반복하지 말라(DRY, don’t repeat yourself)
타입스크립트에서는 타입에 인덱스 시그니처를 명시하여 유연하게 매핑을 표현할 수 있다.
// 인덱스 시그니처 [property: stirng]: string
type Rocket = { [property: string]: string };
const rocket: Rocket = {
name: "Falcon 9",
variant: "v1.0",
thrust: "4,940 kN",
}; // OK
하지만 네 가지 단점이 있다.
자바스크립트의 객체는 키와 값의 모음. 키는 값은 어떤 것이든 될 수 있다.
const x = {};
x[[1, 2, 3]] = 2; // 키를 객체로 사용해도 가능
console.log(x); // {'1,2,3': 2};
자바스크립트는 ‘해시 가능’ 객체라는 표현이 없기 때문에 문자열이 아닌 더 복잡한 객체를 키로 사용하려 한다면, 내부적으로 toString 메서드가 호출되어 객체를 문자열로 반환하여 키로 사용해야한다.
숫자를 키로 사용할 수 없어서 속성 이름으로 숫자로 사용한다면, 런타임시 문자열로 변환된다.
숫자로 인덱싱하는 배열 또한 객체로서 배열의 모든 숫자 인덱스들은 문자열로 변환되어 사용된다.
const x = {
1: 2,
3: 4,
};
console.log(x); // {'1': 2, '3': 4};
console.log(typeof []); // 배열의 타입은 객체 ('object' )
const x = [1, 2, 3];
// 배열에 숫자 인덱스로 배열 요소에 접근 가능
console.log(x[0]); // 1
// 인덱스가 문자열로 변환되어 문자열 키로도 배열 요소에 접근 가능
console.log(x["1"]); // 2
// 키가 문자열로 출력
console.log(Object.keys(x)); // ['0', '1', '2']
// 키의 타입이 문자열
console.log(typeof Object.keys(x)[0]); // 'string'
타입스크립트는 숫자 키를 허용하며 문자열 키와는 다른 것으로 인식한다.
// lib.es5.d.ts
interface Array<T> {
...
[n: number]: T; // 숫자 키
}`
const xs = [1, 2, 3];
const x0 = xs[0]; // OK
const x1 = xs['1'];
// ~~~~ Element implicitly has an 'any' type
// because index expression is not of type 'number' ( 숫자 키와 문자열 키가 다름 )
readonly
불필요한 리렌더링을 막기 위한 최적화 방법
interface ScatterProps {
// The data
xs: number[];
ys: number[];
// Display
xRange: [number, number];
yRange: [number, number];
color: string;
// Events
onClick: (x: number, y: number, index: number) => void;
}
function shouldUpdate(
oldProps: ScatterProps,
newProps: ScatterProps
) {
let k: keyof ScatterProps;
for (k in oldProps) {
if (oldProps[k] !== newProps[k]) {
if (k !== 'onClick') return true;
}
}
return false;
}
function shouldUpdate(
oldProps: ScatterProps,
newProps: ScatterProps
) {
return (
oldProps.xs !== newProps.xs ||
oldProps.ys !== newProps.ys ||
oldProps.xRange !== newProps.xRange ||
oldProps.yRange !== newProps.yRange ||
oldProps.color !== newProps.color
// (no check for onClick)
);
const REQUIRES_UPDATE: {[k in keyof ScatterProps]: boolean} = { // 타입 체커 동작
xs: true,
ys: true,
xRange: true,
yRange: true,
color: true,
onClick: false,
};
function shouldUpdate(
oldProps: ScatterProps,
newProps: ScatterProps
) {
let k: keyof ScatterProps;
for (k in oldProps) {
if (oldProps[k] !== newProps[k] && REQUIRES_UPDATE[k]) {
return true;
}
}
return false;
}