TypeScript笔记

TypeScript介绍

TypeScript是什么

TypeScript(简称TS)是JavaScript的超集

TypeScript = Type + JavaScript(在JS的基础之上,为JS添加了类型支持)

TypeScript是微软开发的开源编程语言,可以在任何运行JavaScript的地方运行

TypeScript为什么要为JS添加类型支持

背景:JS的类型系统存在"先天缺陷",JS代码中绝大部分错误都是类型错误,从而增加了找bug和改bug的时间,严重影响开发效率

从编程语言的动静来区分,TypeScript属于静态类型的编程语言,JS属于动态类型的编程语言

静态类型:编译期做类型检查、动态类型:执行期做类型检查

对于JS来说:需要等到代码执行的时候才能发现错误

对于TS来说:在代码编译时就可以发现错误

TypeScript相比于JS的优势

  • 更早发现错误,减少找bug,改bug的时间,提升开发效率
  • 程序中任何位置的代码都有代码提示,随时随地的安全感,增强了开发体验
  • 强大的类型系统提升了代码的可维护性,使得重构代码更加容易
  • 支持最新的ES语法,优先体验最新的语法,让你走在技术最前沿
  • TS类型推断机制,不需要在代码中的每个地方都显示标注类型,让你在享受优势的同时尽量降低了成本

TypeScript初体验

安装编译TS的工具包

问题:为什么要安装编译TS的工具包

回答:nodejs/浏览器只认识JS代码,不认识TS代码,需要先将TS代码转换成JS代码,然后才能运行

安装命令:npm i -g typescript

typescript包:用来编译TS代码的包,提供了tsc命令,实现了TS转换为JS代码

验证是否安装成功:tsc -v(查看typescript版本)

编译并运行TS代码

  1. 创建hello.ts文件

  2. 将TS编译为JS:在终端中输入命令tsc hello.ts(此时,在同级目录中会出现一个同名的JS文件)

  3. 在终端输入node hello.js

// 与JS代码一样
console.log("hello ts");
// 类型系统
let age: number = 18;
// 立即执行函数
const str = (() => {
  function sayHi(str: string): string {
    return "你好" + str;
  }
  const hi = sayHi("小米");
  return hi;
})();
console.log(str);

简化运行TS代码的步骤

问题描述:每次修改代码后都要在执行两个命令,才能运行TS代码,太麻烦

  1. 安装ts-node包:npm i -g ts-node
  2. 执行命令ts-node hello.ts

TypeScript常用类型

类型注解

示例代码

let age: number = 18;

说明:代码中的 :number 就是类型注解

作用:为变量添加类型约束,比如,上述代码中,约定变量age的类型为number

解释:约定了什么类型,就只能给变量赋值该类型的值,否则会报错

常用基础类型概述

  1. JS已有类型
    • 原始类型:number/string/boolean/null/undefined/symbol
    • 对象类型:Object(数组、对象、函数等)
  2. TS新增类型
    • 联合类型、自定义类型、接口、元组、字面量类型、枚举、void、any等

原始类型就不在介绍了JS中我们已经详细学过了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lZ5qm6pB-1681822203275)(image/image-20230403171110752.png)]

数组类型

数组类型的两种写法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-32fxQm7z-1681822203276)(image/image-20230403203323390.png)]

类型别名

类型别名:为任意类型起别名

使用场景:当同一类型复杂且被多次使用,可以通过类型别名简化该类型的使用

  • 使用type关键字来创建类型别名
  • 类型别名可以是任意合法的变量名称
  • 创建类型别名后,直接使用该类型别名作为变量的类型注解即可

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EMafsCY2-1681822203277)(image/image-20230403210944716.png)]

函数类型

函数的类型实际上指的是函数参数和返回值的类型

为函数指定类型的两种方式:

  1. 单独指定参数、返回值的类型
  2. 同时指定参数、返回值的类型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QnH5pymZ-1681822203277)(image/image-20230404151517751.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3020UtGm-1681822203278)(image/image-20230404151619445.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s2kO7Izp-1681822203278)(image/image-20230404152404338.png)]

如果函数没有返回值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-waTxR34N-1681822203279)(image/image-20230404172629728.png)]

使用函数实现某个功能时,参数可以传也可以不传,这种情况下,再给函数参数指定类型时,就用到可选参数了

可选参数只能出现在参数列表的最后

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yPgPhLv4-1681822203280)(image/image-20230404184117514.png)]

对象类型

JS中的对象是由属性和方法构成的,而TS中对象的类型就是在描述对象的结构,对象类型的写法如下:

let person: { name: string; age: number; sayHi(): void } = {
  name: "jack",
  age: 19,
  sayHi() {},
};
  • 直接使用{}来描述对象结构,属性采用属性名: 类型的形式,方法采用方法名(): 返回值类型的形式
  • 如果方法有参数,就在方法名后面的小括号中指定参数类型
  • 在一行代码中指定对象的多个属性类型时,使用分号来分割
  • 如果一行代码只指定一个属性类型,可以去掉分号
  • 方法的类型也可以使用箭头函数形式

对象的属性和方法也可以是可选的,此时,就用的可选属性了

function myAxios(config: { url: string; method?: string }) {
  console.log(config);
}
myAxios({ url: "" });

接口

当一个对象类型被多次使用时,一般会使用接口来描述对象的类型,达到复用的目的

  • 使用interface关键字来声明接口
  • 接口名称可以是任意合法的变量名称
  • 声明接口后,直接使用接口名称作为变量的类型
  • 因为每一行只有一个属性类型,因此,属性类型后没有分号隔开

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T1JgM5uL-1681822203280)(image/image-20230405110115964.png)]

接口与类型别名对比

  • 相同点:都可以给对象指定类型
  • 不同点:
    • 接口:只能为对象指定类型
    • 类型别名:可以为任意类型指定别名
// 都可以为对象指定类型
interface IPerson {
  name: string;
  age: number;
  sayHi(): void;
}
// 但是类型别名可以为任意类型指定别名
// 而接口只能指定对象类型
type IPersonObject = {
  name: string;
  age: number;
  sayHi(): void;
};
type NumStr = number | string;

接口继承

如果两个接口之间有相同的属性和方法,可以将公共的属性和方法抽离出来,通过继承来实现复用

interface Point2D {
  x: number;
  y: number;
}
interface Point3D extends Point2D {
  z: number;
}
let p3: Point3D = {
  x: 1,
  y: 0,
  z: 11,
};

元组

场景:在地图中,使用经纬度坐标来标记位置信息

可以使用数组来记录坐标,那么,该数组中只有两个元素,并且这两个元素都是数值类型

let position: number[] = [11.11, 98.23]

使用number[]的缺点:不严谨,因为该类型的数组中可以出现任意多个数字

使用元组可以更好的规范类型

元组类型:它确切的知道包含多少个元素,以及特点索引对应的类型

let position: [number, number] = [11.11, 98.23];

类型推论

在TS中,某些没有明确指定类型的地方,TS的类型推论机制会帮助提供类型

发生类型推论的两种常见场景:声明变量并初始化时、绝对函数返回值时

let age = 18; // 可以推论出变量age的类型
function add(num1: number, num2: number) {
  return num1 + num2;
} // 可以推论函数返回值

类型断言

有时候你会比TS更加明确一个值的类型,此时,可以使用类型断言来指定更具体的类型

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>类型断言</title>
  </head>
  <body>
    <a href="http://www.itcast.cn" id="link">传智教育</a>
  </body>
</html>
let aLink = document.getElementById("link")

注意:getElementById方法返回值的类型是HTMLElement,该类型只包含所有标签公共的属性或方法,不包含a标签特有的href等属性

因此,这个类型不具体,无法操作href等a标签特有的属性或方法

解决方式:这种情况下就需要使用类型断言指定更加具体的类型

使用类型断言:

let aLink = document.getElementById("link") as HTMLAnchorElement;
aLink.href;

字面量类型

思考以下代码,两个变量的类型分别是什么

let str1 = "hello TS";
const str2 = "hello TS";

通过TS类型推论机制,可以得到答案

变量str1的类型是string

变量str2的类型为"hello TS"

解释:

  • str1是一个变量,它的值可以是任意字符串,所以类型是string
  • str2是一个常量,它的值不能变化,只能是"hello TS",所以,它的类型是"hello TS"

此处的"hello TS"就是一个字面量类型,某个特定的字符串也可以作为TS中的类型

除开字符串外,任意的JS字面量都可以作为类型使用

使用模式:字面量类型配合联合类型一起使用

使用场景:用来表示一组明确的可选值列表

function changeDirection(direction: 'up' | 'down' | 'left' | 'right') {
    console.log(direction)
}

解释参数direction只能是’up’、‘down’、‘left’、'right’中的任意一个

优势:相比于string类型,使用字面量类型更加明确严谨

枚举

枚举的功能类似于字面量类型+联合类型组合的功能,也可以表示一组明确的可选值

枚举:定义一组命名常量,它描述一个值,该值可以是这些命名常量中的一个

enum Direction {
  Up,
  Down,
  Left,
  Right,
}
function changeDirection(direction: Direction) {
  console.log(direction);
}
changeDirection(Direction.Left);

解释

  1. 使用enum关键字定义枚举
  2. 约定枚举名称,枚举中的值以大写字母开头
  3. 枚举中的多个值以逗号分割
  4. 定义好枚举后,直接使用枚举名称作为类型注解

问题:我们把枚举成员作为了函数的实参,它的值是什么呢?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rtUIkXcM-1681822203281)(image/image-20230406105206390.png)]

解释:通过将鼠标移入Direction.Left,可以看到枚举成员Left的值为2

枚举成员是有值的,默认为,从零开始自增的数值

我们把枚举成员的值为数字的枚举,称为数字枚举

当然也可以给枚举中的成员初始化值

// enum Direction {
//   Up = 10,
//   Down,
//   Left,
//   Right,
// }
enum Direction {
  Up = 2,
  Down = 4,
  Left = 8,
  Right = 16,
}
function changeDirection(direction: Direction) {
  console.log(direction);
}
changeDirection(Direction.Left);

字符串枚举

枚举成员的值是字符串

enum Direction {
  Up = "UP",
  Down = "DOWN",
  Left = "LEFT",
  Right = "RIGHT",
}

注意:字符串枚举没有自增长行为,因此,字符串枚举的每个成员必须有初始值

枚举是TS为数不多的非JavaScript类型级扩展的特性之一

因为其他类型仅仅被当做类型,而枚举不仅被用作类型,还提供值

也就是说,其他类型会在编译为JS代码时自动移除,但是枚举类型会被编译为JS代码

var Direction;
(function (Direction) {
    Direction["Up"] = "UP";
    Direction["Down"] = "DOWN";
    Direction["Left"] = "LEFT";
    Direction["Right"] = "RIGHT";
})(Direction || (Direction = {}));
function changeDirection(direction) {
    console.log(direction);
}
changeDirection(Direction.Left);

说明:枚举与前面讲到的字面量类型+联合类型组合的功能类似,都用来表示一组明确的可选值列表

一般情况下,推荐使用字面量类型+联合类型组合的方式,因为相比枚举,这种方式更加简洁,直观,高效

any类型

原则:不要使用any!!!

这会让TypeScript变为AnyScript(失去TS类型保护的优势)

因为当值的类型为any时,可以对该值进行任意操作,并且不会有代码提示

let obj: any = { x: 0 };
obj.bar = 100;
obj();
const n: number = obj;

解释:以上操作都不会有任何类型错误提示,即使可能存在错误

尽可能的避免使用any类型,除非临时使用any来避免书写很长、很复杂的类型

其它隐式具有any类型的情况:1. 声明变量不提供类型也不提供默认值 2. 函数参数不加类型

因为不推荐使用any类型,所以这两种情况都应该提供类型

typeof

众所周知,JS当中提供了typeof操作符,用来在JS中获取数据的类型

console.log(typeof "hello world");

实际上TS也提供了typeof操作符,可以在类型上下文中引用变量或属性的类型

使用场景:根据已有变量的值,获取该值的类型,来简化类型书写

let p = { x: 1, y: 2 };
// function formatPoint(point: { x: number; y: number }) {}
function formatPoint(point: typeof p) {}
formatPoint(p);

typeof只能用来查询变量或属性的类型,无法查询其他形式的类型

TypeScript高级类型

class类

TypeScript全面支持ES2015中引入的class关键字,并为其添加了类型注解和其他语法

class基本使用如下:

class Person {}
const p = new Person();

根据TS中的类型推论,可以知道Person类的实例对象p的类型是Person

TS中的class不仅提供了class的语法功能,也作为一种类型存在

实例属性初始化

class Person {
  age: number;
  gender = "男";
}

构造函数

class Person {
  age: number;
  gender: string;

  constructor(age: number, gender: string) {
    this.age = age;
    this.gender = gender;
  }
}
const p: Person = new Person(11, "男");

实例方法

class Person {
  x = 1;
  y = 2;
  scale(n: number) {
    this.x *= n;
    this.y *= n;
  }
}
const p = new Person();
p.scale(2);
console.log(p.x);
console.log(p.y);

类的继承

class Animal {
  move() {
    console.log("Moving along");
  }
}
class Dog extends Animal {
  bark() {
    console.log("汪汪");
  }
}
const dog = new Dog();
dog.bark();
dog.move();

类的实现

interface Singable {
  sing(): void;
}
class Person implements Singable {
  sing(): void {
    console.log("帝王一命");
  }
}

类成员的可见性

可以使用TS来控制class的方法或属性对于class外的代码是否可见

可见性修饰符包括public、protected、private

public:可以被任意文件访问,默认可见性

class Animal {
  public move() {
    console.log("Moving along");
  }
}

protected:受保护的,仅对其声明所在类和子类(非实例对象)中可见

class Animal {
  protected move() {
    console.log("Moving along");
  }
}
class Dog extends Animal {
  bark() {
    console.log("汪汪");
    this.move();
  }
}
const dog = new Dog();
dog.bark();

private:私有的,在当前类中可见

class Animal {
  private move() {
    console.log("Moving along");
  }
  walk() {
    this.move();
  }
}

readonly修饰符

用来防止在构造函数之外对属性进行赋值

class Person {
  readonly age: number = 18;

  constructor(age: number) {
    this.age = age;
  }
}
  • 使用readonly关键字修饰该属性是只读的,注意只能修饰属性不能修饰方法
  • 属性age后面的类型注解如果不加,则为字面量类型
  • 接口或者{}表示的对象类型,也可以使用readonly

类型兼容性

两种类型系统:1. Structural Type System(结构化类型系统)、2. Nominal Type System(标明类型系统)

TS采用的是结构化类型系统,也叫做duck typing(鸭子类型),类型检查关注的是值所具有的形状

也就是说,在结构类型系统中,如果两个对象具有相同的形状,则认为他们属于同一类型

class Point {
  x: number;
  y: number;
}
class Point2D {
  x: number;
  y: number;
}
const p: Point = new Point2D()
  1. Point和Point2D是两个名称不同的类
  2. 变量p的类型被显示标注为Point类型,但是它的值确实Point2D的实例对象,并且没有类型错误
  3. 因为TS是结构化类型系统,只检查Point和Point2D的结构是否相同
  4. 但是,如果在Nominal Type System(标明类型系统),它们是不同的类,类型无法兼容

注意:在结构类型系统中,如果两个对象具有相同的形状,则认为他们属于同一类型这种说法并不准确,对于对象类型来说,y的成员至少与x相同,则x兼容y

class Point {
  x: number;
  y: number;
}
class Point3D {
  x: number;
  y: number;
  z: number;
}
const p: Point = new Point3D();

除了class之外,TS中的其他类型也存在相互兼容的情况,包括接口兼容性和函数兼容性等

  • 接口之间的兼容性,类似于class,并且class于interface之间也可以兼容
interface Point {
  x: number;
  y: number;
}
interface Point2D {
  x: number;
  y: number;
}
interface Point3D {
  x: number;
  y: number;
  z: number;
}
let p1: Point;
let p2: Point2D;
let p3: Point3D;
p1 = p2;
p1 = p3;
class Point3_D {
  x: number;
  y: number;
  z: number;
}
p2 = new Point3_D();
  • 函数之间兼容性比较复杂,需要考虑,参数个数、参数类型、返回值类型

  • 参数个数:参数多的兼容参数少的(参数少的可以赋值给多的)

  • 参数类型:相同位置的参数类型要相同

  • 返回值类型:只关注返回值本身即可

// 参数个数:参数多的兼容参数少的(参数少的可以赋值给多的)
type F1 = (a: number) => void;
type F2 = (a: number, b: number) => void;
let f1: F1;
// let f2: F2 = f1;

// 参数类型:相同位置的参数类型要相同
type F3 = (a: number) => string;
type F4 = (a: number) => string;
// let f3: F3;
// let f4: F4 = f3;
interface Point2D {
  x: number;
  y: number;
}
interface Point3D {
  x: number;
  y: number;
  z: number;
}
type F5 = (p: Point2D) => void;
type F6 = (P: Point3D) => void;
// let f5: F5
// let f6: F6 = f5

