typescript와 @types 추가하기
기존 js 라이브러리에는 타입이 없다.
그래서 DefinitelyTyped에서 타입 정보를 얻을 수 있다.
이 타입 모음은 @types 스코프에 공개된다.
@types 라이브러리는 타입 정모나 포함하고 있으며 구현체는 포함하지 않는다.
dependencies에 원본 라이브러리가 있어도 @types 의존성이 devDependencied에 있어야 한다.
npm install react
npm install --save-dev @types/react
런타임에는 typescript와 @types/..에 의존하지 않는 것!
타입스크립트 의존성을 다음 3가지를 맞춰야 한다.
타입스크립트로 작성된 라이브러리라면 타입 선언을 자체적으로 포함하고,
자바스크립트로 작성된 라이브러리라면 타입 선언으 DefinitelyTpyed에 공개하기
공개 메서드에 등장한 어떤 형태의 타입이든 익스포트하기
어차피 라이브러리 사용자는 추출할 수 있음.
// library.ts
// 타입 정의
export interface User {
id: number;
name: string;
}
// 공개 함수
export function getUser(id: number): User {
return { id: id, name: "John Doe" };
}
// 인사말을 생성합니다.
function greet(name: string, title: string) {
return `Hello ${title} ${name}!`;
}
/** 인사말을 생성합니다. */
function greet2(name: string, title: string) {
return `Hello ${title} ${name}!`;
}
greet("John", "Mr.");
greet2("John", "Mr.");

TSDoc 스타일 주석은 편집기의 툴팁 설명에 표시된다!
/**
* 인사말을 생성합니다.
* @param name 인사할 사람의 이름
* @param title 인사할 사람의 직함
* @returns 생성된 인사말
*/
function greet3(name: string, title: string) {
return `Hello ${title} ${name}!`;
}

let, const 변수는 렉시털 스코프
this는 다이나믹 스코프: 정의된 방식이 아니라 호출된 방식에 따라 달라짐
interface TimerInterface {
tick(): void;
stop(): void;
}
class Timer {
constructor(public interval: number, public count: number) {}
start(this: TimerInterface) {
setInterval(() => {
this.tick();
if (--this.count === 0) {
this.stop();
}
}, this.interval);
}
tick() {
console.log("Tick");
}
stop() {
console.log("Timer stopped");
}
}
근데 이 코드는 오버스펙이라는 GPT
function double<T extends number | string> (x: T): T extends string ? string : number;
function double(x: any) {
return x + x;
}
function getValue(key: string): string;
function getValue(key: number): number;
function getValue(key: any): any {
if (typeof key === 'string') {
return "String value";
} else if (typeof key === 'number') {
return 42;
}
}
console.log(getValue("test")); // "String value"
console.log(getValue(123)); // 42
type Value<T> = T extends string ? string : T extends number ? number : never;
function getValue<T extends string | number>(key: T): Value<T> {
if (typeof key === 'string') {
return "String value" as Value<T>;
} else if (typeof key === 'number') {
return 42 as Value<T>;
}
throw new Error("Unsupported type");
}
console.log(getValue("test")); // "String value"
console.log(getValue(123)); // 42
작성 중인 라이브러리가 의존하는 라이브러리의 구현과 무관하게 타입에만 의존한다면, 필요한 선언부만 추출하여 작성 중인 라이브러리에 넣는 것(미러링)을 고려해 보기!
node 개발자에게는 영향이 없지만 웹 기반, js 사용자에게 더 나은 사양 제공할 수 있음.
핵심은 상관 없는 사용자가 불필요하게 @types 의존성 가지게 아지 않는 것!
근데 양이 많아지면 그냥 @types 의존성 추가하는 게 나음.
필수가 아닌 의존성 분리를 구조적 타이핑 사용하면 됨.
예를 들어 보면 외부 라이브러이의 HTTPClient를 사용 가능 경우, 필요한 타입 선언만 추출해서 내부적으로 재정의(미러)하기
// 외부 라이브러리의 HTTPClient 예시
interface HTTPClient {
get(url: string): Promise<string>;
}
// 내 라이브러리에서 미러 타입 사용
interface MyHTTPClient {
get(url: string): Promise<string>;
}
function fetchData(client: MyHTTPClient, url: string) {
return client.get(url);
}
외부 라이브러리에 직접 의존할 경우 결합도가 올라간다!
타입 선언 테스트를 단언으로 떼우면 안 됨
dtslint 같은 걸로 검사하는 게 안전하고 간단함
타입도 테스팅은 한다는 걸 처음 알았음
// double.ts
export function double(x: number): number {
return x * 2;
}
// double.test.ts
import { double } from './double';
// 이 테스트는 실제 런타임 값을 검사하는 것이 아닌, 컴파일 타임의 타입 검증을 위한 것입니다.
describe('double function', () => {
it('should accept and return a number', () => {
const result: number = double(10);
expect(result).toBe(20);
});
// 오류를 예상하는 테스트는 주석으로 처리하거나, 테스트 도구의 기능을 사용하여 타입 오류를 검사할 수 있습니다.
// it('should cause a compile-time error when passing a string', () => {
// const result: number = double("Hello"); // TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
// });
});
// user.ts
interface User {
id: number;
name: string;
}
// 잘못된 타입 반환을 하는 함수
function getUser(): any {
return { id: "123", name: "Alice" }; // id는 문자열이지만, 숫자 타입이어야 함
}
// user.test.ts
import { getUser } from './user';
describe('getUser', () => {
it('should return a User object', () => {
const user = getUser() as User; // 잘못된 타입 단언 사용
expect(user.id).toEqual(123); // 타입스크립트의 타입 체크를 무시
expect(user.name).toEqual("Alice");
});
});
이 테스트는 실제로 getUser 함수가 올바른 User 객체를 반환하는지 검증하지 않습니다. 타입 단언을 사용함으로써 잘못된 데이터 구조가 테스트를 통과할 수 있으므로, 이 테스트는 신뢰할 수 없습니다.
function getUser(): User {
// 올바른 타입 정보를 반환
return { id: 123, name: "Alice" };
}
// 테스트 코드에서는 타입스크립트의 타입 추론을 그대로 사용
describe('getUser', () => {
it('should return a User object with correct types', () => {
const user = getUser();
expect(typeof user.id).toBe('number');
expect(user.name).toEqual("Alice");
});
});
// user.d.ts
export interface User {
id: number;
name: string;
}
export function getUser(): User;
// user-tests.ts
import { getUser, User } from './user';
const user: User = getUser(); // 이 코드는 타입 검증을 위해 있으며 실행되지 않습니다.
dtslint . 로 실행
dtslint는 .d.ts 파일의 타입만을 검사하므로 실제 구현을 테스트하는 것과는 다름

동일성은 두 타입이 정확히 같은 구조의 매개변수, 반환 타입을 가지고 있는 것
(모든 면에서 완벽해야 함)
할당 가능성은 두 타입이 서로 다를 수 있지만 하나의 타입이 다른 타입의 하위 타입일 때
넓은 범위의 타입을 더 좁은 범위의 타입에 할당할 수 있는 관계
타입 동일성 예시
function add(a: number, b: number): number {
return a + b;
}
function sum(a: number, b: number): number {
return a + b;
}
let a: typeof add;
let b: typeof sum;
a = b; // 타입 동일성에 의해 할당 가능
타입 할당 가능성 예시
function process(value: number | string): void {
// 로직 처리
}
let handler: (value: string) => void;
handler = process; // 할당 가능: 'number | string'은 'string'을 포함하므로