原始类型

/**
 * 原始数据类型
 */

const a: string = "a";

const b: number = 123; // NaN, Infinity

const c: boolean = true; // false

// 严格模式下,不能是null
const d: string = null;

// 也有单独check null的选项:strictNullChecks

const e: void = undefined; // 严格模式下只能是undefined,非严格模式下可以是null

const f: null = null;

const g: undefined = undefined;

const h: Symbol = Symbol();

标准库声明

比如我在tsconfig文件里的target设置为es2015,直接我声明的Symbol类型的变量就会直接报错:

想解决就很简单,把target修改为es2105就好,但是为什么?
实际上就是因为这是JavaScript内置对象,Symbol是es6中新增的,所以内置对象是找不到的。

实际上任何一个在es6中新增的对象,我们直接使用都会遇到这种问题,比如Promise

另外还有一种办法那就是:在lib数组中添加es2015,另外如果如此添加lib中,console就又会报错了:

这是因为lib只设置了es2015,默认的标准库都被覆盖了,我们需要添加回来默认的标准库,需要注意的是在Typescript中把DOM和BOM都合并到DOM标准库中了,所以我们只需要添加DOM就行:

这样一来就搞定了报错问题。
所谓标准库实际上就是内置对象声明的所对应的声明文件

在代码中使用内置对象必须要使用对应的标准库,否则Typescript找不到对应的内置类型,就会报错。

中文错误信息

报错的消息显示中文会对国内开发者比较友好,如何使用?

yarn tsc --locale zh-CN

如此这般:

vscode切换中英文也是在settings里搜索locale

切换这里的选项即可,但是不推荐这里这样做,因为很多错误提示我们一般通过chrome在stackoverflow或者其他类似的平台上可以直接寻求帮助,用英文版更加方便

Object 类型

export default {};

// 不能接受原始值
// object类型不单指普通对象
const foo: object = () => {}; //{} //[]

// 我们需要普通对象类型,就用类似对象字面量的语法:不能多不能少
const obj: { foo: number; func: () => void } = { foo: 1, func: () => {} };

// 限制对象的内容应该用Typescript更专业的接口功能。

数组类型

const arr1: Array<number> = [1, 2, 3];

const arr2: Array<string> = ["1", "2", "3"];

function sum(...args: number[]) {
  return args.reduce((prev, value) => {
    return prev + value;
  }, 0);
}

console.log(sum(...arr1));

export default {};

元祖类型

明确元素数量和类型的数组,类型不必要完全相同,在Typescript中可以使用类似数组字面量的语法去定义元祖类型

const tuple: [number, string, string] = [1, "string", "s"];

// 访问方式1:跟调用数组一样差不多
const age = tuple[0];

const str = tuple[1];

const [ageBase, strBase] = tuple;

// 方式2:解构

console.log(ageBase, strBase, age, str);

// es2017的方法Object.entries的返回值也是一个元祖
Object.entries({ foo: 123, bar: "s" });

枚举类型

export default {};

// 枚举
// enum StatusNumber {
//   ZERO = 0,
//   One = 1,
//   TWO = 2,
// }

// 枚举也可以不用等号指定值
// 不指定默认就是从0开始累加,如果指定了一个成员的值
// 后面的值都会逐渐累加

enum StatusNumber {
  ZERO,
  One,
  TWO,
}

// enum StatusNumber {
//   ZERO = 1,
//   One, //2
//   TWO, //3
// }

// 如果枚举值是字符串的话,成员每一个都需要指定具体的字符串的值
// enum StatusNumber {
//   ZERO = "one",
//   One = "two",
//   TWO = "three",
// }

const post = {
  title: "title",
  content: "Typescript",
  status: StatusNumber.ZERO,
};

枚举类型可能会入侵到我们运行中的代码:会影响我们编译之后的结果。

我们的大多数Typescript中定义的类型会在我们编译转换之后被移除掉,只是为了我们在编译过程中做类型检查

这段代码无外乎就是把枚举的名称作为对象的键来存储枚举的值,再把值作为键来存储枚举的键

目的就是可以动态地通过枚举值,也就是0,1,2这样的值来获取枚举的名称:

StatusNumber[0]; // ZERO

如果我们代码中确认没有使用索引器的方式来访问枚举,那我们建议使用常量枚举,在enum前加一个const,就可以看到在编译之后的枚举会被移除掉,使用到枚举的地方就会被替换为具体的数值。

常量枚举适用于那些在编译时被内联的情况,可以提高性能,而普通枚举则提供了更丰富的功能,包括反向映射等