// 返回值类型:只关注返回值本身即可
// 若返回值是对象类型,则多的可以赋值给少的
type F7 = () => string;
type F8 = () => string;
// let f7: F7;
// let f8: F8 = f7;

交叉类型

interface Point2D {
  x: number;
  y: number;
}
interface Point3D extends Point2D {
  z: number;
}
let obj: Point3D = {
  x: 1,
  y: 2,
  z: 3,
};

// 交叉类型
interface Person {
  naem: string
}
interface Contact {
  phone: string
}
type PersonDetail = Person & Contact
let PersonContact: PersonDetail = {
  naem: "jack",
  phone: "13122346567"
}

交叉类型和接口继承的区别

interface A {
  fn: (value: number) => string;
}
//BUG 会报错类型不兼容
// interface B extends A {
//   fn: (value: string) => string;
// }

// 不会报错
interface B {
  fn: (value: string) => string;
}
type C = A & B;
let c: C = {
  fn(value: string | number) {
    return "";
  },
};

泛型

泛型是可以保证在类型安全的情况下,让函数等与多种类型一起工作,从而实现复用,常用于函数,接口,class中

需求:创建一个id函数,传入什么数据就返回该数据本身

function id(value: number) {
  return value;
}

为了能让函数接收任意类型,可以将参数类型改为any,但是这样就失去了TS的类型保护,类型不安全

function id(value: any) {
  return value;
}

泛型在保证类型安全的同时,可以让函数等与多种不同的类型一起工作,灵活可复用

创建泛型函数

// 创建泛型函数
function id<Type>(value: Type) {
  return value;
}
// 调用泛型函数
id<string>("帝王");
  1. 在函数名称的后面添加<>(尖括号),尖括号中添加类型变量,比如此处的Type
  2. 类型变量Type是一种特殊类型的变量,它处理类型而不是值
  3. 该类型变量相当于一个类型容器,能够捕获用户提供的类型
  4. 因为Type是类型,因此可以将其作为函数参数和返回值的类型,表示参数和返回值具有相同的类型
  5. 类型变量Type,可以是任意合法的变量名称
// 创建泛型函数
function id<Type>(value: Type): Type {
  return value;
}
id<string>("帝王");
// 简化调用泛型函数
let num = id(10);
let str = id("帝王");

泛型约束

默认情况下,泛型函数的类型变量Type可以代表多个类型,这导致无法访问任何属性

添加泛型约束收缩类型,主要有以下两种方式

  1. 指定更加具体的类型
// 泛型约束
function id<Type>(value: Type[]) {
  return value.length;
}
  1. 添加约束
interface ILength {
  length: number;
}
function id<Type extends ILength>(value: Type) {
  return value.length;
}
  1. 创建描述约束的接口,该接口要求提供length属性
  2. 通过extends关键字使用该接口,为泛型添加约束
  3. 该约束表示传入的参数必须具有length属性

多个泛型变量的情况

泛型的类型变量是可以有多个的,并且类型变量之间还可以约束

比如:创建一个函数来获取对象中的属性的值

function getProp<Type, Key extends keyof Type>(obj: Type, key: Key) {
  return obj[key];
}
let person = { name: "jack", age: 18 };
getProp(person, "name");
  1. 添加了第二个类型变量,两个类型变量用逗号分割
  2. keyof关键字接收一个对象类型生成其键名称的联合类型
  3. 类型变量Key受Type约束,Key只能是Type所有键中的任意一个

泛型接口

泛型接口:接口也可以配合泛型来使用,以增加其灵活性,增强其复用性

interface idFunc<Type> {
  id(value: Type): Type;
  ids(): Type[];
}
let obj: idFunc<number> = {
  id(val) {
    return val;
  },
  ids() {
    return [1, 2];
  },
};

实际上,JS的数组在TS中就是一个泛型接口

泛型类

创建泛型类

// class GenericNumber<NumType> {
//   defaultValue: NumType;
//   add: (x: NumType, y: NumType) => NumType;
// }
// const myNum = new GenericNumber<number>();
// myNum.defaultValue = 10;

