TypeScript 笔记(续)

36次阅读
没有评论

共计 14537 个字符,预计需要花费 37 分钟才能阅读完成。

1. 高级类型

条件类型

// 基础条件类型
type IsString<T> = T extends string ? true : false;

type A = IsString<string>; // true
type B = IsString<number>; // false

// 嵌套条件类型
type TypeName<T> = 
  T extends string ? "string" :
  T extends number ? "number" :
  T extends boolean ? "boolean" :
  T extends undefined ? "undefined" :
  T extends Function ? "function" :
  "object";

type T0 = TypeName<string>; // "string"
type T1 = TypeName<"hello">; // "string"
type T2 = TypeName<true>; // "boolean"
type T3 = TypeName<() => void>; // "function"
type T4 = TypeName<string[]>; // "object"

// 分布式条件类型
type ToArray<T> = T extends any ? T[] : never;
type StrArrOrNumArr = ToArray<string | number>; // string[] | number[]

// 过滤类型
type Filter<T, U> = T extends U ? T : never;
type StringOrNumber = Filter<string | number | boolean, string | number>; // string | number

// 排除 null 和 undefined
type NonNullable<T> = T extends null | undefined ? never : T;
type T5 = NonNullable<string | number | undefined>; // string | number
type T6 = NonNullable<string[] | null>; // string[]

推断类型

// 使用 infer 推断类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

type T7 = ReturnType<() => string>; // string
type T8 = ReturnType<(s: string) => void>; // void
type T9 = ReturnType<<T>() => T>; // unknown
type T10 = ReturnType<typeof Math.random>; // number

// 推断函数参数
type Parameters<T> = T extends (...args: infer P) => any ? P : never;

type T11 = Parameters<() => void>; // []
type T12 = Parameters<(s: string) => void>; // [s: string]
type T13 = Parameters<<T>(arg: T) => T>; // [arg: unknown]

// 推断构造函数参数
type ConstructorParameters<T> = T extends new (...args: infer P) => any ? P : never;

class Person {constructor(public name: string, public age: number) {}}
type T14 = ConstructorParameters<typeof Person>; // [name: string, age: number]

// 推断实例类型
type InstanceType<T> = T extends new (...args: any[]) => infer R ? R : any;
type T15 = InstanceType<typeof Person>; // Person

映射类型修饰符

// 移除只读修饰符
type CreateMutable<T> = {-readonly [P in keyof T]: T[P];
};

interface LockedAccount {
  readonly id: string;
  readonly name: string;
}

type UnlockedAccount = CreateMutable<LockedAccount>;
// {id: string; name: string;}

// 移除可选修饰符
type Concrete<T> = {[P in keyof T]-?: T[P];
};

interface MaybeUser {
  id: number;
  name?: string;
  age?: number;
}

type User = Concrete<MaybeUser>;
// {id: number; name: string; age: number;}

// 同时移除只读和可选
type MutableRequired<T> = {-readonly [P in keyof T]-?: T[P];
};

键重映射

// 使用 as 子句重映射键
type Getters<T> = {[P in keyof T as `get${Capitalize<string & P>}`]: () => T[P];
};

interface Person {
  name: string;
  age: number;
  location: string;
}

type LazyPerson = Getters<Person>;
// {//   getName: () => string;
//   getAge: () => number;
//   getLocation: () => string;
// }

// 过滤键
type RemoveKindField<T> = {[P in keyof T as Exclude<P, "kind">]: T[P];
};

interface Circle {
  kind: "circle";
  radius: number;
}

type KindlessCircle = RemoveKindField<Circle>;
// {radius: number}

// 根据值类型过滤键
type ExtractPropertyNames<T, U> = {[K in keyof T]: T[K] extends U ? K : never;
}[keyof T];

interface Example {
  name: string;
  age: number;
  isActive: boolean;
  tags: string[];
}

