TypeScript 高级数据类型 | 青训营笔记

这是我参与「第五届青训营 」伴学笔记创作活动的第 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));
复制代码

枚举

枚举是一个特殊的“类”,表示一组常量(不可更改的变量)。使用枚举类型可以为一组数值赋予更加友好的名字。枚举有两种数据类型: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,
}
复制代码

联合类型

联合类型(Union Types)可以通过 | 运算符将变量设置多种类型,赋值时可以根据设置的类型来赋值。当一个值可以是多个单一类型时,可以使用联合类型。例如当变量是 string 或 number 时。

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 Rectangle {
  height: number,
  width: number
}
interface ColoredRectangle extends Rectangle {
  color: string
}
const coloredRectangle: ColoredRectangle = {
  height: 20,
  width: 10,
  color: "red"
};
复制代码
  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 还提供了另一种称为交叉类型的结构,使用 & 运算符定义,主要用于组合现有的对象类型。

  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 关键字可以防止类成员被更改。
class Person {
  readonly name: string = 'Jane'; // name不能在其声明处或构造函数中初始化之后更改
  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;
  }
}
复制代码

猜你喜欢

转载自juejin.im/post/7193932637160308797