// 省略情况<number>
class GenericNumber<NumType> {
  constructor(value: NumType) {
    this.defaultValue = value;
  }
  defaultValue: NumType;
  add: (x: NumType, y: NumType) => NumType;
}
const myNum = new GenericNumber(100);
myNum.defaultValue = 10;

泛型工具类型

泛型工具类型:TS内置了一些常用的工具类型,来简化了一些常见操作

它们都想基于泛型实现的,并且是内置的,可以直接在代码中使用

这些工具类型有很多,简单来看一下

// 用来构造一个类型,将Type所有属性设置为可选
interface Props {
  id: string;
  children: number[];
}
// Props的所有属性变为可选的
type PartialProps = Partial<Props>;
interface Props {
  id: string;
  children: number[];
}
// 将Props的所有属性变为只读的
type ReadonlyProps = Readonly<Props>;
interface Props {
  id: string;
  children: number[];
}
// 选取属性来构建新类型
type PickProps = Pick<Props, 'id' | 'children'>
/*
  type RecordObj = {
    a: string[];
    b: string[];
    c: string[];
  }
*/
type RecordObj = Record<"a" | "b" | "c", string[]>;

索引签名类型

绝大多数情况下,我们都可以在使用对象前就确定对象的结构,并为对象添加准确的类型

当无法确定对象中有哪些属性,此时就用到索引签名类型

interface AnyObject {
  [key: string]: number;
}
let obj: AnyObject = {
  a: 1,
  b: 2,
};

映射类型

映射类型:基于旧类型创建新类型,减少重复,提升开发效率

type PropKeys = "x" | "y" | "z";
type Type = { x: number; y: number; z: number };

用映射类型书写

type PropKeys = "x" | "y" | "z";
type Type2 = { [key in PropKeys]: number };

映射类型除了根据联合类型创建新类型外,还可以根据对象类型来创建

type Props = { a: number; b: string; c: boolean };
type Type3 = { [key in keyof Props]: number };
type Partial<T> = {
    [P in keyof T]?: T[P];
};

索引查询类型

作用:用来查询属性的类型

type Props = { a: number; b: string; c: boolean };
type TypeA = Props["a"];

Props[“a”]表示查询类型Props中属性a的类型number,所以,TypeA的类型为number

[]中的属性必须存在于被查询类型中,否则就会报错

索引查询类型的其他使用方式:同时查询多个索引的类型

type Props = { a: number; b: string; c: boolean };
type Type_A_B = Props['a' | 'b']

使用字符串字面量的联合类型,获取属性a和b对应的类型,结果为string | number

type Types = Props[keyof Props]

使用keyof操作符获取Props中所有键对应的类型

类型声明文件

类型声明文件概述

类型声明文件:用来为已存在的JS库提供类型信息

TS中的两种文件类型

  1. .ts文件:

既包含类型信息又可以执行代码

可以被编译为.js文件,然后执行代码

用途:编写程序代码的地方

  1. .d.ts文件

只包含类型信息的类型声明文件

不会生成.js文件,仅用于提供类型信息

用途:为JS提供类型信息

类型声明文件的使用

在使用TS开发项目时,类型声明文件的使用包含下面两种方式

  1. 使用已有的类型声明文件
  2. 创建自己的类型声明文件
  • 使用已有的类型声明文件
  1. 内置类型声明文件

TS为JS运行时可以的所有标准化内置API都提供了声明文件

比如:在使用数组时,数组所有方法都会有相应的代码提示以及类型信息

实际上这都是TS提供的内置类型声明文件

  1. 第三方库的类型声明文件

目前,几乎所有的常用第三方库都有自己的类型声明文件,第三方库的类型声明文件有两种存在形式1. 库自带类型声明文件2. 由DefinitelyTyped提供

如果是库自带的类型声明文件,正常导入该库,TS就会自带加载库自己的类型声明文件,以提供该库的类型声明

如果是DefinitelyTyped提供,则官网可以查看

  • 创建自己的类型声明文件
  1. 项目内共享类型

如果多个.ts文件中都用到同一个类型,此时可以创建.d.ts文件提供该类型,实现类型共享

操作步骤:

  1. 创建index.d.ts类型声明文件
  2. 创建需要共享的类型,并导出
  3. 在需要使用共享类型的.ts文件中导入即可

index.d.ts文件如下:

type Props = { x: number; y: number };
export { Props };

a.ts文件如下:

import { Props } from "./index";
let p1: Props = {
  x: 1,
  y: 2,
};

b.ts文件如下:

import { Props } from "./index";
let p2: Props = {
  x: 22,
  y: 11,
};
  1. 为已有JS文件提供类型声明

在将JS项目迁移到TS项目时,为了让已有的.js文件有类型声明

成为库作者,创建库给他人使用

在导入JS文件时,TS会自动加载与JS文件同名的.d.ts文件,以提供类型声明

declare关键字:用于类型声明,为其他地方以存在的变量声明类型,而不是创建一个新的变量

在React中使用TypeScript

使用CRA创建支持TS的项目

创建支持TS的项目命令:npx create-react-app my-app --template typescript

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k2XXl8nj-1681822203281)(image/image-20230416212252427.png)]

项目根目录中增加了tsconfig.json配置文件,指定TS的编译选项

react-app-env.d.ts文件:React项目默认的类型声明文件

三斜线指令:指定依赖的其他类型声明文件,types表示依赖的类型声明文件包的名称

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ioc4TaSm-1681822203282)(image/image-20230416213408802.png)]

解释:告诉TS帮我加载react-scripts这个包提供的类型声明

react-scripts的类型声明文件包含了两部分类型

  1. react、react-dom、node的类型
  2. 图片、样式等模块的类型,以允许在代码中导入图片,SVG等文件

TS会自动加载.d.ts文件,以提供类型声明(通过修改tsconfig.json文件中的include配置来验证)

tsconfig.json文件:项目文件和项目编译所需的配置项

React中的常用类型

  1. 函数组件,主要包括以下内容
  • 组件的类型
  • 组件的属性
import React from 'react';
import ReactDOM from 'react-dom/client';

type Props = {
  name: string
  age?: number
}

// FC是函数组件类型,React内置的
const App: FC<Props> = ({name, age}) => {
  return (
    <div>你好,我叫{name},{age}岁了</div>
  )
}

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(<App name="jack"/>)

也可以这样写

import React from 'react';
import ReactDOM from 'react-dom/client';

type Props = {
  name: string
  age?: number
}

const App = ({name, age}: Props) => {
  return (
    <div>你好,我叫{name},{age}岁了</div>
  )
}

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(<App name="jack" />);
  • 组件属性的默认值
import React, { FC } from "react";
import ReactDOM from "react-dom/client";

type Props = {
  name: string;
  age?: number;
};

const App: FC<Props> = ({ name, age }) => {
  return (
    <div>
      你好,我叫{name},{age}岁了
    </div>
  );
};

App.defaultProps = {
  age: 18,
};

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(<App name="jack" age={12} />);

也可以这样写

import React, { FC } from "react";
import ReactDOM from "react-dom/client";

type Props = {
  name: string;
  age?: number;
};

const App = ({ name, age = 18 }: Props) => {
  return (
    <div>
      你好,我叫{name},{age}岁了
    </div>
  );
};

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(<App name="jack" age={12} />);
  • 事件绑定和事件对象

事件对象的类型可以巧用TS的类型推断机制即可

  1. class组件,主要包括以下内容
  • 组件的类型、属性、事件
  • 组件的状态
import React, { Component } from "react";
import ReactDOM from "react-dom/client";

type State = {
  count: number;
};

type Props = {
  message: string;
};

class App extends Component<Props, State> {}

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(<App message="jack" />);
import React, { Component } from "react";
import ReactDOM from "react-dom/client";

type Props = {
  name: string;
  age?: number;
};

class App extends Component<Props> {
  // static defaultProps: Partial<Props> = {
  //   age: 18,
  // };
  render() {
    // const { name, age } = this.props;
    const { name, age = 18 } = this.props;
    return (
      <div>
        你好,我叫:{name},{age} 岁了
      </div>
    );
  }
}

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(<App name="jack" />);
import React, { Component } from "react";
import ReactDOM from "react-dom/client";

type State = {
  count: number;
};

class App extends Component<{}, State> {
  state = {
    count: 0,
  };
  onIncrement = () => {
    this.setState({
      count: this.state.count + 1,
    });
  };
  render() {
    return (
      <div>
        计数器:{this.state.count}
        <br />
        <button onClick={this.onIncrement}>+1</button>
      </div>
    );
  }
}

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(<App />);
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

草莓小子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值