TypeScript - Learning TypeScript Chap. 11 Declartion Files

이소라·2023년 5월 25일
0

TypeScript

목록 보기
25/28

11.1 Declaration Files

  • TypeScript는 구현과 별도로 타입 형태를 선언할 수 있습니다.

  • 타입 선언은 .d.ts 확장자로 끝나는 선언 파일에 작성됩니다.

  • 선언 파일은 일반적으로 프로젝트 내에서 작성되고, 프로젝트의 컴파일된 npm 패키지로 빌드 및 배포되거나 독립 실행(standalone) typings 패키지로 공유됩니다.

  • .d.ts 선언 파일은 런타임 코드를 포함할 수 없다는 제약 사항을 제외하고는 .ts 파일과 유사하게 작동합니다.

    • .d.ts 파일에는 사용 가능한 런타임 값, interface, 모듈, 일반적인 타입의 설명만 포함됩니다.
    • .d.ts 파일에는 JavaScript로 컴파일할 수 있는 모든 런타임 코드를 포함할 수 없습니다.
// types.d.ts
export interface Character {
  catchphrase?: string;
  name: string;
}
import { Character } from './types';

export const character: Character = {
  catchphrase: 'Yee-haw',
  name: 'Sandy Cheeks',
};

Tip

  • 선언 파일은 값이 아닌 타입만 선언할 수 있는 코드 영역을 의미하는 앰비언트 컨텍스트(ambient context)를 생성합니다.



11.2 Declaring Runtime Values

  • 선언 파일은 declare 키워드를 사용해 이러한 구조체가 존재한다고 선언할 수 있습니다.
    • 이렇게 하면 웹 페이지의 <script> 태그 같은 일부 외부 작업이 특정 타입의 이름을 사용해 값을 생성했음을 타입 시스템에 알립니다.
    • declare로 변수를 선언하면 초깃값이 허용되지 않는다는 점을 제외하고 일반적인 변수 선언과 동일한 구문을 사용합니다.
// types.d.ts
// Ok
declare let declared: string;

// Error: Initializer are not allowed in ambient contexts.
declare let initializer: string = 'Wanda';
  • 함수와 클래스도 일반적인 형식과 유사하게 선언되지만 함수 또는 메서드의 본문이 없습니다.
// fairies.d.ts
// Ok
declare function canGrantWish(wish: string): boolean;

// Error: An implementation cannot be declared in ambient contexts.
declare function grantWish(wish: string) {
  return true;
}

class Fairy {
  // Ok
  canGrantWish(wish: string): boolean;
  
  // Error: An implementation cannot be declared in ambient contexts.
  grantWish(wish: string) {
  return true;
  }
}

Tip

  • TypeScript의 암시적 any 타입 규칙은 일반 소스 코드와 마찬가지로 앰비언트 컨텍스트에 선언된 함수와 변수에 대해 동일하게 작동합니다.
    • 앰비언트 컨텍스트에 선언된 함수나 변수가 any 타입이 되는 것을 막기 위해 명시적 type annotation을 사용합니다.
  • declare 키워드를 사용한 타입 선언은 .d.ts 선언 파일에서 사용하는 게 가장 일반적이지만, 선언 파일 외부에서도 사용할 수 있습니다.
    • 모듈, 스크립트 파일에서도 declare 키워드를 사용할 수 있습니다.
    • 전역으로 사용 가능한 변수가 해당 파일에서만 사용되어야 하는 경우 declare 키워드가 유용합니다.
// index.ts
declare const myGlobalValue: string;

console.log(myGlobalValue); // Ok
  • .d.ts 선언 파일에서 interface와 같은 타입 형태는 declare 키워드 유뮤와 관계없이 허용됩니다.
  • .d.ts 선언 파일에서 함수나 변수 같은 런타임 구문에 declare 키워드가 없다면 타입 오류가 발생합니다.
// index.d.ts
// Ok
interface Writer {}
// Ok
declare interface Writer {}
// Ok, fullName의 타입은 원시 타입 string입니다.
declare const fullName: string;
// Ok, fullName의 타입은 리터럴 값 'Liz'입니다.
declare const fullName: 'Liz';

// Error: Tol-level declarations in .d.ts files must 
// start with either 'declare' or 'export' modifier. 
const lastName = 'Lemon';

11.2.1 Global Values

  • import 또는 export 문이 없는 TypeScript 타일은 모듈이 아닌 스크립트로 취급되기 때문에 여기에 선언된 타입을 포함한 구문은 전역으로 사용됩니다.
    • 이를 이용해서 타입을 전역으로 선언할 수 있습니다.
    • 전역 선언 파일은 애플리케이션의 모든 파일에 걸쳐서 사용할 수 있는 전역 타입 또는 변수를 선언하는데 특히 유용합니다.
