[TypeScript 독학] #8 이펙티브 타입스크립트(2)
시작하며
지난글에 이어서 이펙티브 타입스크립트란 책을 읽으면서 중요하다고 생각하는 내용을 간단하게 정리해보려고 한다. 이번 내용은 다른 파트보다 내용이 많은 만큼 실무에서 변수들의 타입을 정의할 때 고민해야 하는 부분들에 대해 잘 다루고 있기 때문에, 한번 읽고 넘어가기 보다는 상황에 맞는 내용을 자주 찾아가면서 공부해야하는 책이라고 생각한다.
3장 타입 추론
타입을 정의하면 변수가 선언되는 시점에 잉여 속성체크가 작동한다.
function logProduct(product: Product) {
const id: number = product.id;
// ~~ Type 'string' is not assignable to type 'number'
const name: string = product.name;
const price: number = product.price;
console.log(id, name, price);
}
const elmo: Product = {
name: 'Tickle Me Elmo',
id: '048188 627152',
//~~ (property) Product.id: number 'string' 형식은 'number' 형식에 할당할 수 없습니다.
price: 28.99,
};
let과 const의 타입 추론
interface Vector3 { x: number; y: number; z: number; }
function getComponent(vector: Vector3, axis: 'x' | 'y' | 'z') {
return vector[axis];
}
const x = 'x'; // type is "x"
let vec = {x: 10, y: 20, z: 30};
getComponent(vec, x); // OK
let은 string 타입으로 추론하지만 const는 값이 변하지 않을 것이기 때문에 리터럴 타입으로 추론한다.
const v1 = {
x: 1,
y: 2,
}; // Type is { x: number; y: number; }
const v2 = {
x: 1 as const,
y: 2,
}; // Type is { x: 1; y: number; }
const v3 = {
x: 1,
y: 2,
} as const; // Type is { readonly x: 1; readonly y: 2; }
as const를 사용하면 최대한 좁은 범위로 타입 추론한다.
타입좁히기
const el = document.getElementById('foo'); // Type is HTMLElement | null
if (!el) throw new Error('Unable to find #foo');
el; // Now type is HTMLElement
el.innerHTML = 'Party Time'.blink();
//if(!el)을 사용한 타입 좁히기의 예
function contains(text: string, search: string|RegExp) {
if (search instanceof RegExp) {
search // Type is RegExp
return !!search.exec(text);
}
search // Type is string
return text.includes(search);
}
//instanceof를 사용한 타입 좁히기의 예
interface A { a: number }
interface B { b: number }
function pickAB(ab: A | B) {
if ('a' in ab) {
ab // Type is A
} else {
ab // Type is B
}
ab // Type is A | B
}
//in을 사용한 타입 좁히기
const el = document.getElementById('foo'); // type is HTMLElement | null
if (typeof el === 'object') {
el; // Type is HTMLElement | null
}
//null의 타입도 object이기 때문에 타입 좁히기 실패
function foo(x?: number|string|null) {
if (!x) {
x; // Type is string | number | null | undefined
}
}
//0,''으로 인해 타입 좁히기 실패
interface UploadEvent { type: 'upload'; filename: string; contents: string }
interface DownloadEvent { type: 'download'; filename: string; }
type AppEvent = UploadEvent | DownloadEvent;
function handleEvent(e: AppEvent) {
switch (e.type) {
case 'download':
e // Type is DownloadEvent
break;
case 'upload':
e; // Type is UploadEvent
break;
}
}
//명시적으로 type을 명시하여(태그된 유니온) 타입 좁히기
function isInputElement(el: HTMLElement): el is HTMLInputElement {
return 'value' in el;
}
function getElementContent(el: HTMLElement) {
if (isInputElement(el)) {
el; // Type is HTMLInputElement
return el.value;
}
el; // Type is HTMLElement
return el.textContent;
}
//사용자 정의 타입가드를 사용한 타입 좁히기
한꺼번에 객체 생성하기
interface Point { x: number; y: number; }
const pt = {} as Point;
pt.x = 3;
pt.y = 4; // OK
//객체를 나누어서 정의해야 한다면 타입 단언문으로 가능
interface Point { x: number; y: number; }
const pt = {x: 3, y: 4};
const id = {name: 'Pythagoras'};
const namedPoint = {};
Object.assign(namedPoint, pt, id);
namedPoint.name;
// ~~~~ Property 'name' does not exist on type '{}'
//namedPoint의 타입이 {}로 정해져서 할당 불가
interface Point { x: number; y: number; }
const pt = {x: 3, y: 4};
const id = {name: 'Pythagoras'};
const namedPoint = {...pt, ...id};
namedPoint.name; // OK, type is string
//여러 객체를 합쳐야 하는 경우 전개연산자로 타입 정의
declare let hasMiddle: boolean;
const firstLast = {first: 'Harry', last: 'Truman'};
const president = {...firstLast, ...(hasMiddle ? {middle: 'S'} : {})};
//전개 연산자와 조건부 연산자를 사용하여 선택적 필드 생성하는 법
declare let hasMiddle: boolean;
const firstLast = {first: 'Harry', last: 'Truman'};
function addOptional<T extends object, U extends object>(
a: T, b: U | null
): T & Partial<U> {
return {...a, ...b};
}
const president = addOptional(firstLast, hasMiddle ? {middle: 'S'} : null);
president.middle // OK, type is string | undefined
//헬퍼함수를 사용하여 선택적필드를 포함하는 새로운 객체 생성
일관성있는 별칭 사용하기
const borough = {name: 'Brooklyn', location: [40.688, -73.979]};
const loc = borough.location;
//객체의 요소에 별칭 부여
interface Coordinate {
x: number;
y: number;
}
interface BoundingBox {
x: [number, number];
y: [number, number];
}
interface Polygon {
exterior: Coordinate[];
holes: Coordinate[][];
bbox?: BoundingBox;
}
function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
const box = polygon.bbox;
if (polygon.bbox) {
if (pt.x < box.x[0] || pt.x > box.x[1] ||
// ~~~ ~~~ Object is possibly 'undefined'
pt.y < box.y[1] || pt.y > box.y[1]) {
// ~~~ ~~~ Object is possibly 'undefined'
return false;
}
}
// ...
}
//반복적으로 사용하는 값 polygon.bbox에 별칭(box) 부여
function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
const {bbox} = polygon;
if (bbox) {
const {x, y} = bbox;
if (pt.x < x[0] || pt.x > x[1] ||
pt.y < x[0] || pt.y > y[1]) {
return false;
}
}
// ...
}
//별칭을 사용하는 것보다 구조 분해 할당으로 변수를 뽑아내는 것이 더 좋음.
async/await 사용하기
function fetchURL(url: string, cb: (response: string) => void) {
cb(url);
}
const url1 = '1';
const url2 = '2';
const url3 = '3';
// END
fetchURL(url1, function(response1) {
fetchURL(url2, function(response2) {
fetchURL(url3, function(response3) {
// ...
console.log(1);
});
console.log(2);
});
console.log(3);
});
console.log(4);
// Logs:
// 4
// 3
// 2
// 1
async function fetchPages() {
const response1 = await fetch(url1);
const response2 = await fetch(url2);
const response3 = await fetch(url3);
// ...
}
//콜백보다는 프로미스나 async/await이 코드를 작성하기 쉽고 코드를 추론하기 쉽다.
async function fetchPages() {
const [response1, response2, response3] = await Promise.all([
fetch(url1), fetch(url2), fetch(url3)
]);
// ...
}
//여러 비동기처리를 병렬로 하고 싶을 때는 Promise.all, 구조분해할당, await을 사용하면 response1,2,3을 Response로 추론함
function fetchPagesCB() {
let numDone = 0;
const responses: string[] = [];
const done = () => {
const [response1, response2, response3] = responses;
// ...
};
const urls = [url1, url2, url3];
urls.forEach((url, i) => {
fetchURL(url, r => {
responses[i] = url;
numDone++;
if (numDone === urls.length) done();
});
});
}
//콜백을 사용하여 여러 비동기요청을 병렬처리하면 추론해야하는 타입이 많아짐.
function timeout(millis: number): Promise<never> {
return new Promise((resolve, reject) => {
setTimeout(() => reject('timeout'), millis);
});
}
async function fetchWithTimeout(url: string, ms: number) {
return Promise.race([fetch(url), timeout(ms)]);
}
//Promise.race를 사용하면 타입추론이 작동함
const _cache: {[url: string]: string} = {};
function fetchWithCache(url: string, callback: (text: string) => void) {
if (url in _cache) {
callback(_cache[url]);
} else {
fetchURL(url, text => {
_cache[url] = text;
callback(text);
});
}
}
//함수는 반드시 비동기/동기 중 한가지 방식으로만 작동해야 한다. 캐시된 경우 콜백함수가 동기로 호출되기 때문에 사용하기 어려워짐
// Function getJSON(url: string): Promise<any>
async function getJSON(url: string) {
const response = await fetch(url);
const jsonPromise = response.json(); // Type is Promise<any>
return jsonPromise;
}
//이미 promise인 경우 Promise<Promise<any>>가 아니라 Promise<any>로 인식함
타입 추론 작동 이해하기
type Language = 'JavaScript' | 'TypeScript' | 'Python';
function setLanguage(language: Language) { /* ... */ }
setLanguage('JavaScript'); // OK
let language = 'JavaScript';
setLanguage(language);
// ~~~~~~~~ Argument of type 'string' is not assignable
// to parameter of type 'Language'
//language를 선언한 시점에 타입이 string로 추론되어서 에러 발생
type Language = 'JavaScript' | 'TypeScript' | 'Python';
function setLanguage(language: Language) { /* ... */ }
let language: Language = 'JavaScript';
setLanguage(language); // OK
//language 선언 때 타입을 지정해주는 방법을 해결
const language = 'JavaScript';
setLanguage(language); // OK
//const를 사용하여 타입 제한
function panTo(where: [number, number]) { /* ... */ }
const loc = [10, 20] as const;
panTo(loc);
// ~~~ Type 'readonly [10, 20]' is 'readonly'
// and cannot be assigned to the mutable type '[number, number]'
//as const는 타입을 너무 과하게 추론하여 타입체크 미통과
function panTo(where: readonly [number, number]) { /* ... */ }
const loc = [10, 20] as const;
panTo(loc); // OK
//readonly를 사용하여 as const로 정의한 타입의 타입체크 통과
function callWithRandomNumbers(fn: (n1: number, n2: number) => void) {
fn(Math.random(), Math.random());
}
const fn = (a, b) => {
// ~ Parameter 'a' implicitly has an 'any' type
// ~ Parameter 'b' implicitly has an 'any' type
console.log(a + b);
}
callWithRandomNumbers(fn);
//콜백함수를 따로 선언할 경우 매개변수가 any로 추론됨
4장 타입 설계
유효한 상태만 표현하는 타입을 지향하기
interface State {
pageText: string;
isLoading: boolean;
error?: string;
}
//웹페이지 상태를 가정
declare let currentPage: string;
function renderPage(state: State) {
if (state.error) {
return `Error! Unable to load ${currentPage}: ${state.error}`;
} else if (state.isLoading) {
return `Loading ${currentPage}...`;
}
return `<h1>${currentPage}</h1>\n${state.pageText}`;
}
//error가 선택적 필드이기 때문에 발생한지 안한지 알 수 없음, 분기 부정확
async function changePage(state: State, newPage: string) {
state.isLoading = true;
try {
const response = await fetch(getUrlForPage(newPage));
if (!response.ok) {
throw new Error(`Unable to load ${newPage}: ${response.statusText}`);
}
const text = await response.text();
state.isLoading = false;
state.pageText = text;
} catch (e) {
state.error = '' + e;
}
}
//에러 발생시 로딩 상태가 false가 되는 로직없음
//error를 초기화하지 않아 이전 에러가 출력될 수 있음
interface RequestPending {
state: 'pending';
}
interface RequestError {
state: 'error';
error: string;
}
interface RequestSuccess {
state: 'ok';
pageText: string;
}
type RequestState = RequestPending | RequestError | RequestSuccess;
interface State {
currentPage: string;
requests: {[page: string]: RequestState};
}
//유니온 타입을 사용하여 웹페이지의 상태를 명시적으로 분리
사용할 때는 너그럽게, 생성할 때는 엄격하게
interface CameraOptions {
center?: LngLat;
zoom?: number;
bearing?: number;
pitch?: number;
}
type LngLat =
{ lng: number; lat: number; } |
{ lon: number; lat: number; } |
[number, number];
type LngLatBounds =
{northeast: LngLat, southwest: LngLat} |
[LngLat, LngLat] |
[number, number, number, number];
declare function setCamera(camera: CameraOptions): void;
declare function viewportForBounds(bounds: LngLatBounds): CameraOptions;
//LngLat과 LngLatBounds의 타입이 자유로워서 경우의 수가 많음
interface LngLat { lng: number; lat: number; };
type LngLatLike = LngLat | { lon: number; lat: number; } | [number, number];
interface CameraOptions {
center?: LngLatLike;
zoom?: number;
bearing?: number;
pitch?: number;
}
// interface Camera {
// center: LngLat;
// zoom: number;
// bearing: number;
// pitch: number;
// }
// interface CameraOptions extends Omit<Partial<Camera>, 'center'> {
// center?: LngLatLike;
// }
//를 명시적을 표현
type Feature = any;
declare function calculateBoundingBox(f: Feature): [number, number, number, number];
interface LngLat { lng: number; lat: number; };
type LngLatLike = LngLat | { lon: number; lat: number; } | [number, number];
interface Camera {
center: LngLat;
zoom: number;
bearing: number;
pitch: number;
}
interface CameraOptions extends Omit<Partial<Camera>, 'center'> {
center?: LngLatLike;
}
type LngLatBounds =
{northeast: LngLatLike, southwest: LngLatLike} |
[LngLatLike, LngLatLike] |
[number, number, number, number];
declare function setCamera(camera: CameraOptions): void;
declare function viewportForBounds(bounds: LngLatBounds): Camera;
function focusOnFeature(f: Feature) {
const bounds = calculateBoundingBox(f);
const camera = viewportForBounds(bounds);
setCamera(camera);
const {center: {lat, lng}, zoom} = camera; // OK
zoom; // Type is number
window.location.search = `?v=@${lat},${lng}z${zoom}`;
}
//매개변수의 타입이 반환타입보다 느슨한 경향이 있음
문서에 타입 정보를 쓰지 않기
/**
* Returns a string with the foreground color.
* Takes zero or one arguments. With no arguments, returns the
* standard foreground color. With one argument, returns the foreground color
* for a particular page.
*/
function getForegroundColor(page?: string) {
return page === 'login' ? {r: 127, g: 127, b: 127} : {r: 0, g: 0, b: 0};
}
//주석과 코드가 일치하지 않고 코드만 보아도 명확하게 알 수 있는 정보가 포함되어 있음
type Color = { r: number; g: number; b: number };
// END
/** Get the foreground color for the application or a specific page. */
function getForegroundColor(page?: string): Color {
// COMPRESS
return page === 'login' ? {r: 127, g: 127, b: 127} : {r: 0, g: 0, b: 0};
// END
}
//타입스크립트는 주석이 없이도 코드를 해석하기 용이하기 때문에 코드 자체가 주석의 역할을 함
function sort(nums: readonly number[]) { /* ... */ }
//변수를 변경하지 말라고 주석을 다는 것보다 readonly를 사용하여 규칙을 강제로 지키게 함
//ageNum을 사용하는 것보다 age:number를 사용하여 규칙을 강제하고 주석 역할을 함. 단, 단위가 있는 경우는 명시
타입 주변에 null 값 배치하기
function extent(nums: number[]) {
let min, max;
for (const num of nums) {
if (!min) {
min = num;
max = num;
} else {
min = Math.min(min, num);
max = Math.max(max, num);
// ~~~ Argument of type 'number | undefined' is not
// assignable to parameter of type 'number'
}
}
return [min, max];
}
//0이 포함되어 있는 경우나, nums 배열이 비어있는 경우 의도치 않은 값 리턴
function extent(nums: number[]) {
let result: [number, number] | null = null;
for (const num of nums) {
if (!result) {
result = [num, num];
} else {
result = [Math.min(num, result[0]), Math.max(num, result[1])];
}
}
return result;
}
//null이거나 아니거나로 나누어서 에러 해결
interface UserInfo { name: string }
interface Post { post: string }
declare function fetchUser(userId: string): Promise<UserInfo>;
declare function fetchPostsForUser(userId: string): Promise<Post[]>;
class UserPosts {
user: UserInfo | null;
posts: Post[] | null;
constructor() {
this.user = null;
this.posts = null;
}
async init(userId: string) {
return Promise.all([
async () => this.user = await fetchUser(userId),
async () => this.posts = await fetchPostsForUser(userId)
]);
}
getUserName() {
// ...?
}
}
//네트워크 요청이 로드되는 동안 user와 posts 속성은 null 상태여서 4가지의 경우의 수가 생김
유니온의 인터페이스보다는 인터페이스의 유니온을 사용하기
type FillPaint = unknown;
type LinePaint = unknown;
type PointPaint = unknown;
type FillLayout = unknown;
type LineLayout = unknown;
type PointLayout = unknown;
interface Layer {
layout: FillLayout | LineLayout | PointLayout;
paint: FillPaint | LinePaint | PointPaint;
}
//인터페이스 내의 유니온타입보다는
type FillPaint = unknown;
type LinePaint = unknown;
type PointPaint = unknown;
type FillLayout = unknown;
type LineLayout = unknown;
type PointLayout = unknown;
interface FillLayer {
layout: FillLayout;
paint: FillPaint;
}
interface LineLayer {
layout: LineLayout;
paint: LinePaint;
}
interface PointLayer {
layout: PointLayout;
paint: PointPaint;
}
type Layer = FillLayer | LineLayer | PointLayer;
//인터페이스 정의 후 유니온 타입을 정의
type FillPaint = unknown;
type LinePaint = unknown;
type PointPaint = unknown;
type FillLayout = unknown;
type LineLayout = unknown;
type PointLayout = unknown;
interface FillLayer {
type: 'fill';
layout: FillLayout;
paint: FillPaint;
}
interface LineLayer {
type: 'line';
layout: LineLayout;
paint: LinePaint;
}
interface PointLayer {
type: 'paint';
layout: PointLayout;
paint: PointPaint;
}
type Layer = FillLayer | LineLayer | PointLayer;
//인터페이스의 유니온을 사용하는 태그된 유니온 사용의 예
string 타입보다 더 구체적인 타입 사용하기
interface Album {
artist: string;
title: string;
releaseDate: string; // YYYY-MM-DD
recordingType: string; // E.g., "live" or "studio"
}
//구체적이지 않은 타입 설정
type RecordingType = 'studio' | 'live';
interface Album {
artist: string;
title: string;
releaseDate: Date;
recordingType: RecordingType;
}
//구체적으로 타입을 설정하기
type RecordingType = 'studio' | 'live';
interface Album {
artist: string;
title: string;
releaseDate: Date;
recordingType: RecordingType;
}
function pluck<T>(record: T[], key: keyof T) {
return record.map(r => r[key]);
}
//key of를 사용하여 T 객체 내의 값의 타입으로 지정
function pluck<T>(record: T[], key: keyof T) {
return record.map(r => r[key]);
}
declare let albums: Album[];
const releaseDates = pluck(albums, 'releaseDate'); // Type is (string | Date)[]
//keyof 를 사용하여더라도 releaseDates의 타입이 Date[]가 아닌 (string | Date)[]로 여전히 범위가 넓음
function pluck<T, K extends keyof T>(record: T[], key: K): T[K][] {
return record.map(r => r[key]);
}
//T[K]로 출력 값의 타입을 좁힘
부정확한 타입보다는 미완성 타입을 사용하기
type GeoPosition = [number, number];
interface Point {
type: 'Point';
coordinates: GeoPosition;
}
//위도와 경도를 나타내는 Point는 튜플타입을 사용했으나 고도와 같은 값이 추가될 경우 타입을 무시해야함
type Expression1 = any;
type Expression2 = number | string | any[];
type Expression4 = number | string | CallExpression;
type CallExpression = MathCall | CaseCall | RGBCall;
interface MathCall {
0: '+' | '-' | '/' | '*' | '>' | '<';
1: Expression4;
2: Expression4;
length: 3;
}
interface CaseCall {
0: 'case';
1: Expression4;
2: Expression4;
3: Expression4;
length: 4 | 6 | 8 | 10 | 12 | 14 | 16 // etc.
}
interface RGBCall {
0: 'rgb';
1: Expression4;
2: Expression4;
3: Expression4;
length: 4;
}
const tests: Expression4[] = [
10,
"red",
true,
// ~~~ Type 'true' is not assignable to type 'Expression4'
["+", 10, 5],
["case", [">", 20, 10], "red", "blue", "green"],
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Type '["case", [">", ...], ...]' is not assignable to type 'string'
["**", 2, 31],
// ~~~~~~~~~~~~ Type '["**", number, number]' is not assignable to type 'string
["rgb", 255, 128, 64],
["rgb", 255, 128, 64, 73]
// ~~~~~~~~~~~~~~~~~~~~~~~~ Type '["rgb", number, number, number, number]'
// is not assignable to type 'string'
];
const okExpressions: Expression4[] = [
['-', 12],
// ~~~~~~~~~ Type '["-", number]' is not assignable to type 'string'
['+', 1, 2, 3],
// ~~~~~~~~~~~~~~ Type '["+", number, ...]' is not assignable to type 'string'
['*', 2, 3, 4],
// ~~~~~~~~~~~~~~ Type '["*", number, ...]' is not assignable to type 'string'
];
//인터페이스를 사용하여 케이스를 나누고 튜플로 정의하여 모든 에러를 찾아냄
//모든 에러를 찾아내지만 에러 메세지가 부정확해져서 자동완성을 방해함
//위와 같은 경우에는 느슨한 타입을 사용하는 것이 더 효율적임
데이터가 아닌, API와 명세를 보고 타입 만들기
export interface getLicense_repository_licenseInfo {
__typename: "License";
/** Short identifier specified by <https://spdx.org/licenses> */
spdxId: string | null;
/** The license full name specified by <https://spdx.org/licenses> */
name: string;
}
export interface getLicense_repository {
__typename: "Repository";
/** The description of the repository. */
description: string | null;
/** The license associated with the repository */
licenseInfo: getLicense_repository_licenseInfo | null;
}
export interface getLicense {
/** Lookup a given repository by the owner and repository name. */
repository: getLicense_repository | null;
}
export interface getLicenseVariables {
owner: string;
name: string;
}
//+추가 타입스크립트와 비슷한 타입 시스템을 가지는 GraphQL
//API의 타입을 직접 생성하지 말고 명세로 부터 타입을 생성
//Apollo: GraphQl 타입을 타입스크립트 타입으로 변경해주는 도구
//quicktype 데이터로부터 타입을 생성하는 도구
해당 분야의 용어로 타입 이름 짓기
interface Animal {
name: string;
endangered: boolean;
habitat: string;
}
const leopard: Animal = {
name: 'Snow Leopard',
endangered: false,
habitat: 'tundra',
};
//타입의 이름이 모호해서 의미가 불분명함, 나쁜 예
interface Animal {
commonName: string;
genus: string;
species: string;
status: ConservationStatus;
climates: KoppenClimate[];
}
type ConservationStatus = 'EX' | 'EW' | 'CR' | 'EN' | 'VU' | 'NT' | 'LC';
type KoppenClimate = |
'Af' | 'Am' | 'As' | 'Aw' |
'BSh' | 'BSk' | 'BWh' | 'BWk' |
'Cfa' | 'Cfb' | 'Cfc' | 'Csa' | 'Csb' | 'Csc' | 'Cwa' | 'Cwb' | 'Cwc' |
'Dfa' | 'Dfb' | 'Dfc' | 'Dfd' |
'Dsa' | 'Dsb' | 'Dsc' | 'Dwa' | 'Dwb' | 'Dwc' | 'Dwd' |
'EF' | 'ET';
const snowLeopard: Animal = {
commonName: 'Snow Leopard',
genus: 'Panthera',
species: 'Uncia',
status: 'VU', // vulnerable
climates: ['ET', 'EF', 'Dfd'], // alpine or subalpine
};
//타입의 기준과 이름, 제한된 유니온 타입 등이 분명함, 좋은 예
//해당 분야의 이름 사용, 같은 의미에는 같은 이름 사용
공식 명칭에는 상표를 붙이기
interface Vector2D {
_brand: '2d';
x: number;
y: number;
}
function vec2D(x: number, y: number): Vector2D {
return {x, y, _brand: '2d'};
}
function calculateNorm(p: Vector2D) {
return Math.sqrt(p.x * p.x + p.y * p.y); // Same as before
}
calculateNorm(vec2D(3, 4)); // OK, returns 5
const vec3D = {x: 3, y: 4, z: 1};
calculateNorm(vec3D);
// ~~~~~ Property '_brand' is missing in type...
//Vector2D 타입만 받을 수 있도록 vec2D 함수를 통해 객체 생성
type SortedList<T> = T[] & {_brand: 'sorted'};
function isSorted<T>(xs: T[]): xs is SortedList<T> {
for (let i = 1; i < xs.length; i++) {
if (xs[i] > xs[i - 1]) {
return false;
}
}
return true;
}
function binarySearch<T>(xs: SortedList<T>, x: T): boolean {
// COMPRESS
return true;
// END
}
//상표 기법과 사용자 정의 타입 가드를 사용하여 정렬되어 있는 배열인지 검사
type Meters = number & {_brand: 'meters'};
type Seconds = number & {_brand: 'seconds'};
const meters = (m: number) => m as Meters;
const seconds = (s: number) => s as Seconds;
const oneKm = meters(1000); // Type is Meters
const oneMin = seconds(60); // Type is Seconds
const tenKm = oneKm * 10; // Type is number
const v = oneKm / oneMin; // Type is number
//number도 상표 기법을 사용할 수 있지만 연산 후 사라짐
마치며
책을 읽고 실무에서 작성된 코드를 보면서 타입스크립트는 사용자가 어떻게 사용하냐에 따라 장점을 끌어올릴 수 있는 언어라는 생각이 들었다. 다른 언어와 다르게 tsconfig.json으로 설정할 수 있는 옵션이 다양하고, 타입스크립트를 사용한다고 하더라도 잘못 사용하면 자바스크립트와 다르지 않을 수도 있다. 결국 사용자가 타입스크립트에 대해 잘 이해하고 상황에 맞게 제대로 사용해야 의미가 있는 언어라는 생각이 들었다.
Author And Source
이 문제에 관하여([TypeScript 독학] #8 이펙티브 타입스크립트(2)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@bbaa3218/TypeScript-독학-8-이펙티브-타입스크립트2저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)