type StringKeys = ExtractPropertyNames<Example, string>; // "name" | "tags"

2. 模板字面量类型

基础模板字面量

type World = "world";
type Greeting = `hello ${World}`; // "hello world"

type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";

type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
// "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"

type Lang = "en" | "ja" | "pt";
type LocaleMessageIDs = `${Lang}_${AllLocaleIDs}`;
// "en_welcome_email_id" | "en_email_heading_id" | ... 12 more ... 

字符串操作类型

// 内置字符串操作类型
type UppercaseGreeting = Uppercase<"hello world">; // "HELLO WORLD"
type LowercaseGreeting = Lowercase<"HELLO WORLD">; // "hello world"
type CapitalizedGreeting = Capitalize<"hello world">; // "Hello world"
type UncapitalizedGreeting = Uncapitalize<"Hello World">; // "hello World"

// 创建访问器方法
type Getter<T extends string> = `get${Capitalize<T>}`;
type Setter<T extends string> = `set${Capitalize<T>}`;

type Name = "name";
type NameAccessors = Getter<Name> | Setter<Name>; // "getName" | "setName"

// 事件处理函数
type EventName = "click" | "hover" | "focus";
type EventHandlerName = `on${Capitalize<EventName>}`;
// "onClick" | "onHover" | "onFocus"

模板字面量推断

// 解析路径参数
type Route = "/user/:id" | "/post/:slug" | "/category/:name";

type ExtractRouteParams<T extends string> = 
  T extends `${string}:${infer Param}/${infer Rest}`
    ? Param | ExtractRouteParams<`/${Rest}`>
    : T extends `${string}:${infer Param}`
    ? Param
    : never;

type Params = ExtractRouteParams<Route>; // "id" | "slug" | "name"

// 更复杂的路径解析
type ParseRoute<T extends string> =
  T extends `${infer Start}/:${infer Param}/${infer Rest}`
    ? {[K in Param | keyof ParseRoute<`/${Rest}`>]: string }
    : T extends `${infer Start}/:${infer Param}`
    ? {[K in Param]: string }
    : {};

type UserRoute = ParseRoute<"/user/:id">; // {id: string}
type PostRoute = ParseRoute<"/post/:slug/comments/:commentId">; // {slug: string; commentId: string}

3. 声明合并

接口合并

// 相同名称的接口会自动合并
interface Box {
  height: number;
  width: number;
}

interface Box {
  scale: number;
  // height: string; // 错误:后续属性声明必须属于同一类型
}

let box: Box = {height: 5, width: 6, scale: 10};

// 合并函数重载
interface Cloner {clone(animal: Animal): Animal;
}

interface Cloner {clone(animal: Sheep): Sheep;
}

interface Cloner {clone(animal: Dog): Dog;
  clone(animal: Cat): Cat;
}

// 等价于:interface Cloner {clone(animal: Dog): Dog;
  clone(animal: Cat): Cat;
  clone(animal: Sheep): Sheep;
  clone(animal: Animal): Animal;
}

// 命名空间与类合并
class Album {label: Album.AlbumLabel = new Album.AlbumLabel();
}
namespace Album {
  export class AlbumLabel {name: string = "Default Label";}
  export const version = "1.0";
}

const album = new Album();
console.log(album.label.name); // "Default Label"
console.log(Album.version); // "1.0"

命名空间合并

namespace Animals {export class Zebra {}
}

namespace Animals {
  export interface Legged {numberOfLegs: number;}
  export class Dog {}}

// 等价于:namespace Animals {
  export interface Legged {numberOfLegs: number;}
  export class Zebra {}
  export class Dog {}}

枚举合并

enum Color {
  Red = 1,
  Green = 2
}

enum Color {Blue = 4}

// 等价于:enum Color {
  Red = 1,
  Green = 2,
  Blue = 4
}

console.log(Color.Red); // 1
console.log(Color.Blue); // 4

4. 类型守卫与类型断言