函数类型

export default {};

function func1(a: number, b: number): string {
  return "func1";
}

func1(1, 2);

// 形参和实参必须保持完全一致
// 如果需要可选:
function func2(a: number, b?: number): string {
  return "func2";
}

func2(1);

// 或者使用ES6的默认参数:
function func3(a: number, b: number = 10): string {
  return "func3";
}

func3(1);

//可选参数必须要出现在必选参数的最后
// 如果我们要接受任意多个参数,我们可以使用ES6的rest操作符:
function func4(a: number, b: number, ...rest: number[]) {
  return "func4Rest";
}

func4(1, 2, 3, 4, 4, 5, 5, 5, 55);
const functionArrow: (a: number, b: number) => string = function (
  a: number,
  b: number
): string {
  return "";
};

any类型

export default {};

function stringify(value: any) {
  return JSON.stringify(value);
}

// any类型依然属于动态类型,Typescript不会对any做类型检查

// any不会有类型检查所以会有类型安全的问题,尽量不要使用

隐式类型推断

如果我们没有明确通过类型注解来标记变量类型,Typescript就会通过自己的推断来标记变量的类型

export default {};

// number
let age = 19;

// any
let foo;

类型断言

在有些特殊的情况下,Typescript无法自行推断类型的,但是在开发者的角度下,我们是可以额明确的知道变量的类型的。这种情况下我们就需要使用类型断言。比如:

export default {};
const nums = [11, 12, 13, 14, 10, 9, 8];

const res = nums.find((i) => i > 0);

// const resSuqare = res * res;

// 断言方式1:
const num = res as number; //推荐

// 断言方式2:
const num2 = <number>res; //jsx语法下不可以使用

我们知道这个变量nums里一定有一个大于0的数字,但是Typescript依旧会报错,这个时候我们就可以使用断言了↑

接口

export default {};

interface Post {
  title: string;
  content: string;
}

function printPost(post: Post) {
  console.log(post.title);
  console.log(post.content);
}

printPost({ title: "Typescript", content: "Interface" });

总结接口:接口就是用来约束对象的结构,一个对象实现一个接口,他就必须要拥有接口里的所有成员。

Typescript中的接口只是为我们有结构的数据做类型约束,在实际运行的阶段,接口其实并没有意义。

可选成员

export default {};

interface Post {
  title: string;
  content: string;
  // 可选,加一个?即可,在实际调用时可以加上也可以不加上
  should?: boolean;
}

const printPost = (post: Post) => {
  console.log(post.title);
  console.log(post.content);
};

printPost({ title: "Typescript", content: "Interface" });
printPost({ title: "Typescript", content: "Interface", should: true });

readonly

不允许外界设置的话就加上readonly

export default {};

interface Post {
  title: string;
  content: string;
  // 可选,加一个?即可,在实际调用时可以加上也可以不加上
  should?: boolean;
  // readonly在初始化完成之后再次修改的话就会报错
  readonly summary: string;
}

const printPost = (post: Post) => {
  console.log(post.title);
  console.log(post.content);
};

printPost({ title: "Typescript", content: "Interface", summary: "sss" });
printPost({
  title: "Typescript",
  content: "Interface",
  should: true,
  summary: "sss",
});

不可修改

动态成员

interface Cache {
  [prop: string]: string;
}

const cache: Cache = {};

cache.foo = '111'
cache.bar = '222'
cache.zoom = '333'

cache可以自由的添加成员,但是必须要是string类型。

描述一类具体事物的抽象特征

ES6以前,函数+原型来模拟实现类

ES6之后,有一个专门的class

在Typescript中增强了class的相关语法

export default {};

class Person {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  sayHi = (msg: string): void => {
    console.log(`${msg}`);
  };
}

访问修饰符

  • private 私有属性
    添加了private之后,这种属性只能在类的内部访问
  • public 公有成员
    Typescript中是默认的public,加不加也没有所谓
  • protected 受保护成员
    使用 protected 访问修饰符表示成员只能在类内部和继承的子类中访问,外部无法访问

外部也不能访问,和private的区别在哪儿?

class Student extends Person {
  constructor(name: string, age: number) {
    // 调用父类的构造函数
    super(name, age);
    // 可以访问受到保护的属性sex
    console.log(this.sex);
  }
}

age不行,因为是私有属性

这些访问修饰符的作用就是控制类的成员的可访问级别

另外:我们如果把构造函数给private,那么就不能在外面被实例化,也不能够被继承,解决办法就是在class内部添加一个静态方法:

