掌握 TypeScript 语法的绝佳速览指南

发布时间 2023-08-02 15:35:50作者: 周瑜已被使用

1.环境搭建

1.1 安装

  • 安装 typescript
var code = "810377bc-9dfc-4e7d-a66e-3c2c3c49c7c0"
yarn add typescript -g
  • 安装 ts-node

    ts-node 是一个在内存中运行的 TypeScript 运行时,它可以直接运行 ts 文件,而不需要将 ts 文件编译成 js 文件

yarn add ts-node -g
  • 安装 @types/node

    @types/node 是 node.js 的类型定义文件,如果不安装,ts-node 会报错

yarn add @types/node -g

1.2 配置

  • 初始化 tsconfig.json
tsc --init
  • 配置 tsconfig.json
{
  "compilerOptions": {
    "target": "es2016", // 编译目标为 ES2016
    "module": "commonjs", // 模块化为 CommonJS
    "lib": ["es2016"], // 引入 ES2016 的类型声明
    "outDir": "./dist", // 输出目录
    "strict": true, // 开启严格模式 (所有严格的类型检查选项) 严格模式会包含 strictNullChecks
    "strictNullChecks": true // 开启严格的空值检查 (null 和 undefined) 不能赋值给其他类型
  },
  "include": ["src/**/*"] // 需要编译的文件
}

1.3 运行

  • 运行 ts 文件
# --watch 只监听 src 目录下的文件
# -e ts 只监听 ts 文件
# --exec ts-node 执行 ts-node src/index.ts
npx nodemon --watch src -e ts --exec ts-node src/index.ts

2.基础类型

2.1 布尔值

let isDone: boolean = false;

2.2 数字

let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;

2.3 字符串

let name: string = "bob";
name = "smith";

2.4 数组

let list: number[] = [1, 2, 3];
let list: (number | string)[] = [1, 2, 3, "4"];
let list: Array<number> = [1, 2, 3];
let list: Array<number | string> = [1, 2, 3, "4"];

2.5 元组 Tuple

元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同

let x: [string, number];
x = ["hello", 10]; // OK
x = [10, "hello"]; // Error

2.6 枚举

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

2.7 Any

any 类型是 TypeScript 中最灵活的类型,它可以让你在编译时可选择地包含或移除类型检查。 与普通的 Object 类型不同,Object 类型的变量只是允许你给它赋任意值,但是却不能够在它上面调用任意的方法,即便它真的有这些方法

let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean

2.8 Void

某种程度上来说,void 类型像是与 any 类型相反,它表示没有任何类型。 当一个函数没有返回值时,你通常会见到其返回值类型是 void

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

2.9 Null 和 Undefined

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

2.10 Never

never 类型表示的是那些永不存在的值的类型。 例如,never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型
void 和 never 的区别是,void 表示没有返回值,never 表示永远不会有返回值

function error(message: string): never {
  throw new Error(message);
}
function fail(): never {
  return error("Something failed");
}
function infiniteLoop(): never {
  while (true) {}
}
//箭头函数表达式的返回值类型
const foo = (x: string): never => {
  throw new Error(x);
};

2.11 Object

declare function create(o: object | null): void;
create({ prop: 0 }); // OK
create(null); // OK
create(42); // Error
create("string"); // Error
create(false); // Error
create(undefined); // Error
create(Symbol()); // Error
create(1n); // Error
create(true); // Error

2.12 类型断言

let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
let strLength: number = (someValue as string).length;
let strLength: number = someValue.length; // Error

2.13 类型推论

TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查
如果定义的时候赋值了,会根据值的类型推断出一个类型

let x = 3;
x = "3"; // Error
let x = [0, 1, null];
x = ["1", 2, 3]; // Error

window.onmousedown = function (mouseEvent) {
  console.log(mouseEvent.button); //<- Error
};
window.onmousedown = function (mouseEvent: any) {
  console.log(mouseEvent.button); //<- Now, no error is given
};

let x = ["hello", 10];
x[0] = "world";
x[1] = 100;
x[0] = 100; // Error
x[1] = "hello"; // Error

2.15 接口

在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型

interface Person {
  name: string;
  age: number;
}
let tom: Person = {
  name: "Tom",
  age: 25,
};

3.进阶类型

3.1 交叉类型

交叉类型是将多个类型合并为一个类型,新的类型将具有所有类型的特性

interface Dog {
  run(): void;
}
interface Cat {
  jump(): void;
}
let pet: Dog & Cat = {
  run() {},
  jump() {},
};

3.2 联合类型

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

let myFavoriteNumber: string | number;
myFavoriteNumber = "seven";
myFavoriteNumber = 7;
myFavoriteNumber = true; // Error

3.3 类型保护

TypeScript 能够在特定的区块中保证变量属于某种确定的类型,可以在此区块中放心地引用此类型的属性,或者调用此类型的方法

interface Cat {
  name: string;
  run(): void;
}
interface Fish {
  name: string;
  swim(): void;
}
function getName(animal: Cat | Fish) {
  return animal.name;
}
function isFish(animal: Cat | Fish) {
  if (typeof (animal as Fish).swim === "function") {
    return true;
  }
  return false;
}

3.4 类型别名

类型别名用来给一个类型起个新名字

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
  if (typeof n === "string") {
    return n;
  } else {
    return n();
  }
}

3.5 字符串字面量类型

字面量类型是指,值可以作为类型

let a: "A";
a = "A"; // OK
a = "B"; // Error

let b: 1 | 2 | 3;
b = 1; // OK
b = 4; // Error

let c: [1, "2", true]; // (number | string | boolean)[
c = [1, "2", true]; // OK
c = [1, "2", false]; // Error

let d: { name: string; age: number };
d = { name: "Tom", age: 25 }; // OK

let e: { name: string; age: number }[];
e = [{ name: "Tom", age: 25 }]; // OK
e = [
  { name: "Tom", age: 25 },
  { name: "Jack", age: 18 },
]; // OK

let f: [];
f = []; // OK
f = [1]; // Error

let g: ["2", string];
g = ["2", "2"]; // OK
g = ["2", 2]; // Error

4.函数

4.1 函数声明

在 JavaScript 中,有两种常见的定义函数的方式——函数声明(Function Declaration)和函数表达式(Function Expression)

function sum(x: number, y: number): number {
  return x + y;
}
let mySum = function (x: number, y: number): number {
  return x + y;
};

4.2 函数表达式

在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型

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

4.3 接口定义函数的形状

我们也可以使用接口的方式来定义一个函数需要符合的形状

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

4.4 可选参数

与接口中的可选属性类似,我们用 ? 表示可选的参数

function buildName(firstName: string, lastName?: string) {
  if (lastName) {
    return firstName + " " + lastName;
  } else {
    return firstName;
  }
}
let tomcat = buildName("Tom", "Cat");
let tom = buildName("Tom");

4.5 参数默认值

TypeScript 会将添加了默认值的参数识别为可选参数

function buildName(firstName: string, lastName: string = "Cat") {
  return firstName + " " + lastName;
}
let tomcat = buildName("Tom", "Cat");
let tom = buildName("Tom");

4.6 剩余参数

ES6 中,可以使用 …rest 的方式获取函数中的剩余参数(rest 参数)

function push(array: any[], ...items: any[]) {
  items.forEach(function (item) {
    array.push(item);
  });
}
let a = [];
push(a, 1, 2, 3);

4.7 重载

重载允许一个函数接受不同数量或类型的参数时,作出不同的处理

function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
  if (typeof x === "number") {
    return Number(x.toString().split("").reverse().join(""));
  } else if (typeof x === "string") {
    return x.split("").reverse().join("");
  }
}

5.类

5.1 类的概念

TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 public、private 和 protected

class Animal {
  public name;
  public constructor(name) {
    this.name = name;
  }
}
let a = new Animal("Jack");
console.log(a.name); // Jack
a.name = "Tom";
console.log(a.name); // Tom

5.2 类的继承

使用 extends 关键字来创建子类

class Animal {
  public name;
  public constructor(name) {
    this.name = name;
  }
}
class Cat extends Animal {
  constructor(name) {
    super(name); // 调用父类的 constructor(name)
    console.log(this.name);
  }
  sayHi() {
    return `Meow, My name is ${this.name}`;
  }
}
let cat = new Cat("Tom"); // Tom
console.log(cat.sayHi()); // Meow, My name is Tom

5.3 类的成员修饰符

TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 public、private 和 protected

class Animal {
  public name;
  private age;
  protected weight;
  public constructor(name) {
    this.name = name;
  }
}
let a = new Animal("Jack");
console.log(a.name); // Jack
a.name = "Tom";
console.log(a.name); // Tom
console.log(a.age); // Error
console.log(a.weight); // Error

5.4 readonly 修饰符

使用 readonly 修饰符将属性设置为只读,只读属性必须在声明时或构造函数里被初始化

class Animal {
  readonly name;
  public constructor(name) {
    this.name = name;
  }
}
let a = new Animal("Jack");
console.log(a.name); // Jack
a.name = "Tom"; // Error

5.5 参数属性

参数属性可以方便地让我们在一个地方定义并初始化一个成员

class Animal {
  public constructor(public name) {}
}
// 等同于
class Animal {
  public name;
  public constructor(name) {
    this.name = name;
  }
}

5.6 静态属性

使用 static 修饰符修饰的属性是静态属性

class Animal {
  static num = 42;
  public constructor() {}
}
console.log(Animal.num); // 42

5.7 抽象类

abstract 用于定义抽象类和其中的抽象方法

abstract class Animal {
  public name;
  public constructor(name) {
    this.name = name;
  }
  public abstract sayHi();
}
let a = new Animal("Jack"); // Error

class Cat extends Animal {
  public sayHi() {
    console.log(`${this.name} hi.`);
  }
}
let cat = new Cat("Tom");
cat.sayHi(); // Tom hi.

5.8 类的类型

给类加上 TypeScript 的类型很简单,与接口类似

class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  sayHi(): string {
    return `My name is ${this.name}`;
  }
}
let a: Animal = new Animal("Jack");
console.log(a.sayHi()); // My name is Jack

5.9 访问器

TypeScript 支持通过 getters/setters 来截取对对象成员的访问

class Animal {
  constructor(name) {
    this.name = name;
  }
  get name() {
    return "Jack";
  }
  set name(value) {
    console.log("setter: " + value);
  }
}
let a = new Animal("Kitty"); // setter: Kitty
a.name = "Tom"; // setter: Tom
console.log(a.name); // Jack

5.10 静态方法

使用 static 修饰符修饰的方法是静态方法

class Animal {
  static isAnimal(a) {
    return a instanceof Animal;
  }
}
let a = new Animal("Jack");
Animal.isAnimal(a); // true
a.isAnimal(a); // TypeError: a.isAnimal is not a function

6.类与接口

6.1 类实现接口

实现(implements)是面向对象中的一个重要概念。 一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 implements 关键字来实现。这个特性大大提高了面向对象的灵活性

interface Alarm {
  alert(): void;
}
class Door {}
class SecurityDoor extends Door implements Alarm {
  alert() {
    console.log("SecurityDoor alert");
  }
}
class Car implements Alarm {
  alert() {
    console.log("Car alert");
  }
}

6.2 接口继承接口

接口与接口之间可以是继承关系

interface Alarm {
  alert(): void;
}
interface LightableAlarm extends Alarm {
  lightOn(): void;
  lightOff(): void;
}

6.3 接口继承类

接口也可以继承类

class Point {
  x: number;
  y: number;
  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
}
interface Point3d extends Point {
  z: number;
}
let point3d: Point3d = { x: 1, y: 2, z: 3 };
// 等同于
interface Point3d {
  x: number;
  y: number;
  z: number;
}

7.泛型

7.1 泛型的概念

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性

function createArray<T>(length: number, value: T): Array<T> {
  let result: T[] = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
}
createArray<string>(3, "x"); // ['x', 'x', 'x']

7.2 多个类型参数

定义泛型的时候,可以一次定义多个类型参数

function swap<T, U>(tuple: [T, U]): [U, T] {
  return [tuple[1], tuple[0]];
}
swap([7, "seven"]); // ['seven', 7]

7.3 泛型约束

我们需要限制泛型的类型,让它不要直接使用任何类型

interface Lengthwise {
  length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}
loggingIdentity(7); // Error
loggingIdentity("seven"); // OK
loggingIdentity({ length: 10, value: 3 }); // OK

function copyFields<T extends U, U>(target: T, source: U): T {
  for (let id in source) {
    target[id] = (<T>source)[id];
  }
  return target;
}
let x = { a: 1, b: 2, c: 3, d: 4 };
copyFields(x, { b: 10, d: 20 }); // OK

7.4 泛型接口

除了泛型类,还有泛型接口

interface CreateArrayFunc {
  <T>(length: number, value: T): Array<T>;
}
let createArray: CreateArrayFunc;
createArray = function <T>(length: number, value: T): Array<T> {
  let result: T[] = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
};
createArray(3, "x"); // ['x', 'x', 'x']

7.5 泛型类

与泛型接口类似,泛型也可以用于类的类型定义中

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;
};

7.6 泛型参数的默认类型

在 TypeScript 2.3 以后,我们可以为泛型中的类型参数指定默认类型

function createArray<T = string>(length: number, value: T): Array<T> {
  let result: T[] = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
}
createArray(3, "x"); // ['x', 'x', 'x']

7.7 泛型的约束与索引类型

在 TypeScript 中,我们可以使用 [ ] 的形式来访问 T 的元素类型

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"); // okay
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.