自定义类型守卫

// 使用类型谓词
function isString(value: unknown): value is string {return typeof value === 'string';}

function isNumber(value: unknown): value is number {return typeof value === 'number';}

function isDate(value: unknown): value is Date {return value instanceof Date;}

// 使用 in 操作符
interface Admin {
  name: string;
  privileges: string[];
}

interface Employee {
  name: string;
  startDate: Date;
}

type UnknownEmployee = Employee | Admin;

function printEmployeeInformation(emp: UnknownEmployee) {console.log("Name:" + emp.name);
  if ("privileges" in emp) {console.log("Privileges:" + emp.privileges);
  }
  if ("startDate" in emp) {console.log("Start Date:" + emp.startDate);
  }
}

// 使用字面量类型守卫
type NetworkLoadingState = {state: "loading";};

type NetworkFailedState = {
  state: "failed";
  code: number;
};

type NetworkSuccessState = {
  state: "success";
  response: {
    title: string;
    duration: number;
    summary: string;
  };
};

type NetworkState = 
  | NetworkLoadingState
  | NetworkFailedState
  | NetworkSuccessState;

function logger(state: NetworkState): string {switch (state.state) {
    case "loading":
      return "Downloading...";
    case "failed":
      return `Error ${state.code} downloading`;
    case "success":
      return `Downloaded ${state.response.title} - ${state.response.summary}`;
  }
}

断言函数

// 断言函数
function assert(condition: any, msg?: string): asserts condition {if (!condition) {throw new Error(msg);
  }
}

function assertIsString(val: any): asserts val is string {if (typeof val !== "string") {throw new Error("Not a string!");
  }
}

function yell(str: any) {assertIsString(str);
  return str.toUpperCase(); // str 现在是 string 类型}

// 使用断言函数处理未知数据
function processUserData(data: unknown) {assert(typeof data === "object" && data !== null, "Data must be an object");
  assert("name" in data && typeof data.name === "string", "Name must be a string");
  assert("age" in data && typeof data.age === "number", "Age must be a number");

  // 现在 TypeScript 知道 data 的形状
  console.log(`Name: ${data.name}, Age: ${data.age}`);
}

5. 异步编程

Promise 类型

// Promise 基础类型
const promise: Promise<string> = new Promise((resolve, reject) => {setTimeout(() => {resolve("Hello, World!");
  }, 1000);
});

// 异步函数返回类型
async function fetchData(): Promise<string> {const response = await fetch('/api/data');
  const data = await response.text();
  return data;
}

// 处理多个 Promise
async function fetchMultipleData(): Promise<[string, number]> {const [data1, data2] = await Promise.all([
    fetch('/api/data1').then(r => r.text()),
    fetch('/api/data2').then(r => r.json())
  ]);
  return [data1, data2];
}

// 错误处理
async function safeFetch<T>(url: string): Promise<T | null> {
  try {const response = await fetch(url);
    if (!response.ok) {throw new Error(`HTTP error! status: ${response.status}`);
    }
    return await response.json();} catch (error) {console.error('Fetch error:', error);
    return null;
  }
}

高级异步模式

// 带重试的异步函数
async function fetchWithRetry<T>(
  url: string,
  maxRetries: number = 3,
  delay: number = 1000
): Promise<T> {for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {const response = await fetch(url);
      if (!response.ok) {throw new Error(`HTTP error! status: ${response.status}`);
      }
      return await response.json();} catch (error) {if (attempt === maxRetries) {throw error;}
      console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  throw new Error('All retry attempts failed');
}

// 异步迭代器
async function* asyncGenerator(): AsyncGenerator<number, void, unknown> {
  let i = 0;
  while (i < 3) {await new Promise(resolve => setTimeout(resolve, 1000));
    yield i++;
  }
}

async function consumeAsyncGenerator() {for await (const value of asyncGenerator()) {console.log(value);
  }
}

// Promise 工具类型
type Awaited<T> = T extends PromiseLike<infer U> ? Awaited<U> : T;

async function example() {const result = await Promise.resolve(Promise.resolve(42));
  type ResultType = Awaited<typeof result>; // number
}

6. 性能优化与最佳实践

类型性能优化

// 1. 使用接口而不是类型别名(对于对象类型)// 推荐
interface User {
  id: number;
  name: string;
  email: string;
}

// 不推荐(对于对象类型)type UserType = {
  id: number;
  name: string;
  email: string;
};

// 2. 使用 const 枚举
const enum Direction {
  Up = "UP",
  Down = "DOWN",
  Left = "LEFT",
  Right = "RIGHT"
}

// 3. 避免深度嵌套的类型
// 不推荐
type DeepNested = {
  level1: {
    level2: {
      level3: {
        level4: {value: string;};
      };
    };
  };
};

// 推荐
interface Level4 {value: string;}

interface Level3 {level4: Level4;}

interface Level2 {level3: Level3;}

interface Level1 {level2: Level2;}

// 4. 使用索引签名谨慎
interface StringMap {[key: string]: string;
}

// 更好的做法是使用 Record 工具类型
type BetterStringMap = Record<string, string>;

工程化最佳实践

// 1. 使用 barrel exports
// src/components/index.ts
export {Button} from './Button';
export {Input} from './Input';
export {Modal} from './Modal';

// 2. 配置路径映射
// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {"@/*": ["src/*"],
      "@/components/*": ["src/components/*"],
      "@/utils/*": ["src/utils/*"]
    }
  }
}