class Person {
  name: string;
  // 私有属性
  private age: number;
  protected sex: string;
  private constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
    this.sex = "male";
  }

  sayHi = (msg: string): void => {
    console.log(`${msg}`);
  };

  // 新建一个静态方法在内部使用构造函数,在外部调用就可以了
  static create(name: string, age: number) {
    return new Person(name, age);
  }
}

const ming = Person.create("ming", 12);

protected这样在外部也是无法实例化的,唯一区别在于可以继承,不再赘述了。

类的只读属性

也是一样,加readonly,但是如果有访问修饰符的话,要加在访问修饰符之后,比如:

protected readonly sex: string

在初始化之后,我们的sex属性就无法再修改了,不管是在内部还是外部。

类与接口

相比于类,接口的概念更加抽象一点

就比方说:手机和座机,都能打电话,但是手机还可以调用摄像头拍视频,座机不能,这其中,打电话作为一个公共的特征,可以抽象为一个接口

export default {};

interface EatAndRun {
  eat(food: string): void;
  run(distance: string): void;
}

class Person implements EatAndRun {
  eat(food: string): void {
    console.log("用餐具吃:", food);
  }

  run(distance: string): void {
    console.log("跑了:", distance);
  }
}

class Animal implements EatAndRun {
  eat(food: string): void {
    console.log("原始的吃:", food);
  }

  run(distance: string): void {
    console.log("爬行了:", distance);
  }
}

实现接口使用interface,implements实现一下EatAndRun接口,那么在这个类型中,必须要有这个类型的成员,没有就会报错

但是在C#和Java这种语言中,建议我们尽可能让每个接口的定义更加简单和细化:比如eatAndRun抽象了两个能力,吃和跑,但是这两个并不一定都同时存在。

更为合理的是:一个接口只会约束一个能力,同时让一个类型同时实现多个接口,这样会更加的合理:

export default {};

interface Eat {
  eat(food: string): void;
}

interface Run {
  run(distance: string): void;
}

class Person implements Eat, Run {
  eat(food: string): void {
    console.log("用餐具吃:", food);
  }

  run(distance: string): void {
    console.log("跑了:", distance);
  }
}

class Animal implements Eat, Run {
  eat(food: string): void {
    console.log("原始的吃:", food);
  }

  run(distance: string): void {
    console.log("爬行了:", distance);
  }
}

抽象类

抽象类在某种程度上和接口有点类似,不同于接口的是,抽象类可以包含一些具体的实现。

// 定义抽象类
abstract class AbstractClass {
  // 定义抽象方法,子类需要实现这个方法
  abstract abstractMethod(): void;

  // 普通方法
  regularMethod() {
    console.log("This is a regular method");
  }
}

// 继承抽象类的子类
class ConcreteClass extends AbstractClass {
  // 实现抽象方法
  abstractMethod() {
    console.log("Abstract method implemented in ConcreteClass");
  }

  // 子类可以覆盖普通方法
  regularMethod() {
    console.log("This is an overridden regular method");
  }
}

// 实例化子类
const instance = new ConcreteClass();

// 调用抽象方法
instance.abstractMethod(); // 输出: Abstract method implemented in ConcreteClass

// 调用普通方法
instance.regularMethod();  // 输出: This is an overridden regular method

泛型

泛型就是把定义时不能明确的类型变为一个参数,在使用的时候再传递这样一个类型参数:

//  泛型
export default {};

function createNumberArray(length: number, value: number): number[] {
  const arr = Array<number>(length).fill(value);
  return arr;
}

const res = createNumberArray(3, 100);

function createArray<T>(length: number, value: T): T[] {
  const arr = Array<T>(length).fill(value);
  return arr;
}

const resNumber = createArray<number>(3, 100);

const resString = createArray<string>(3, "1");

类型声明

比如我们用到第三方的npm包:

就会报错,关于类型错误的,但是这里只是举一个例子,因为lodash是支持Typescript的类型的,有些npm包如果是没有支持的话呢,就可以用到类型声明的declare了:


// 类型声明
import { camelCase } from "lodash";

declare function camelCase(input: string): string;

camelCase("string hello");

当然了,如果npm包是支持Typescript的话,你安装对应的@types/xxx就可以了,在node_modules里可以看到他们:

xxx.d.ts这种后缀的文件基本就是Typescript中的类型的声明文件。

最后修改:2024 年 03 月 21 日
收款不要了,给孩子补充点点赞数吧