// global.d.ts
declare const version: string;
// verstion.ts
export function logVersion() {
  console.log(`Version: ${version}`); // Ok
}
  • 전역으로 선언된 값은 전역 변수를 사용하는 브라우저 애플리케이션에서 가장 자주 사용합니다.

Tip

  • .d.ts 파일에 선언된 전역 타입에 자동으로 접근할 수 없는 경우 .d.ts 파일에서 import나 export 문이 있는지 다시 확인해야 합니다.

11.2.2 Global Interface Merging

  • 전역 API와 값에 대한 많은 타입 선언이 전역으로 존재합니다.
  • interface는 동일한 이름의 다른 interface와 병합되기 때문에, import나 export 문이 없는 .d.ts 선언 파일 같은 전역 스크립트 컨텍스트에서 interface를 선언하면 해당 interface가 전역으로 확장됩니다.
<script type="text/javascript">
  window.myVerstion = "3.1.1"
</script>
// index.ts
export function logWindowVersion() {
  console.log(`Window version is: ${window.myVersion}`);
  window.alert('Built-in window types still work! Hooray!');
}

11.2.3 Global Augmentations

  • 경우에 따라서 모듈 파일에 선언된 타입이 전역으로 사용되어야 합니다.
  • TypeScript에서 declare global 코드 블록 구문을 사용해 해당 블록 내용이 전역 컨텍스트에 있다고 표시합니다.
// types/data.d.ts
export interface Data {
  version: string;
}
// types/global.d.ts
import { Data } from './types/data';

declare global {
  const golballyDeclared: Data;
}

declare const locallyDeclared: Data;
// index.ts
import { Data } from './types/data';

function logData(data: Data) {
  // Ok
  console.log(`Data version is: ${data.version}`);
}

// Ok, import 문 없이 globallyDeclared 변수에 접근할 수 있습니다.
logData(globallyDeclared);
// Error: Cannot find name 'locallyDeclared'
logData(locallyDeclared);



11.3 Built-In Declarations

  • Array, Function, Map, Set과 같은 전역 객체는 타입 시스템이 알아야 하지만 코드에서 선언되지 않은 구문입니다.
    • 이와 같은 전역 객체는 디노, Node.js, 웹 브라우저 등에서 실행되는 런타임 코드에 의해 제공됩니다.

11.3.1 Library Declarations

  • 모든 JavaScript 런타임에 존재하는 Array, Function 같은 내장된 전역 객체는 lib.[target].d.ts 파일 이름으로 선언됩니다.

    • 여기서 target은 ES5, ES2020 또는 ESNext와 같이 프로젝트에서 대상으로 하는 JavaScript의 최소 지원 버전입니다.
  • 내장된 라이브러리 선언 파일 또는 lib 파일은 JavaScript의 내장된 API 전체를 나타내기 때문에 상당히 큽니다.

// lib.es5.d.ts

interface Array<T> {
  /**
  배열의 길이를 가져오거나 설정합니다.
  배열의 가장 큰 인덱스보다 1이 더 큰 숫자입니다.
  **/
  length: number;
  // ...
}
  • lib 파일은 TypeScript npm 패키지의 일부로 배포되며 node_modules/typescript/lib.es5.d.ts와 같은 경로의 패키지 내부에서 찾을 수 있습니다.

라이브러리 target

  • TypeScript는 기본적으로 tsc CLI 또는 프로젝트의 tsconfig.json(기본값 es5)에서 제공된 target 설정에 따라 적절한 lib 파일을 포함합니다.

    • JavaScript 최신 버전에 대한 연속적인 lib 파일들은 interface 병합을 사용해 서로 빌드됩니다.
  • TypeScript 프로젝트는 target으로 지정한 JavaScript 버전의 모든 최소 버전 lib을 포함합니다.

    • 예를 들어, target이 es2016인 프로젝트에는 lib.es5.d.ts, lib.es2015.d.ts, lib.es2016.d.ts까지 포함됩니다.

Tip

  • target보다 최신 버전의 JavaScript에서만 사용할 수 있는 기능은 타입 시스템에서 사용할 수 없습니다.

11.3.2 DOM Declarations

  • JavaScript 언어 자체 외에 가장 일반적으로 참조되는 타입 선언 영역은 웹 브라우저를 위한 것입니다.
    • 'DOM(Document Object Model)' 타입이라고 하는 웹 브라우저 타입은 localStorage와 같은 API와 웹 브라우저에서 주로 사용하는 HTMLElement와 같은 타입 형대를 다룹니다.
    • DOM 타입은 lib.dom.d.ts 파일과 다른 lib.*.d.ts 선언 파일에도 저장됩니다.
    • 전역 DOM 타입은 종종 전역 인터페이스로 설명됩니다.