// 使用
import {Button} from '@/components';
import {formatDate} from '@/utils/date';

// 3. 环境变量类型安全
interface ProcessEnv {
  readonly NODE_ENV: 'development' | 'production' | 'test';
  readonly API_URL: string;
  readonly APP_VERSION: string;
}

declare global {
  namespace NodeJS {interface ProcessEnv extends ProcessEnv {}
  }
}

// 4. 错误处理模式
class AppError extends Error {
  constructor(
    message: string,
    public code: string,
    public statusCode: number = 500
  ) {super(message);
    this.name = 'AppError';
  }
}

function handleError(error: unknown): never {if (error instanceof AppError) {console.error(`AppError [${error.code}]: ${error.message}`);
    throw error;
  }

  if (error instanceof Error) {console.error(`Unexpected error: ${error.message}`);
    throw new AppError('Internal server error', 'INTERNAL_ERROR');
  }

  console.error('Unknown error occurred');
  throw new AppError('Unknown error', 'UNKNOWN_ERROR');
}

7. 测试与调试

单元测试配置

// jest.config.js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/src'],
  testMatch: [
    '**/__tests__/**/*.+(ts|tsx|js)',
    '**/?(*.)+(spec|test).+(ts|tsx|js)'
  ],
  transform: {'^.+\\.(ts|tsx)$': 'ts-jest',
  },
  collectCoverageFrom: [
    'src/**/*.{ts,tsx}',
    '!src/**/*.d.ts',
  ],
};

// src/utils/math.test.ts
import {add, multiply, divide} from './math';

describe('Math utilities', () => {describe('add', () => {it('should add two numbers correctly', () => {expect(add(1, 2)).toBe(3);
      expect(add(-1, 1)).toBe(0);
      expect(add(0, 0)).toBe(0);
    });

    it('should handle decimal numbers', () => {expect(add(1.5, 2.5)).toBe(4);
      expect(add(0.1, 0.2)).toBeCloseTo(0.3);
    });
  });

  describe('multiply', () => {it('should multiply two numbers correctly', () => {expect(multiply(2, 3)).toBe(6);
      expect(multiply(-2, 3)).toBe(-6);
      expect(multiply(0, 5)).toBe(0);
    });
  });

  describe('divide', () => {it('should divide two numbers correctly', () => {expect(divide(6, 2)).toBe(3);
      expect(divide(10, 4)).toBe(2.5);
    });

    it('should throw error when dividing by zero', () => {expect(() => divide(5, 0)).toThrow('Division by zero');
    });
  });
});

// src/utils/math.ts
export function add(a: number, b: number): number {return a + b;}

export function multiply(a: number, b: number): number {return a * b;}

export function divide(a: number, b: number): number {if (b === 0) {throw new Error('Division by zero');
  }
  return a / b;
}

调试配置

// .vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug TypeScript",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/dist/index.js",
      "outFiles": ["${workspaceFolder}/dist/**/*.js"],
      "sourceMaps": true,
      "preLaunchTask": "tsc: build",
      "console": "integratedTerminal"
    },
    {
      "name": "Debug Jest Tests",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/node_modules/.bin/jest",
      "args": ["--runInBand", "--no-cache"],
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen",
      "disableOptimisticBPs": true,
      "windows": {"program": "${workspaceFolder}/node_modules/jest/bin/jest"
      }
    },
    {
      "name": "Debug Current Test File",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/node_modules/.bin/jest",
      "args": [
        "${relativeFile}",
        "--no-cache"
      ],
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen",
      "disableOptimisticBPs": true,
      "windows": {"program": "${workspaceFolder}/node_modules/jest/bin/jest"
      }
    }
  ]
}

8. 构建与部署

现代化构建配置

// package.json
{
  "name": "typescript-project",
  "version": "1.0.0",
  "scripts": {
    "build": "tsc",
    "build:watch": "tsc --watch",
    "build:production": "tsc --project tsconfig.prod.json",
    "dev": "ts-node src/index.ts",
    "start": "node dist/index.js",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "lint": "eslint src/**/*.ts",
    "lint:fix": "eslint src/**/*.ts --fix",
    "type-check": "tsc --noEmit"
  },
  "devDependencies": {
    "@types/jest": "^29.0.0",
    "@types/node": "^18.0.0",
    "@typescript-eslint/eslint-plugin": "^5.0.0",
    "@typescript-eslint/parser": "^5.0.0",
    "eslint": "^8.0.0",
    "jest": "^29.0.0",
    "ts-jest": "^29.0.0",
    "ts-node": "^10.0.0",
    "typescript": "^4.9.0"
  }
}

// tsconfig.prod.json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "sourceMap": false,
    "declaration": false,
    "removeComments": true,
    "noEmitOnError": true
  },
  "exclude": ["**/*.test.ts", "**/*.spec.ts", "node_modules"]
}

Docker 部署配置

# Dockerfile
FROM node:18-alpine AS builder

WORKDIR /app
COPY package*.json ./
COPY tsconfig.json ./
RUN npm ci

COPY src/ ./src/
RUN npm run build

FROM node:18-alpine AS runtime

WORKDIR /app
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001

COPY package*.json ./
RUN npm ci --only=production

COPY --from=builder --chown=nextjs:nodejs /app/dist/ ./dist/

USER nextjs

EXPOSE 3000

CMD ["node", "dist/index.js"]

持续集成配置

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [16.x, 18.x]

    steps:
    - uses: actions/checkout@v3

    - name: Use Node.js ${{matrix.node-version}}
      uses: actions/setup-node@v3
      with:
        node-version: ${{matrix.node-version}}
        cache: 'npm'

    - name: Install dependencies
      run: npm ci

    - name: Type check
      run: npm run type-check

    - name: Lint
      run: npm run lint

    - name: Test
      run: npm test

    - name: Build
      run: npm run build

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    steps:
    - uses: actions/checkout@v3

    - name: Build and push Docker image
      uses: docker/build-push-action@v4
      with:
        context: .
        push: true
        tags: user/app:latest

正文完
 0
一诺
版权声明:本站原创文章,由 一诺 于2025-10-22发表,共计14537字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)
验证码