Skip to content

基础

TypeScript 环境编译

1. 使用 ts-node

  • 适用于开发环境快速运行 TypeScript 代码
  • 安装:npm install -g ts-node typescript
  • 使用:ts-node script.ts
  • 优点:无需编译即可直接运行,开发效率高
  • 缺点:不适合生产环境,运行性能较差

2. 使用 tsc 编译 (推荐)

  • TypeScript 官方编译器
  • 安装:npm install -g typescript
  • 编译:tsc script.ts
  • 配置文件:tsconfig.json
json
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "outDir": "./dist",
    "strict": true
  }
}
  • 优点:
    • 可靠的编译结果
    • 完整的类型检查
    • 适合生产环境
  • 缺点:需要额外的编译步骤

3. 使用打包工具

Webpack

  • 安装:npm install webpack typescript ts-loader --save-dev
  • webpack.config.js 配置:
js
module.exports = {
  entry: './src/index.ts',
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
};

Rollup

  • 安装:npm install rollup @rollup/plugin-typescript --save-dev
  • rollup.config.js 配置:
js
import typescript from '@rollup/plugin-typescript';

export default {
  input: 'src/index.ts',
  output: {
    file: 'bundle.js',
    format: 'cjs',
  },
  plugins: [typescript()],
};

TypeScript 的基础

TypeScript 类型

1. 原始数据类型

  • number:数字类型

    typescript
    let decimal: number = 6;
    let hex: number = 0xf00d;
    let binary: number = 0b1010;
  • string:字符串类型

    typescript
    let name: string = '张三';
    let greeting: string = `你好,${name}`;
  • boolean:布尔类型

    typescript
    let isDone: boolean = false;
  • undefined:未定义

    typescript
    let u: undefined = undefined;
  • null:空值

    typescript
    let n: null = null;

2. 特殊类型

  • void:表示没有任何类型,通常用于函数返回值

    typescript
    function warnUser(): void {
      console.log('警告信息');
    }
  • any:任意类型,相当于放弃类型检查

    typescript
    let notSure: any = 4;
    notSure = '可以是字符串';
    notSure = false; // 也可以是布尔值
  • unknown:比 any 更安全的类型,需要类型检查或类型断言才能使用

    typescript
    let value: unknown = 4;
    // 需要类型检查或类型断言才能使用
    if (typeof value === 'string') {
      console.log(value.toUpperCase());
    }
  • never:永不存在的值的类型

    typescript
    // 抛出异常的函数
    function error(message: string): never {
      throw new Error(message);
    }
    
    // 永远不会返回的函数
    function infiniteLoop(): never {
      while (true) {}
    }

3. 复合类型

  • 数组

    typescript
    let list: number[] = [1, 2, 3];
    let list2: Array<number> = [1, 2, 3];
  • 元组

    typescript
    let tuple: [string, number] = ['hello', 10];
  • 对象

    typescript
    let obj: object = {
      name: '张三',
      age: 20,
    };

4. 类型推论

typescript
// TypeScript 会自动推断类型
let x = 3; // 推断为 number
let arr = [1, 2, 3]; // 推断为 number[]

5. 类型断言

typescript
let someValue: unknown = 'this is a string';
// 两种断言方式
let strLength1: number = (someValue as string).length;
let strLength2: number = (<string>someValue).length;

// 当你确定类型兼容但TypeScript无法识别时使用类型断言
const element = document.getElementById('root') as HTMLElement;

// 双重断言用于不直接兼容的类型转换(谨慎使用)
const str = 'hello' as any as number; // 不推荐

6. 函数和类

typescript
// 基本函数类型
function add(x: number, y: number): number {
  return x + y;
}

// 可选参数
function greeting(name?: string): string {
  return name ? `你好, ${name}` : '你好!';
}

// 默认参数
function welcome(name: string = '访客'): string {
  return `欢迎, ${name}!`;
}

// 函数重载
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
  return typeof x === 'number'
    ? Number(x.toString().split('').reverse().join(''))
    : x.split('').reverse().join('');
}

// 基本类
class Person {
  // 成员属性
  private name: string;
  protected age: number;
  public readonly id: number;

  // 构造函数
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
    this.id = Math.random();
  }

  // 实例方法
  public sayHello(): void {
    console.log(`大家好,我是${this.name},今年${this.age}岁`);
  }

  // 静态方法
  static createPerson(name: string, age: number): Person {
    return new Person(name, age);
  }

  // getter/setter
  get personName(): string {
    return this.name;
  }

  set personName(value: string) {
    this.name = value;
  }
}

// 类的继承
class Student extends Person {
  private grade: string;

  constructor(name: string, age: number, grade: string) {
    super(name, age);
    this.grade = grade;
  }

  public study(): void {
    console.log(`${this.personName}正在学习`);
  }
}

// 接口实现
interface Flyable {
  fly(): void;
}

class Bird implements Flyable {
  fly(): void {
    console.log('鸟儿在飞翔');
  }
}

总结

  • 尽量避免使用 any 类型,它会失去 TypeScript 的类型保护
  • 优先使用 unknown 替代 any,因为 unknown 更安全
  • 合理使用类型推论,避免过度类型标注
  • 必要时使用类型断言,但要谨慎使用
ts
const a = 12;

联合和交叉类型

1. 联合类型(Union Types)

  • 使用 | 符号定义可以是多种类型之一的值
typescript
// 基础联合类型
let mixedType: string | number;
mixedType = 'hello'; // OK
mixedType = 123; // OK
mixedType = true; // Error

// 函数参数的联合类型
function printId(id: number | string) {
  if (typeof id === 'string') {
    console.log(id.toUpperCase());
  } else {
    console.log(id);
  }
}

// 数组的联合类型
let arr: (number | string)[] = [1, 'a', 2, 'b'];

2. 交叉类型(Intersection Types)

  • 使用 & 符号将多个类型合并为一个类型
typescript
// 基础交叉类型
interface BusinessPartner {
  name: string;
  credit: number;
}

interface Identity {
  id: number;
  email: string;
}

type Employee = BusinessPartner & Identity;

// 使用交叉类型
let emp: Employee = {
  name: '张三',
  credit: 100,
  id: 1234,
  email: 'zhangsan@example.com',
};

3. 类型保护(Type Guards)

typescript
// 使用 typeof 进行类型保护
function padLeft(value: string, padding: string | number) {
  if (typeof padding === 'number') {
    return ' '.repeat(padding) + value;
  }
  return padding + value;
}

// 使用 instanceof 进行类型保护
class Bird {
  fly() {
    console.log('鸟儿飞');
  }
}
class Fish {
  swim() {
    console.log('鱼儿游');
  }
}

function move(pet: Bird | Fish) {
  if (pet instanceof Bird) {
    pet.fly();
  } else {
    pet.swim();
  }
}

// 使用 in 操作符进行类型保护
interface Admin {
  name: string;
  privileges: string[];
}

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

type UnknownEmployee = Employee | Admin;

function printEmployeeInfo(emp: UnknownEmployee) {
  console.log(`名字: ${emp.name}`);

  if ('privileges' in emp) {
    // 现在 TypeScript 知道这是 Admin 类型
    console.log(`特权: ${emp.privileges.join(', ')}`);
  }

  if ('startDate' in emp) {
    // 现在 TypeScript 知道这是 Employee 类型
    console.log(`入职时间: ${emp.startDate}`);
  }
}

4. 可辨识联合(Discriminated Unions)

typescript
interface Circle {
  kind: 'circle';
  radius: number;
}

interface Square {
  kind: 'square';
  sideLength: number;
}

type Shape = Circle | Square;

function getArea(shape: Shape) {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'square':
      return shape.sideLength ** 2;
  }
}

总结

  • 联合类型:

    • 当值可能是多种类型之一时使用
    • 配合类型保护确保类型安全
    • 避免过多类型的联合,会增加代码复杂度
  • 交叉类型:

    • 用于组合多个接口或类型
    • 确保合并的类型之间没有冲突
    • 优先使用接口继承而不是复杂的交叉类型

包装类型(Wrapper Types)

在 TypeScript 中,包装类型是对原始类型的对象包装。主要包括 String、Number、Boolean、Symbol、BigInt。

包装类型与原始类型的区别

  1. 类型不同
typescript
// 原始类型
let str: string = 'hello';
let num: number = 123;
let bool: boolean = true;

// 包装类型
let strObj: String = new String('hello');
let numObj: Number = new Number(123);
let boolObj: Boolean = new Boolean(true);

// 类型检查
typeof str; // 'string'
typeof strObj; // 'object'
  1. 内存占用
typescript
// 原始类型直接存储值
let x: number = 123;

// 包装类型需要创建对象,占用更多内存
let y: Number = new Number(123);
  1. 方法调用
typescript
// 原始类型会被临时转换为包装类型来调用方法
let str: string = 'hello';
console.log(str.toUpperCase()); // 'HELLO'

// 包装类型可以直接调用方法
let strObj: String = new String('hello');
console.log(strObj.toUpperCase()); // 'HELLO'
  1. 相等性比较
typescript
let str: string = 'hello';
let strObj: String = new String('hello');

console.log(str === 'hello'); // true
console.log(strObj === 'hello'); // false
console.log(strObj.valueOf() === 'hello'); // true

总结

  • 除非特殊需求,始终使用原始类型(小写)而不是包装类型(大写)
  • 避免使用 new String()、new Number()、new Boolean() 创建包装对象
  • 如果需要类型转换,使用 String()、Number()、Boolean() 函数而不是构造函数
  • 记住包装类型主要是为了提供额外的方法和属性,而不是为了替代原始类型

TypeScript 类型层级

TypeScript 的类型系统呈现出一个层级结构,从顶层到底层可以表示为:

any / unknown (顶层类型)

Object / object

包装类型(String/Number/Boolean/Symbol/BigInt)

原始类型(string/number/boolean/symbol/bigint)

字面量类型("hello"/42/true)

never (底层类型)

类型兼容性规则

1. 基础类型兼容性

typescript
// 1.1 基础类型的层级关系
let str: string = 'hello';
let num: number = 42;
let bool: boolean = true;

let anyVal: any = str; // ✅ 任何类型都可以赋值给 any
let unknownVal: unknown = str; // ✅ 任何类型都可以赋值给 unknown

// 1.2 字面量类型到基础类型
let literalStr: 'hello' = 'hello';
let literalNum: 42 = 42;
let strVal: string = literalStr; // ✅ 字面量类型可以赋值给对应的基础类型
let numVal: number = literalNum; // ✅ 字面量类型可以赋值给对应的基础类型

// 1.3 联合类型的兼容性
type StringOrNumber = string | number;
let union: StringOrNumber = 'hello'; // ✅
union = 42; // ✅ 联合类型可以接受其中任一类型的值

2. 对象类型兼容性

typescript
// 2.1 基本对象兼容性
interface Point2D {
  x: number;
  y: number;
}

interface Point3D {
  x: number;
  y: number;
  z: number;
}

let point2D: Point2D;
let point3D: Point3D = { x: 1, y: 2, z: 3 };
point2D = point3D; // ✅ 多的属性可以赋值给少的

// 2.2 可选属性
interface Options {
  title?: string;
  width?: number;
}

let opt1: Options = {}; // ✅ 可选属性都可以不传
let opt2: Options = { title: '标题' }; // ✅ 可以只传部分可选属性

// 2.3 只读属性
interface ReadonlyPoint {
  readonly x: number;
  readonly y: number;
}

interface MutablePoint {
  x: number;
  y: number;
}

let readonlyPoint: ReadonlyPoint;
let mutablePoint: MutablePoint;

readonlyPoint = mutablePoint; // ✅ 可变类型可以赋值给只读类型
// mutablePoint = readonlyPoint;  // ❌ 只读类型不能赋值给可变类型

3. 函数类型兼容性

typescript
// 3.1 参数数量
type Func1 = (a: number) => void;
type Func2 = (a: number, b?: string) => void;

let f1: Func1;
let f2: Func2;

f1 = f2; // ✅ 参数多的函数可以赋值给参数少的函数
// f2 = f1;  // ❌ 参数少的函数不能赋值给参数多的函数

// 3.2 参数类型
type Handler1 = (event: Event) => void;
type Handler2 = (event: MouseEvent) => void;

let h1: Handler1;
let h2: Handler2;

h1 = h2; // ✅ 更具体的类型可以赋值给更宽泛的类型
// h2 = h1;  // ❌ 更宽泛的类型不能赋值给更具体的类型

// 3.3 返回值类型
type Getter1 = () => { x: number };
type Getter2 = () => { x: number; y: number };

let g1: Getter1;
let g2: Getter2;

g1 = g2; // ✅ 返回值类型遵循对象类型的兼容性规则

4. 泛型类型兼容性

typescript
// 4.1 基本泛型兼容性
interface Box<T> {
  value: T;
}

let numberBox: Box<number> = { value: 42 };
let stringBox: Box<string> = { value: 'hello' };

// numberBox = stringBox;  // ❌ 不同的泛型参数导致类型不兼容

// 4.2 泛型函数兼容性
interface Event<T> {
  data: T;
}

type Handler<T> = (event: Event<T>) => void;

let numberHandler: Handler<number>;
let anyHandler: Handler<any>;

anyHandler = numberHandler; // ✅ 具体类型的处理器可以赋值给 any 类型的处理器

5. 类的兼容性

typescript
// 5.1 基本类兼容性
class Animal {
  name: string = '';
}

class Dog extends Animal {
  breed: string = '';
}

let animal: Animal;
let dog: Dog = new Dog();

animal = dog; // ✅ 子类实例可以赋值给父类类型

// 5.2 私有成员和保护成员
class Parent {
  private secret: string = '';
  protected shared: string = '';
}

class Child extends Parent {
  useShared() {
    this.shared; // ✅ 可以访问protected成员
    // this.secret;  // ❌ 不能访问private成员
  }
}

// 5.3 静态成员和实例成员
class StaticClass {
  static count: number = 0;
  instanceValue: string = '';
}

type StaticType = typeof StaticClass; // 获取类的静态部分类型
type InstanceType = StaticClass; // 获取类的实例类型

总结

  1. 类型兼容性基于结构类型系统,关注的是类型的结构而不是名称
  2. 对象类型的兼容性取决于它们的属性
  3. 函数类型的兼容性取决于参数和返回值
  4. 使用类型断言时要谨慎,优先使用类型收窄
  5. 理解泛型的类型兼容性对于编写灵活的代码很重要