TypeScript看这篇就够了

TypeScript 技术文档

1. 简介

TypeScript 是一种由微软开发的开源编程语言,它在 JavaScript 的基础上增加了静态类型定义。TypeScript 可以编译成纯 JavaScript,因此可以在任何支持 JavaScript 的环境中运行。它的设计目标是让大型 JavaScript 项目更容易开发和维护。

主要特点:

  • 类型系统:TypeScript 提供静态类型检查,能够在编译时发现潜在的错误。
  • 现代 JavaScript 特性:支持 ES6/ES7 的所有新特性,比如箭头函数、解构赋值、模块化等。
  • 兼容性:TypeScript 是 JavaScript 的超集,所有合法的 JavaScript 代码在 TypeScript 中也是合法的。
  • 强大的工具支持:TypeScript 有丰富的 IDE 支持,如 Visual Studio Code,可以提供代码补全、重构、导航等功能。

2. 安装与配置

要开始使用 TypeScript,你需要安装 Node.js 和 npm。安装完 Node.js 后,可以使用 npm 来安装 TypeScript:

npm install -g typescript

安装完成后,你可以使用 tsc 命令来编译 TypeScript 文件:

tsc --version

创建一个 TypeScript 文件,例如 hello.ts

let message: string = 'Hello, TypeScript!';
console.log(message);

编译并运行:

tsc hello.ts
node hello.js

为了更方便地管理 TypeScript 项目,可以使用 tsconfig.json 文件来配置编译选项。创建一个默认的 tsconfig.json 文件:

tsc --init

tsconfig.json 示例:

{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

3. 基本类型

TypeScript 提供了一系列基本类型,涵盖了 JavaScript 中的所有基本数据类型。

3.1 布尔值

let isDone: boolean = false;

3.2 数字

let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;

3.3 字符串

let color: string = 'blue';
color = 'red';

可以使用模板字符串:

let fullName: string = `Bob Bobbington`;
let age: number = 37;
let sentence: string = `Hello, my name is ${fullName}. I'll be ${age + 1} years old next month.`;

3.4 数组

let list: number[] = [1, 2, 3];

或使用泛型数组类型:

let list: Array<number> = [1, 2, 3];

3.5 元组

let x: [string, number];
x = ['hello', 10];

3.6 枚举

enum Color {Red, Green, Blue}
let c: Color = Color.Green;

3.7 Any

当你不确定变量类型时,可以使用 any 类型:

let notSure: any = 4;
notSure = 'maybe a string instead';
notSure = false;

3.8 Void

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

function warnUser(): void {
  console.log('This is my warning message');
}

3.9 Null 和 Undefined

let u: undefined = undefined;
let n: null = null;

3.10 Never

表示那些永不存在的值的类型。例如,永远不会返回的函数表达式或箭头函数的返回值类型:

function error(message: string): never {
  throw new Error(message);
}

3.11 Object

表示非原始类型:

declare function create(o: object | null): void;
create({ prop: 0 });
create(null);

4. 接口

接口是 TypeScript 中的核心原则之一,用于定义对象的类型。接口能够描述一个对象的形状,能够检查对象是否符合特定的结构。

4.1 简单示例

interface LabelledValue {
  label: string;
}

function printLabel(labelledObj: LabelledValue) {
  console.log(labelledObj.label);
}

let myObj = { size: 10, label: 'Size 10 Object' };
printLabel(myObj);

在这个例子中,LabelledValue 接口定义了一个 label 属性,并且 printLabel 函数期望一个实现了 LabelledValue 接口的对象作为参数。即使 myObj 有其他属性,但只要它至少有一个 label 属性,TypeScript 就会认为它是合法的。

4.2 可选属性

可选属性接口允许一些属性存在,也允许一些属性不存在:

interface SquareConfig {
  color?: string;
  width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {
  let newSquare = { color: 'white', area: 100 };
  if (config.color) {
    newSquare.color = config.color;
  }
  if (config.width) {
    newSquare.area = config.width * config.width;
  }
  return newSquare;
}

let mySquare = createSquare({ color: 'black' });

在这个例子中,SquareConfig 接口定义了 colorwidth 可选属性。createSquare 函数根据传入的 config 对象动态地创建一个新的对象。

4.3 只读属性

一些属性可以在对象刚刚创建的时候修改其值,而在此之后将是只读的:

interface Point {
  readonly x: number;
  readonly y: number;
}

let p1: Point = { x: 10, y: 20 };
p1.x = 5; // 错误:不能改变只读属性

在这个例子中,xy 属性是只读的,因此它们的值一旦被赋值就不能再被修改。

4.4 函数类型

接口也可以描述函数类型:

interface SearchFunc {
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function (source: string, subString: string) {
  let result = source.search(subString);
  return result > -1;
};

在这个例子中,SearchFunc 接口定义了一个函数类型,该函数接收两个字符串参数并返回一个布尔值。

4.5 可索引类型

接口可以描述那些能够通过索引得到某种类型的对象,这在对象上可以用数字索引或字符串索引:

interface StringArray {
  [index: number]: string;
}

let myArray: StringArray;
myArray = ['Bob', 'Fred'];

let myStr: string = myArray[0];

在这个例子中,StringArray 接口描述了具有数字索引的数组类型,并且返回值是字符串类型。

5. 类

TypeScript 支持所有 JavaScript 的类特性,并且增加了一些新的特性。

5.1 简单类

class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    return 'Hello, ' + this.greeting;
  }
}

let greeter = new Greeter('world');

在这个例子中,Greeter 类有一个 greeting 属性和一个 greet 方法。greet 方法返回一个问候字符串。

5.2 继承

类可以扩展其他类,这意味着一个类可以继承另一个类的属性和方法:

class Animal {
  name: string;
  constructor(theName: string) {
    this.name = theName;
  }
  move(distanceInMeters: number = 0) {
    console.log(`${this.name} moved ${distanceInMeters}m.`);
  }
}

class Snake extends Animal {
  constructor(name: string) {
    super(name);
  }
  move(distanceInMeters = 5) {
    console.log('Slithering...');
    super.move(distanceInMeters);
  }
}

let sam = new Snake('Sammy the Python');
sam.move();

在这个例子中,Snake 类扩展了 Animal 类,并且重写了 move 方法。

5.3 公共,私有与受保护修饰符

TypeScript 中的成员默认是公共的。你也可以用 private 修饰符将成员标记为私有的:

typescript
class Animal {
  private name: string;
  constructor(theName: string) {
    this.name = theName;
  }
}

new Animal('Cat').name; // 错误:name 是私有的

可以使用 protected 修饰符声明受保护的成员,这些成员只能在类本身及其子类中访问:

class Person {
  protected name: string;
  constructor(name: string) {
    this.name = name;
  }
}

class Employee extends Person {
  private department: string;

  constructor(name: string, department: string) {
    super(name);
    this.department = department;
  }

  getElevatorPitch() {
    return `Hello, my name is ${this.name} and I work in ${this.department}.`;
  }
}

let howard = new Employee('Howard', 'Sales');
console.log(howard.getElevatorPitch()); // 有效
console.log(howard.name); // 错误

5.4 readonly 修饰符

你可以使用 readonly 关键字将属性设置为只读:

class Octopus {
  readonly name: string;
  readonly numberOfLegs: number = 8;

  constructor(theName: string) {
    this.name = theName;
  }
}

let dad = new Octopus('Man with the 8 strong legs');
dad.name = 'Man with the 3-piece suit'; // 错误!name 是只读的

5.5 存取器

TypeScript 支持通过 getset 关键字来定义存取器:

class Employee {
  private _fullName: string;

  get fullName(): string {
    return this._fullName;
  }

  set fullName(newName: string) {
    this._fullName = newName;
  }
}

let employee = new Employee();
employee.fullName = 'Bob Smith';
console.log(employee.fullName);

在这个例子中,Employee 类有一个私有属性 _fullName,并且通过存取器来设置和获取它的值。

6. 函数

TypeScript 中的函数与 JavaScript 中的函数类似,但在参数和返回类型上提供了更多的类型检查。

6.1 函数类型

你可以为函数的参数和返回值指定类型:

function add(x: number, y: number): number {
  return x + y;
}

let myAdd: (x: number, y: number) => number = function (x: number, y: number): number {
  return x + y;
};

6.2 可选参数和默认参数

可以通过在参数名旁使用 ? 来实现可选参数:

function buildName(firstName: string, lastName?: string): string {
  if (lastName) {
    return firstName + ' ' + lastName;
  } else {
    return firstName;
  }
}

let result1 = buildName('Bob'); // 有效
let result2 = buildName('Bob', 'Adams'); // 有效

还可以为参数提供默认值:

function buildName(firstName: string, lastName = 'Smith'): string {
  return firstName + ' ' + lastName;
}

let result1 = buildName('Bob'); // 有效,返回 "Bob Smith"
let result2 = buildName('Bob', 'Adams'); // 有效,返回 "Bob Adams"

6.3 剩余参数

可以使用 ... 语法将所有参数收集到一个变量中:

function buildName(firstName: string, ...restOfName: string[]) {
  return firstName + ' ' + restOfName.join(' ');
}

let employeeName = buildName('Joseph', 'Samuel', 'Lucas', 'MacKinzie');

6.4 this 和箭头函数

在 TypeScript 中,this 的值取决于函数调用的位置。可以使用箭头函数来正确地捕获 this 值:

let deck = {
  suits: ['hearts', 'spades', 'clubs', 'diamonds'],
  cards: Array(52),
  createCardPicker: function () {
    return () => {
      let pickedCard = Math.floor(Math.random() * 52);
      let pickedSuit = Math.floor(pickedCard / 13);

      return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
    };
  }
};

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

console.log('card: ' + pickedCard.card + ' of ' + pickedCard.suit);

在这个例子中,箭头函数不会创建自己的 this,它会捕获 deck 对象的 this 值。

7. 泛型

泛型是能够创建可重用组件的一种工具,能够使组件可以支持多种类型的数据。

7.1 泛型函数

function identity<T>(arg: T): T {
  return arg;
}

let output = identity<string>('myString'); // 手动指定类型
let output2 = identity('myString'); // 类型推断

7.2 泛型接口

interface GenericIdentityFn<T> {
  (arg: T): T;
}

function identity<T>(arg: T): T {
  return arg;
}

let myIdentity: GenericIdentityFn<number> = identity;

在这个例子中,GenericIdentityFn 接口描述了一个泛型函数类型,并且 myIdentity 是一个特定类型的泛型函数。

7.3 泛型类

class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
  return x + y;
};

在这个例子中,GenericNumber 是一个泛型类,它可以处理任意类型的数字。

7.4 泛型约束

有时候我们想要限制某种类型的泛型函数,这时候可以使用泛型约束:

interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

loggingIdentity({ length: 10, value: 3 });

在这个例子中,loggingIdentity 函数要求传入的参数必须有 length 属性。

7.5 在泛型约束中使用类型参数

function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

let x = { a: 1, b: 2, c: 3, d: 4 };

getProperty(x, 'a'); // 有效
getProperty(x, 'm'); // 错误:类型“"m"”的参数不能赋给类型“"a" | "b" | "c" | "d"”的参数

在这个例子中,getProperty 函数接受一个对象和一个属性名称,并返回该属性的值。K 被约束为 T 的属性名称。

8. 模块

模块在 TypeScript 中是用于组织代码的主要方式,它们有助于分离代码和避免全局作用域的污染。

8.1 导出

export interface StringValidator {
  isAcceptable(s: string): boolean;
}

export const numberRegexp = /^[0-9]+$/;

8.2 导入

import { StringValidator } from './StringValidator';

let myValidator: StringValidator;

你可以使用 export default 导出一个默认的对象:

export default class ZipCodeValidator {
  static numberRegexp = /^[0-9]+$/;
  isAcceptable(s: string): boolean {
    return ZipCodeValidator.numberRegexp.test(s);
  }
}

然后可以使用 import 导入默认对象:

import ZipCodeValidator from './ZipCodeValidator';

let myValidator = new ZipCodeValidator();

9. 类型推断

TypeScript 能够根据代码中的一些简单的规则推断变量的类型。如果变量声明时没有指定类型,TypeScript 会自动推断出一个类型。

9.1 基础示例

let x = 3; // x 被推断为 number 类型

9.2 最佳通用类型

当需要从多个表达式中推断类型时,TypeScript 会选择最合适的通用类型:

let x = [0, 1, null]; // x 的类型推断为 (number | null)[]

10. 类型兼容性

TypeScript 中的类型兼容性是基于结构子类型的。结构类型系统是基于类型的成员来确定类型的兼容性。

10.1 接口兼容性

interface Named {
  name: string;
}

class Person {
  name: string;
}

let p: Named;
p = new Person(); // OK, 因为 Person 有一个兼容的 name 属性

10.2 函数兼容性

let x = (a: number) => 0;
let y = (b: number, s: string) => 0;

y = x; // OK
x = y; // 错误


11. 高级类型

TypeScript 提供了许多高级类型操作,帮助你在编写复杂类型定义时提供更强的灵活性。

11.1 交叉类型

交叉类型 & 是将多个类型合并为一个类型:

function extend<T, U>(first: T, second: U): T & U {
  let result = <T & U>{};
  for (let id in first) {
    (result as any)[id] = (first as any)[id];
  }
  for (let id in second) {
    if (!result.hasOwnProperty(id)) {
      (result as any)[id] = (second as any)[id];
    }
  }
  return result;
}

let x = extend({ a: 'hello' }, { b: 42 });
let a = x.a; // string
let b = x.b; // number

11.2 联合类型

联合类型 | 表示一个值可以是几种类型之一:

function padLeft(value: string, padding: string | number) {
  if (typeof padding === 'number') {
    return Array(padding + 1).join(' ') + value;
  }
  if (typeof padding === 'string') {
    return padding + value;
  }
  throw new Error(`Expected string or number, got '${typeof padding}'.`);
}

padLeft('Hello world', 4); // 返回 "    Hello world"
padLeft('Hello world', '>>>'); // 返回 ">>>Hello world"

11.3 类型别名

类型别名可以为类型起一个新名字:

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;

function getName(n: NameOrResolver): Name {
  if (typeof n === 'string') {
    return n;
  } else {
    return n();
  }
}

11.4 字面量类型

字面量类型约束一个变量的值只能是某个特定的值:

type Easing = 'ease-in' | 'ease-out' | 'ease-in-out';

class UIElement {
  animate(dx: number, dy: number, easing: Easing) {
    if (easing === 'ease-in') {
      // ...
    } else if (easing === 'ease-out') {
      // ...
    } else if (easing === 'ease-in-out') {
      // ...
    } else {
      // 错误:参数必须是 'ease-in','ease-out' 或 'ease-in-out'
    }
  }
}

let button = new UIElement();
button.animate(0, 0, 'ease-in'); // 有效
button.animate(0, 0, 'uneasy'); // 错误:参数不是有效的字面量

12. 装饰器

装饰器是一个特殊类型的声明,能够被附加到类声明、方法、访问器、属性或参数上。装饰器使用 @expression 这种形式,expression 必须求值为一个函数,它将在运行时被调用,被装饰的声明信息作为参数传入。

12.1 类装饰器

function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    return 'Hello, ' + this.greeting;
  }
}

12.2 方法装饰器

function enumerable(value: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.enumerable = value;
  };
}

class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }

  @enumerable(false)
  greet() {
    return 'Hello, ' + this.greeting;
  }
}

13. 编译选项

TypeScript 编译器可以通过命令行参数和 tsconfig.json 文件进行配置。

13.1 tsconfig.json

tsconfig.json 文件用于配置 TypeScript 项目。一个简单的示例:

{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "**/*.spec.ts"]
}

14. 常见问题与最佳实践

14.1 如何进行类型定义?

在 TypeScript 中进行类型定义时,推荐尽量使用接口,因为接口可以被类实现和扩展,并且更加灵活和易于阅读。

14.2 何时使用类型断言?

类型断言用于告诉编译器某个值的具体类型:

let someValue: any = 'this is a string';
let strLength: number = (someValue as string).length;

尽量避免过度使用类型断言,因为它可能隐藏潜在的类型错误。

14.3 如何处理第三方库的类型定义?

可以使用 DefinitelyTyped 项目提供的类型定义文件:

npm install @types/jquery --save-dev

这样就可以在 TypeScript 项目中使用 jQuery 的类型定义文件。

14.4 如何调试 TypeScript 代码?

可以通过生成 Source Map 文件来调试 TypeScript 代码:

{
  "compilerOptions": {
    "sourceMap": true
  }
}

这样就可以在调试工具中直接看到 TypeScript 代码,并且设置断点进行调试。

14.5 如何提高代码质量?

  1. 启用严格模式:在 tsconfig.json 中启用 strict 选项。
  2. 使用代码格式化工具:如 Prettier 来保持代码风格一致。
  3. 使用代码静态分析工具:如 ESLint 来发现和修复代码中的潜在问题。
  4. 编写单元测试:确保代码的正确性和稳定性。

总结

TypeScript 通过增加静态类型检查和现代 JavaScript 特性,为开发大型 JavaScript 项目提供了更强的开发体验和更高的代码质量。通过合理使用 TypeScript 的类型系统、接口、类、泛型、模块、装饰器等特性,开发者可以更高效地构建可维护、可扩展的 JavaScript 应用程序。

相关推荐

  1. TypeScript

    2024-06-09 14:18:03       12 阅读
  2. 掌握mysql,文章

    2024-06-09 14:18:03       25 阅读
  3. 掌握mysql,文章

    2024-06-09 14:18:03       24 阅读
  4. 入门SAM

    2024-06-09 14:18:03       14 阅读
  5. Rust async,

    2024-06-09 14:18:03       14 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-09 14:18:03       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-09 14:18:03       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-09 14:18:03       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-09 14:18:03       18 阅读

热门阅读

  1. 【分享】使用 Reducer 和 Context 拓展你的应用

    2024-06-09 14:18:03       13 阅读
  2. 力扣2799.统计完全子数组的数目

    2024-06-09 14:18:03       11 阅读
  3. lua中大数相乘的问题

    2024-06-09 14:18:03       10 阅读
  4. LeetCode 第401场周赛个人题解

    2024-06-09 14:18:03       12 阅读
  5. 二叉树的统一迭代法-前序中序后序-力扣

    2024-06-09 14:18:03       12 阅读