Writing type declaration files
The Role of Type Declaration Files
Type declaration files (.d.ts) provide type information for TypeScript, enabling developers to add static type checking to JavaScript libraries or modules. When using third-party JavaScript libraries, if there is no type declaration file, the TypeScript compiler will report errors. Declaration files do not contain concrete implementations but only type definitions.
// Assume there is a JavaScript library mathUtils.js
function add(a, b) {
return a + b;
}
// Corresponding declaration file mathUtils.d.ts
declare function add(a: number, b: number): number;
Ways to Create Declaration Files
There are three main ways to create declaration files:
- Manual Writing: Suitable for small projects or custom modules.
- Automatic Generation Using Tools: For example,
dts-gen
can generate declaration files based on existing JavaScript code. - Obtaining from DefinitelyTyped: Most popular libraries have community-maintained type declarations.
Example of manually creating a declaration file:
// myLib.d.ts
declare namespace MyLib {
interface Config {
timeout: number;
retries: number;
}
function init(config: Config): void;
function fetchData(url: string): Promise<any>;
}
Module Declarations vs. Global Declarations
Depending on the usage scenario, declaration files can be categorized as module declarations or global declarations:
Module Declarations
Suitable for module systems like CommonJS, AMD, or ES modules:
// Module declaration
declare module 'module-name' {
export interface Options {
debug?: boolean;
}
export function create(options?: Options): void;
}
Global Declarations
Required when a library is imported via <script>
tags:
// Global variable declaration
declare const VERSION: string;
// Global function declaration
declare function greet(name: string): void;
// Global class declaration
declare class Utils {
static formatDate(date: Date): string;
}
Type Merging and Extension
TypeScript supports extending existing types through declaration merging:
Interface Merging
// Original declaration
interface User {
name: string;
}
// Extended declaration
interface User {
age: number;
}
// After merging, equivalent to:
interface User {
name: string;
age: number;
}
Namespace Merging
// Original namespace
namespace API {
export function get(url: string): Promise<any>;
}
// Extended namespace
namespace API {
export function post(url: string, data: any): Promise<any>;
}
Common Type Declaration Patterns
Function Overload Declarations
declare function createElement(tag: 'div'): HTMLDivElement;
declare function createElement(tag: 'span'): HTMLSpanElement;
declare function createElement(tag: string): HTMLElement;
Class Declarations
declare class Animal {
constructor(name: string);
name: string;
move(distance: number): void;
}
Generic Types
declare interface Response<T = any> {
data: T;
status: number;
}
declare function fetch<T>(url: string): Promise<Response<T>>;
Handling Complex Types
Conditional Types
declare type NonNullable<T> = T extends null | undefined ? never : T;
declare type Flatten<T> = T extends Array<infer U> ? U : T;
Mapped Types
declare type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
declare type Partial<T> = {
[P in keyof T]?: T[P];
};
Advanced Features in Declaration Files
Triple-Slash Directives
Used to declare dependencies between files:
/// <reference types="node" />
/// <reference path="other.d.ts" />
Global Augmentation
Extend the global scope within a module using declare global
:
// In a module declaration file
export {};
declare global {
interface Window {
myCustomProp: string;
}
}
Handling Special Cases in Third-Party Libraries
Module Augmentation
When extending third-party modules:
import { OriginalModule } from 'some-module';
declare module 'some-module' {
interface OriginalModule {
newMethod(): void;
}
}
Default Export Handling
// For CommonJS modules
declare module 'module-name' {
const value: any;
export = value;
}
// For ES module default exports
declare module 'es-module' {
const defaultValue: any;
export default defaultValue;
}
Best Practices for Type Declarations
- Prefer interfaces over type aliases unless union types or tuples are needed.
- Provide complete type definitions for public APIs.
- Use JSDoc comments to enhance type declarations.
- Keep declaration files synchronized with implementations.
- Include examples for complex types.
/**
* Calculates the sum of two numbers
* @param a - The first addend
* @param b - The second addend
* @returns The sum of the two numbers
*/
declare function add(a: number, b: number): number;
Testing Type Declaration Files
Tools like tsd
can be used to test type declarations:
import { expectType } from 'tsd';
expectType<string>(add(1, 2)); // This will error because add returns a number
Publishing Type Declaration Files
When publishing a package with type declarations, there are two approaches:
- Include
.d.ts
files directly in the package and specify thetypes
field inpackage.json
. - Publish to DefinitelyTyped to provide declarations via the
@types
scope.
Example package.json
configuration:
{
"name": "my-package",
"version": "1.0.0",
"types": "./dist/index.d.ts",
"files": ["dist"]
}
Handling Libraries Without Type Declarations
For libraries without type declarations, create a types
directory and configure tsconfig.json
:
{
"compilerOptions": {
"typeRoots": ["./node_modules/@types", "./types"]
}
}
Then create the corresponding declaration file in the types
directory:
types/
some-library/
index.d.ts
Common Issues in Type Declarations
Circular Type References
Solution:
// a.d.ts
import type { B } from './b';
export interface A {
b: B;
}
// b.d.ts
import type { A } from './a';
export interface B {
a?: A;
}
Dynamic Property Access
declare interface DynamicObject {
[key: string]: any;
}
// More precise typing
declare interface StringMap<T> {
[key: string]: T;
}
Performance Optimization in Type Declarations
Excessive type declarations may impact compilation performance. Optimize by:
- Using
import type
to avoid importing actual code. - Splitting large declarations into multiple files.
- Avoiding overuse of conditional types and complex type operations.
// Use type-only imports
import type { SomeType } from 'some-module';
// Instead of
import { SomeType } from 'some-module';
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn