这是我参与「第五届青训营 」伴学笔记创作活动的第 7 天

TypeScript 介绍

  1. TypeScript 是 JavaScript 的超集,提供了 JavaScript 的一切功能,并提供了可选的静态类型、Mixin、类、接口和泛型等特性。
  2. TypeScript 的目标是经过其类型体系帮助及早发现过错并提高 JavaScript 开发效率。
  3. 经过 TypeScript 编译器或 Babel 转码器转译为 JavaScript 代码,可运转在任何浏览器,任何操作体系。
  4. 任何现有的 JavaScript 程序都能够运转在 TypeScript 环境中,并只对其中的 TypeScript 代码进行编译。
  5. 在完整保存 JavaScript 运转时行为的基础上,经过引进静态类型界说来提高代码的可维护性,削减或许呈现的 bug。
  6. 永久不会改动 JavaScript 代码的运转时行为,例如数字除以零等于 Infinity。这意味着,假如将代码从 JavaScript 迁移到 TypeScript ,即使 TypeScript 认为代码有类型过错,也能够保证以相同的方式运转。
  7. 对 JavaScript 类型进行了扩展,增加了例如 anyunknownnevervoid
  8. 一旦 TypeScript 的编译器完成了查看代码的作业,它就会 擦除 类型以生成终究的“已编译”代码。这意味着一旦代码被编译,生成的一般 JS 代码便没有类型信息。这也意味着 TypeScript 绝不会依据它揣度的类型更改程序的 行为。最重要的是,虽然您或许会在编译过程中看到类型过错,但类型体系自身与程序如何运转无关。
  9. 在较大型的项目中,能够在单独的文件 tsconfig.json 中声明 TypeScript 编译器的装备,并细化地调整其作业方式、严厉程度、以及将编译后的文件存储在何处。

函数

TypeScript 具有界说函数参数和回来值的特定语法。

  1. 函数回来值的类型能够明确界说。
function getTime(): number {
  return new Date().getTime();
}
let time = getTime(); // let time: number
console.log(time);

假如没有界说回来类型,TypeScript 将测验经过回来的变量或表达式的类型来揣度它。

  1. 类型 void 可用于指示函数不回来任何值。
function printHello(): void {
  console.log('Hello!');
}
  1. 函数参数的类型与变量声明的语法相似。
function multiply(a: number, b: number) {
  return a * b;
}

假如没有界说参数类型,TypeScript 将默许运用 any,除非额定的类型信息可用,如默许参数和类型别号。

  1. 默许情况下,TypeScript 会假定一切参数都是必需的,但它们能够显式标记为可选。
// 这儿的 `?` 运算符将参数 `c` 标记为可选
function add(a: number, b: number, c?: number) {
  return a + b + (c || 0);
}
console.log(add(2,5));
  1. 对于具有默许值的参数,默许值坐落类型注释之后。
function pow(value: number, exponent: number = 10) {
  return value ** exponent;
}

TypeScript 还能够从默许值揣度类型。

function pow(value, exponent = 10) {
  return value ** exponent;
}
console.log(pow(10, '2')); // Argument of type 'string' is not assignable to parameter of type 'number'.
  1. 命名参数遵从与一般参数相同的形式。
function divide({ dividend, divisor }: { dividend: number, divisor: number }) {
  return dividend / divisor;
}
console.log(divide({dividend: 10, divisor: 2}));
  1. 剩下参数能够像一般参数一样类型化,但类型有必要是数组,由于剩下参数始终是数组。
function add(a: number, b: number, ...rest: number[]) {
  return a + b + rest.reduce((p, c) => p + c, 0);
}
console.log(add(10,10,10,10,10));
  1. 函数类型能够与具有类型别号的函数分隔指定。
type Negate = (value: number) => number;
// 参数 value 主动从 Negate 类型被分配 number 类型
const negateFunction: Negate = (value) => value * -1;
console.log(negateFunction(10));
  1. 函数重载是办法姓名相同,而参数不同,回来类型能够相同也能够不同。
  • 函数重载类型化界说了一个函数能够被调用的一切方式,在主动补全时会很有用,能够在主动补全中列出一切或许的重载记录。
  • 函数重载需求界说重载签名(一个以上,界说函数的形参和回来类型,没有函数体,不行调用)和一个完成签名。
  • 除了惯例的函数之外,类中的办法也能够重载。
function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
  if (d !== undefined && y !== undefined) {
    return new Date(y, mOrTimestamp, d);
  } else {
    return new Date(mOrTimestamp);
  }
}
const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 5);
const d3 = makeDate(1, 3); // No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.

在本例中,咱们编写了两个重载:一个承受一个参数,另一个承受三个参数。前两个签名称为重载签名,但它们都不能用两个参数调用。

在下面这个示例中,咱们能够用字符串或数组调用它。可是,咱们不能运用或许是字符串或数组的值调用它,报错:No overload matches this call.,由于 TypeScript 只能将函数调用解析为单个重载:

function len(s: string): number;
function len(arr: any[]): number;
function len(x: any[] | string) {
  return x.length;
}
len(""); // OK
len([0]); // OK
len(Math.random() > 0.5 ? "hello" : [0]); // No overload matches this call.

由于两个重载都有相同的参数计数和相同的回来类型,所以咱们能够编写一个非重载版本的函数:

function len(x: any[] | string) {
  return x.length;
}

现在咱们能够运用恣意一种值调用它,所以假如或许,能够首选具有联合类型的参数,而不是重载函数。

枚举

枚举是一个特别的“类”,表明一组常量(不行更改的变量)。运用枚举类型能够为一组数值赋予愈加友好的姓名。枚举有两种数据类型:stringnumer

  1. 默许情况下,枚举会将第一个值初始化为 0,后边的值依次值加 1
enum CardinalDirections {
  North,
  East,
  South,
  West
};
let currentDirection: CardinalDirections = CardinalDirections.North;
console.log(currentDirection); // '0' 由于 North 是第一个值
// currentDirection = 'North'; // Error: "North" is not assignable to type 'CardinalDirections'.
  1. 能够设置第一个枚举的值的数字,并让它主动递增。
enum CardinalDirections {
  North = 1,
  East,
  South,
  West
}
console.log(CardinalDirections.North); // logs 1
console.log(CardinalDirections.West); // logs 4
  1. 能够为每个枚举值分配仅有的数值,值将不会主动递增。
enum StatusCodes {
  NotFound = 404,
  Success = 200,
  Accepted = 202,
  BadRequest = 400
};
console.log(StatusCodes.NotFound); // logs 404
console.log(StatusCodes.Success); // logs 200
  1. string 类型比 numer 类型枚举更常见,由于它们的可读性和目的性更强。
enum CardinalDirections {
  North = 'North',
  East = "East",
  South = "South",
  West = "West"
};
console.log(CardinalDirections.North); // logs "North"
console.log(CardinalDirections.West); // logs "West"

能够混合字符串和数字枚举值,但不主张这样做。

  1. 能够经过枚举值来获取枚举名称。
enum StatusCodes {
  NotFound = 404,
  Success = 200,
  Accepted = 202,
  BadRequest = 400
};
let s1 = StatusCodes[200]; // string | undefined
console.log(s1); // Success
  1. 假如某个特点的值是计算出来的,它后边的第一位成员有必要初始化。
const value = 0;
enum List {
  A = value,
  B = 2,  // 有必要初始化
  C,
}
  1. 为了防止在额定生成的代码上的开销和额定的非直接的对枚举成员的拜访,咱们能够运用 const 枚举,常量枚举不答应包含计算成员。不同于惯例的枚举,它们在编译阶段会被删除。
const a = 1;
const enum Enum {
  A = 1,
  B = A * 2,
  C = a * 2, // const enum member initializers can only contain literal values and other computed enum values.
}
  1. 运用 keyof typeof 来获取将一切 Enum 键表明为字符串的 Type。
enum LogLevel {
  ERROR,
  WARN,
  INFO,
  DEBUG,
}
// 相当于 type LogLevelStrings = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
type LogLevelStrings = keyof typeof LogLevel;

联合类型

联合类型(Union Types)能够经过 | 运算符将变量设置多种类型,赋值时能够依据设置的类型来赋值。当一个值能够是多个单一类型时,能够运用联合类型。例如当变量是 stringnumber 时。

function printStatusCode(code: string | number) {
  console.log(`My status code is ${code}.`)
}
printStatusCode(404);
printStatusCode('404');

注意:运用联合类型时,需求知道你的类型是什么,以防止类型过错:

function printStatusCode(code: string | number) {
  console.log(`My status code is ${code.toUpperCase()}.`); // error: Property 'toUpperCase' does not exist on type 'string | number'. Property 'toUpperCase' does not exist on type 'number'
}

在上述示例中,由于 toUpperCase() 是一个字符串办法,而数字无法拜访它。

类型别号和接口

TypeScript 答应类型与运用它们的变量分隔界说。类型别号和接口答应在不同的变量之间轻松同享类型。

类型别号

  1. 类型别号就像咱们运用了匿名目标类型一样。
type Point = {
  x: number;
  y: number;
};
function printCoord(pt: Point) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 100, y: 100 });
  1. 能够运用类型别号为任何类型命名,而不仅仅是目标类型。例如,类型别号能够命名联合类型:
type ID = number | string;
  1. 类型别号能够界说指定区间详细的数值,该类型只能取界说的区间内的数值。
type Direction = 'center' | 'left' | 'right';
let d: Direction = ''; // Type '""' is not assignable to type 'Direction'.
  1. 类型别号能够指定模板字符串类型规矩。
type BooleanString = `${boolean}`;
const bool: BooleanString = '1'; // Type '"1"' is not assignable to type '"false" | "true"'.
type SerialNumber= `${number}.${number}`;
const id: SerialNumber= '1.2';

接口

接口相似于类型别号,可是只适用于目标类型

  1. 就像上面运用类型别号一样,TypeScript 只关心咱们传递给 printCoord 的值的结构——它是否具有预期的特点。只关心类型的结构和功能,这便是咱们将 TypeScript 称为结构类型体系的原因。
interface Point {
  x: number;
  y: number;
}
function printCoord(pt: Point) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 100, y: 100 });
  1. 接口的简直一切功能都能够在类型别号中运用,关键区别在于类型别号不能重新界说以增加新特点,而接口始终是可扩展的。
type Window = {
  title: string
}
// Error: Duplicate identifier 'Window'.
type Window = {
  ts: TypeScriptAPI
}
interface Window {
  title: string
}
interface Window {
  ts: TypeScriptAPI
}
  1. 接口经过 extends 关键字能够承继另一个接口、类、类型别号来扩展成员,支撑多承继,在 extends 关键字之后用逗号分隔。
interface Show {
  isShow: boolean;
}
type Graphic = {
  name: string;
}
class Point {
  x: number;
  y: number;
}
interface Point3d extends Point, Graphic, Show {
  z: number;
}
const point3d: Point3d = { x: 1, y: 2, z: 3, name: '1', isShow: true };
  1. 与运用接口描述函数类型差不多,咱们也能够描述那些能够“经过索引得到”的类型。可索引类型具有一个 索引签名,它描述了目标索引的类型,还有相应的索引回来值类型。
interface i1 {
  [index: number]: string
}
let list: i1 = ["0", "1", "2"];
// list2 = ["0", 1, "2"] // Type 'number' is not assignable to type 'string'.
interface i2 {
  [index: string]: number
}
const list2: i2 = {};
list2["0"] = 0;
list2[1] = "1"; // Type 'string' is not assignable to type 'number'.

TypeScript 支撑两种索引签名:字符串和数字。能够一起运用两种类型的索引,可是数字索引的回来值有必要是字符串索引回来值类型的子类型。这是由于当运用number 来索引时,JavaScript 会将它转换成 string 然后再去索引目标。也便是说用100(一个 number)去索引等同于运用 "100"(一个 string)去索引,因而两者需求保持一致。字符串索引签名能够很好的描述 dictionary 形式,而且它们也会保证一切特点与其回来值类型相匹配。

class Animal {
  name: string;
}
class Dog extends Animal {
  breed: string;
}
// 过错:运用数值型的字符串索引,有时会得到彻底不同的Animal!
interface NotOkay {
  [x: number]: Animal; // 'number' index type 'Animal' is not assignable to 'string' index type 'Dog'.
  [x: string]: Dog;
}
interface NumberDictionary {
  [index: string]: number;
  length: number;    // 能够,length是number类型
  name: string       // 过错,name的类型与索引类型回来值的类型不匹配
}
  1. 除了描述带有特点的一般目标外,接口也能够描述函数类型。
  • 运用接口表明函数类型,需求给接口界说一个调用签名,它就像是一个只有参数列表和回来值类型的函数界说。
  • 参数列表里的每个参数都需求姓名和类型,对于函数类型的类型查看来说,函数的参数名不需求与接口里界说的姓名相匹配。
  • 函数的参数会逐个进行查看,要求对应位置上的参数类型是兼容的。
  • 假如不指定类型,类型体系会揣度出参数类型。
interface SearchFunc {
  (source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
  let result = source.search(subString);
  return result > -1;
}
mySearch = function(src: string, sub: string): boolean {
  let result = src.search(sub);
  return result > -1;
}
mySearch = function(src, sub) {
  let result = src.search(sub);
  return result > -1;
}

穿插类型

接口答应咱们经过扩展其他类型来构建新类型。TypeScript 还提供了另一种称为穿插类型的结构,运用 & 运算符界说,主要用于组合现有的目标类型。

  1. 穿插类型包含了所需的一切类型的特性。
interface Colorful {
  color: string;
}
interface Circle {
  radius: number;
}
function draw(circle: Colorful & Circle) {
  console.log(`Color was ${circle.color}`);
  console.log(`Radius was ${circle.radius}`);
}
draw({ color: "blue", radius: 42 });
// 'raidus' does not exist in type 'Colorful & Circle'. Did you mean to write 'radius'?
draw({ color: "red", raidus: 42 });

在这儿,咱们将 ColorfulCircle 相交以生成一个包含 ColorfulCircle 的一切成员的新类型。

  1. 能够将多个接口类型合并成一个类型,完成等同于接口承继的效果。
interface A {
  name: string;
  age: number;
}
interface B {
  name: string;
  height: string;
}
type Person = A & B;  // 相当于求并集
const person: Person = { name: 'Tom', age: 18, height: '60kg' };
  1. 类型别号也能够与接口穿插。
interface Animal {
  name: string
}
type Person = Animal & {
  age: number;
}
  1. 类型别号能够经过穿插类型完成接口的承继行为。
type Animal = {
  name: string
}
type Bear = Animal & { 
  honey: boolean 
}
  1. 原始类型之间穿插类型为 never,由于任何类型都不能满足一起归于多种原始类型。
type Useless = string & number; // type Useless: never
Useless = 1; // 'Useless' only refers to a type, but is being used as a value here.

TypeScript 向 JavaScript 类增加了类型和可见性修饰符。

  1. 类的成员(特点和办法)运用类型注释(相似于变量)进行类型化。
class Person {
  name: string;
}
const person = new Person();
person.name = "Jane";
  1. 类成员也能够被赋予影响可见性的特别修饰符。TypeScript 中有三个主要的可见性修饰符:
  • public -(默许)答应从任何地方拜访类成员
  • private – 只答应从类内部拜访类成员
  • protected – 答应从自身和承继它的任何类拜访类成员
class Person {
  private name: string;
  constructor(name: string) {
    this.name = name;
  }
  getName(): string {
    return this.name;
  }
}
const person = new Person("Jane");
console.log(person.getName()); // person.name isn't accessible from outside the class since it's private
  1. TypeScript 经过向参数增加可见性修饰符,能够在结构函数中界说类成员。
class Person {
  constructor(private name: string) {}
  getName(): string {
    return this.name;
  }
}
const person = new Person("Jane");
console.log(person.getName()); // Jane
  1. 与数组相似,readonly 关键字能够防止类成员被更改,只读特点有必要在声明时或结构函数里被初始化,readonly 关键字也能够在结构函数中界说类成员。
class Person {
  readonly name: string = 'Jane';
  constructor(name?: string) {
    if(name) this.name = name;
  }
}
const person = new Person("a");
// person.name = ''; // Cannot assign to 'name' because it is a read-only property.
  1. 类经过 extends 关键字承继另一个类,一个类只能承继一个类;经过 implements 关键字完成接口(接口描述了类的公共部分,而不是公共和私有两部分),一个类支撑完成多个接口,在 implements 关键字之后用逗号分隔。
interface Shape {
  getArea: () => number;
}
class Rectangle implements Shape {
  constructor(protected readonly width: number, protected readonly height: number) {}
  getArea(): number {
    return this.width * this.height;
  }
}
class Square extends Rectangle {
  constructor(width: number) {
    super(width, width);
  }
}
  1. 当一个类扩展另一个类时,它能够用相同的名称重写父类的成员。较新版本的 TypeScript 答应运用 override 关键字显式标记,它能够帮助防止意外重写不存在的办法。运用设置 noImplicitOverride 能够强制在重写时运用它。
class Rectangle {
  constructor(protected readonly width: number, protected readonly height: number) {}
  toString(): string {
    return `Rectangle[width=${this.width}, height=${this.height}]`;
  }
}
class Square extends Rectangle {
  constructor(width: number) {
    super(width, width);
  }
  override toString(): string {
    return `Square[width=${this.width}]`;
  }
}
  1. 抽象类答应它们用作其他类的基类,而无需完成其一切成员。经过运用 abstract 关键字界说抽象类,未完成的成员也需求运用 abstract 关键字标识。抽象类不能直接实例化,由于它们没有完成其一切成员。
abstract class Polygon {
  abstract getArea(): number;
  toString(): string {
    return `Polygon[area=${this.getArea()}]`;
  }
}
class Rectangle extends Polygon {
  constructor(protected readonly width: number, protected readonly height: number) {
    super();
  }
  getArea(): number {
    return this.width * this.height;
  }
}
  1. TypeScript 支撑经过 getters/setters 来截取对目标成员的拜访,有效地控制对目标成员的拜访。只带有 get 不带有 set 的存取器主动被揣度为 readonly。
class Employee {
  login: boolean;
  private _fullName: string;
  get fullName(): string {
    return this._fullName;
  }
  set fullName(newName: string) {
    console.log(this.login);
    if (this.login === true) {
      this._fullName = newName;
    } else {
      console.log("Error: Unauthorized update of employee!");
    }
  }
}
const employee = new Employee();
employee.login = true;
employee.fullName = "Bob Smith";
if (employee.fullName) {
  console.log(employee.fullName);
}
  1. 静态成员存在于类本身上面而不是类的实例上。
class StaticMem {
  static num: number;
  static disp(): void {
    console.log("num 值为 " + StaticMem.num);
  }
}
StaticMem.num = 12;
StaticMem.disp();