// lib.dom.d.ts
interface Storage {
  // 키/값 쌍의 수를 반환합니다.
  readonly length: number;
  
  // 모든 키/값 쌍을 제거합니다.
  clear(): void;
  
  /*
  주어진 키에 연결된 현재값을 반환하거나
  주어진 키가 존재하지 않는 경우 null을 반환합니다.
  */
  getItem(key: string): string | null;
}
  • lib 컴파일러 옵션을 재정의하지 않는 TypeScript 프로젝트는 DOM 타입을 기본으로 포함합니다.



11.4 Module Declarations

  • 선언 파일의 또 다른 중요한 기능은 모듈의 상태를 설명하는 기능입니다.
    • 모듈의 문자열 이름 앞에 declare 키워드를 사용하면 모듈의 내용을 타입 시스템에 알릴 수 있습니다.
    • 모듈 선언은 JavaScript나 TypeScript 파일 확장자가 아닌 특정 파일의 내용을 코드로 가져올 수 있음을 웹 애플리케이션에 알리기 위해 사용합니다.
// modules.d.ts
declare module 'my-example-lib' {
  export const value: string;
}
// index.ts
import { value } from 'my-example-lib';

// Ok
console.log(value);
  • 코드에서 declare module을 자주 사용해서는 안 됩니다.
  • declare module는 와일드카드(wildcard) 모듈 선언과 패키지 타입과 함께 사용됩니다.

11.4.1 Wildcard Module Declarations

  • 모듈 선언으로 하나의 * 와일드카드를 포함해 해당 패턴과 일치하는 모든 모듈을 나타낼 수 있습니다.
    • 예를 들어 create-react-app과 create-next-app 같은 인기 있는 react start에 미리 구성된 것처럼 많은 웹 프로젝트는 CSS 모듈을 지원하며 CSS 파일에서 런타임에 사용할 수 있는 객체를 가져옵니다.
      • 기본적으로 {[i: string]: string} 타입의 객체를 내보내는 *.module.css와 같은 패턴으로 모듈을 정의합니다.
// styles.d.ts
declare module '*.module.css' {
  const styles: { [i: string]: string };
  export default styles;
}
import styles from './styles.module.css';

style.anyClassName; // 타입 : string



11.5 Package Types

  • TypeScript로 작성된 프로젝트는 여전히 .js로 컴파일된 파일이 포함된 패키지를 배포합니다.
  • 일반적으로 .d.ts 파일을 사용해 이러한 JavaScritp 파일 뒤에 TypeScript 타입 시스템 형태를 지원하도록 선언합니다.

11.5.1 declaration

  • TypeScript는 입력된 파일에 대한 .d.ts 출력 파일과 JavaScript 출력 파일을 함께 생성하는 선언 옵션을 제공합니다.
// index.ts
export const greet = (text: string) => {
  console.log(`Hello, ${text}`);
};
  • module은 es2015, target은 es2015인 선언 옵션을 사용해 위의 index 파일에 대한 출력 파일을 생성하면 다음과 같습니다.
// index.d.ts
export declare const greet: (text: string) => void;
// index.js
export const greet = (text) => {
  console.log(`Hello, ${text}`);
};
  • 자동으로 생성된 .d.ts 파일은 프로젝트에서 사용자가 사용할 타입 정의를 생성하는 가장 좋은 방법입니다.
  • 일반적으로 .js 파일을 생성하는 TypeScript로 작성된 대부분의 패키지도 해당 파일과 함께 .d.ts를 번들로 묶는 것이 좋습니다.

11.5.2 Dependency Package Types

  • TypeScript는 프로젝트의 node_modules 의존성(dependency) 내부에서 번들로 제공되는 .d.ts 파일을 감지하고 타입 시스템에 알립니다
  • 자체 .d.ts 선언 파일과 함께 제공되는 npm 모듈은 대부분 다음과 같은 파일 구조를 갖습니다.
lib/
	index.js
    index.d.ts
package.json
  • 예를 들어 테스트 프레임워크인 제스트(Jest)는 TypeScript로 작성되었으며 jest 패키지 내에 자체 번들 .d.ts 파일을 제공합니다.
    • describe와 it 같은 함수를 제공하는 @jest/globals 패키지에 대한 의존성을 가지며 jest는 전역으로 사용할 수 있습니다.
      • @jest/globals 패키지는 describe와 it을 내보냅니다.
      • 그런 다음 jest 패키지는 해당 함수를 가져오고, 해당 함수의 describe와 it 변수를 가지고 전역 스코프로 확장합니다.
