잉여 속성 체크: 객체 리터럴을 타입이 명시된 변수에 할당할 때 해당타입의 속성유무와 그 외의 속성을 확인하여 에러를 발생시킴
잉여속성 체크를 피하는법: 임시 변수 사용
interface Room {
numDoors: number;
ceilingHeightFt: number;
}
const r: Room = {
numDoors: 1,
ceilingHeightFt: 10,
elephant: 'present', // 에러 발생
}
// ---------------
const obj = {
numDoors: 1,
ceilingHeightFt: 10,
elephant: 'present',
}
const r: Room = obj; // 정상
// -----------------
interface Options {
title: string;
darkMode?: boolean;
}
const intermediate = { darkmode: true, title: 'Ski Free' };
const o: Options = intermediate;
// 오류
const o: Options = { darkmode: true, title: 'Ski Free' };
// 객체 리터럴은 알려진 속성만 지정할 수 있지만 'Options' 형식에 'darkmode'이(가) 없습니다. 'darkMode'을(를) 쓰려고 했습니까?
darkMode의 m이 소문자여서 발생한 에러
공통속성 체크: 약한 타입(?:)과 관련된 할당문마다 수행하며 공통된 속성이 없으면 오류를 표시함!
만약 약한 타입으로만 이루어진 인터페이스가 존재시, 이를 타입으로 하는 값은 빈 객체 혹은 공통된 속성을 포함하는 값으로 이루어진다.
TS에서 일반 함수선언문보다 함수 표현식을 사용하여 함수의 매개변수부터 반환값까지 전체를 함수 타입으로 선언하는 것이 좋다!(재사용 측면에서)
함수 타입 선언의 장점
// 이렇게 쓰기보다
async function checkedFetch(input: RequestInfo, init?: RequestInit) {
const response = await fetch(input, init);
if (!response.ok) {
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;
};
→ 다른 함수의 시그니처 참조시, typeof fn
을 사용하자.
타입이나 인터페이스 접두어에 T or I를 붙이는것은 C#에서 비롯된 관례이므로 지양하자.
타입과 인터페이스의 다른점
extends
키워드를 통해 확장이 가능하다.type
은 확장할 수 있다. interface는 Xtype
키워드를 통해 더 간결하게 표현 가능concat
같은 메서드 사용 불가복잡한 타입 → 타입 별칭사용 추천
일관성과 보강의 관점에서 → 인터페이스 추천
캡틴판교는 인터페이스 추천(확장성 측면에서)
반복을 줄이는 방법
Pick
, Partial
과 같은 유틸리티 타입 사용typeof
ReturnType<typeof 함수>
제네릭 타입에서 매개변수를 제한하는법 → extends(’확장'이 아닌 ‘부분 집합'의 개념)
즉 이말의 의미는 <T extends “어떤 타입">
일 때, T가 “어떤 타입”의 부분집합이라고 생각하면 쉽다.
// K가 T의 key들만 올 수 있음(number | string | symbol)
type Pick<T, K extends keyof T> = {
[k in K]: T[k];
};
// 객체의 경우
interface Name {
first: string;
last: string;
}
// 위 보다
interface Name2 {
first: string;
last: string;
third: string;
}
// 이게 더 범위가 작으므로 올 수 있음
const couple2: DancingDuo<Name2> = [
{ first: 'Ginger', last: 'hi', third: 'ho' },
{ first: 'Ginger', last: 'hi', third: 'ho' },
];
인덱스 시그니처 → type IndexSig = { [property: string] : string };
인덱스 시그니처의 네 가지 단점
위와 같은 인덱스 시그니처는 런타임 때까지 객체의 속성을 알 수 없을 경우에만 사용!, 또한 안전한 접근을 위해
undefined
추가를 고려할 수 있지만 이 부분을 체크하는 코드가 필요!
Record: Record<’x’ | ‘y’ | ‘z’, number>
→ key 타입에 유연성 제공(값은 number
)
매핑된 타입: { [k in ‘x’ | ‘y’ | ‘z’]: number } or { [k in ‘a’ | ‘b’ | ‘c’]: k extends ‘b’ ? string: number; }
위와 같은 인덱스 시그니처보다 정확한 타입을 사용하자!
자바스크립트는 배열, 객체에 대한 인덱스 접근을 모두 문자열로 변환하여 인식한다.
그러나 타입스크립트는 숫자 키를 허용하여 문자열 키와 다른 것으로 인식한다.
interface Array<T> {
// ...
[n: number]: T;
}
이를 통해 타입 체크 시점에 오류를 잡아준다.(인덱스 시그니처로 사용된 number
타입은 버그를 잡기 위한 순수 TS 코드)
const xs = [1, 2, 3];
const x1 = xs['1']; // (x), 에러 발생(인덱스 식이 number형식이 아닌 string이라 에러)
Object.keys
같은 구문들은 여전히 문자열로 반환된다. 그러나 타입스크립트에서 for...in 문안에서 사용하는 것은 배열을 순회하는 코드 스타일에 대한 실용적인 허용이라고 생각하여 배열에 문자열 인덱스로 접근해도 된다.
for...in 은 for..of, for보다 타입이 불확실하면 몇 배나 느리다.
Array
타입이 사용하질 않은 메서드를 가지는게 싫다면 ArrayLike
타입 사용! → 여전히 키는 문자열
인덱스 시그니처에 number를 사용하기보다 Array나 튜플, 또는 ArrayLike타입을 사용하는 것이 좋다! → 어떤 특별한 의미를 지닌다는 의미에서
자바스크립트의 참조 타입들은 함수로 호출해서 인수로 넘길때, 그 안의 내용을 변경할 수 있다.
→ 이로 인해 발생하는 문제가 있어서 readonly
접근 제어자를 통해 제한할 수 있다.
readonly
arr.length = 0
→ X)pop
을 비롯한 다른 메서드를 호출할 수 없다.매개변수가 readonly이면
number[]
의 타입이 readonly number[]
의 타입보다 기능이 많아서 서브타입이 되어 readonly
배열에 할당할 수 있지만, 반대는 불가능하다.
readonly 사용시 장점으로 인터페이스를 명확히 하고 타입 안전성을 높일 수 있다.
readonly는 얕게 동작하는 것임을 명심하자!
interface ScatterProps {
xs: number[];
ys: number[];
xRange: [number, number];
yRange: [number, number];
color: string;
onClick: (x: number, y: number, index: number) => void;
}
// 실패에 닫힌 접근법: 오류에 적극적으로 대처
// 새로운 프로퍼티 추가시 onclick으로만 return true반환 ㅜㅜ..
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;
}
// 실패에 열린 접근법: 오류에 소극적으로 대처
// 새로운 프로퍼티 추가시 고려 X
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
);
}
// 매핑된 타입을 이용하면 ScatterProps에 속성이 추가되었을 때,
// 이 부분도 에러가 발생하여 이 부분을 고쳐야만 함 (선택 강제)
// -> 매핑된 타입을 고려하는 이유
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;
}
타입스크립트는 타입 추론기능이 제공되므로 굳이 불필요한 타입 구문을 추가하지 말자.
→ 어디에 타입 구문을 넣고, 어디를 빼야할지 아는 것이 중요하다!
함수의 인수로 객체를 넘기는 경우 비구조화 할당문을 사용하면 모든 지역변수의 타입이 추론되도록 할 수 있다.
이상적인 타입스크립트 코드는 함수/메서드 시그니처에 타입 구문을 포함하지만, 함수 내에서 생성된 지역 변수에는 타입 구문을 넣지 않는다.
객체 리터럴(잉여 속성 체크), 함수 반환에는 타입을 명시하는 것이 좋다.
→ 내부 구현의 오류가 사용자 코드 위치에 나타나는것을 방지하고, 개발자가 오류가 어디에서 났는지 알 수 있음
반환타입을 명시하면 함수에 대해 명확하게 알 수 있고, 명명된 타입을 사용할 수 있다.
다른 타입의 경우 변수를 재사용하지 말고 별도의 변수로 분리하자.
const id = '12-34-56';
fetchProduct(id);
{
const id = 123456; // id 대신 serial을 사용하자!
fetchProductBySerialNumber(id);
}
위와 같이 스코프가 다른경우 서로 아무런 관계 없이 사용할 수 있지만 사람에게 혼동을 줄 수 있기 때문에 목적이 다른 곳에는 별도의 변수명을 사용하는 것이 좋다.