// package.json
{
  "devDependencies": {
    "jest": "^32.1.0"
  }
}
// node_modules/@jest/globals/index.d.ts
export function describe(name: string, test: () => void): void;
export function it(name: string, test: () => void): void;
// node_modules/jest/index.d.ts
import  * as global from '@jest/globals';

declare global {
  const describe: typeof globals.describe;
  const it: typeof globals.it;
}
  • 이 구조는 jest를 사용하는 프로젝트가 describe와 it의 전역 버전을 참조할 수 있도록 허용합니다.

11.5.3 Exposing Package Types

  • 프로젝트가 npm에 배포되고 사용자를 위한 타입을 제공하려면 패키지의 package.json 파일에 types 필드를 추가해 루트 선언 파일을 가리킵니다.
    • types 필드는 main 필드와 유사하게 작동하고 종종 동일한 것처럼 보이지만 .js 확장자 대신에 .d.ts 확장자를 사용합니다.
{
  "author": "Pendant Publishing",
  "main": "./lib/index.js",
  "name": "coffeetable",
  "types": "./lib/index.d.ts",
  "version": "0.5.22",
}
  • 예를 들어 위의 가상 패키지 파일에서 main 런타임 파일인 ./lib/index.js는 types 파일인 ./lib/index.d.ts와 병렬 처리됩니다.
  • 그런 다음 TypeScript는 유틸리티 패키지에서 가져온 파일을 사용하기 위해 ./lib/index.d.ts의 내용을 사용합니다.

Tip

  • types 필드가 패키지의 package.json에 존재하지 않으면, TypeScript는 ./index.d.ts를 기본값으로 지정합니다.



11.6 DefinitelyTyped

  • 안타깝게도 모든 프로젝트가 TypeScript로 작성된 것은 아닙니다.
  • TypeScript 프로젝트는 여전히 해당 패키지에서 모듈의 타입 형태를 알려줘야 합니다.
  • TypeScript 팀과 커뮤니티는 커뮤니티에 작성된 패키지 정의를 수용하기 위해 DefinitelyTyped(DT)라는 거대한 저장소를 만들었습니다.
    • 저장소에는 변경 제안 검토 및 업데이트 게시와 관련된 자동화 부분과 수천 개의 .d.ts 정의 패키지가 포함되어 있습니다.
    • DT 패키지는 타입을 제공하는 패키지와 동일한 이름으로 npm에 @types 범위(scope)로 게시됩니다.

Note

  • @typesdependencies 또는 devDependencies로 설치하지만, 최근 몇 년 동안 이 둘의 구분은 모호해졌습니다.
    • 프로젝트가 npm 패키지로 배포되어야 하는 경우, dependencies를 사용해야만 패키지를 사용하는 곳에서 사용되는 타입 정의를 가져올 수 있습니다.
    • 프로젝트가 서버에서 빌드 및 실행되는 독립 실행형 애플리케이션이라면 devDependencies를 사용해 타입이 단지 개발 시 사용되는 툴임을 전달해야 합니다.
  • 예를 들어 2022년 집필시 점 lodash에 의존하는 유틸리티 패키지는 package.json에 다음과 같은 줄이 포함됩니다.
// package.json
{
  "dependencies": {
    "@types/lodash": "^4.14.182",
    "lodash": "^4.17.21"
  }
}
  • 리액트에 구축된 독립 실행형 애플리케이션의 package.json에는 다음과 같은 줄이 포함될 수 있습니다.
// package.json
{
  "dependencies": {
    "react": "^18.1.0"
  },
  "devDependencies": {
    "@types/react": "^18.0.9"
  }
}
  • 시맨틱 버저닝(semantic versioning)인 "semver" 번호는 @types/ 패키지와 패키지가 서로 반드시 일치하지는 않습니다.

11.6.1 Type Availability

  • 가장 인기 있는 JavaScript 패키지는 자체 타이핑과 함께 제공되거나 DefinitelyTyped를 통해 타이핑할 수 있습니다.

  • 아직 사용 가능한 타입이 없는 패키지에서 타입을 얻는 일반적인 세 가지 옵션은 다음과 같습니다.

    1. @types/ 패키지를 생성하기 위해 DefinitelyTyped에 pull request를 보냅니다.
    2. declare module 구문을 사용해 프로젝트 내에서 작성합니다.
    3. tsconfig.json의 noImplicitAny 옵션을 비활성하고 강력하게 경고합니다.

0개의 댓글