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代码
-
创建hello.ts文件
-
将TS编译为JS:在终端中输入命令tsc hello.ts(此时,在同级目录中会出现一个同名的JS文件)
-
在终端输入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代码,太麻烦
- 安装ts-node包:
npm i -g ts-node
- 执行命令
ts-node hello.ts
TypeScript常用类型
类型注解
示例代码
let age: number = 18;
说明:代码中的 :number 就是类型注解
作用:为变量添加类型约束,比如,上述代码中,约定变量age的类型为number
解释:约定了什么类型,就只能给变量赋值该类型的值,否则会报错
常用基础类型概述
- JS已有类型
- 原始类型:number/string/boolean/null/undefined/symbol
- 对象类型:Object(数组、对象、函数等)
- TS新增类型
- 联合类型、自定义类型、接口、元组、字面量类型、枚举、void、any等
原始类型就不在介绍了JS中我们已经详细学过了
数组类型
数组类型的两种写法
类型别名
类型别名:为任意类型起别名
使用场景:当同一类型复杂且被多次使用,可以通过类型别名简化该类型的使用
- 使用type关键字来创建类型别名
- 类型别名可以是任意合法的变量名称
- 创建类型别名后,直接使用该类型别名作为变量的类型注解即可
函数类型
函数的类型实际上指的是函数参数和返回值的类型
为函数指定类型的两种方式:
- 单独指定参数、返回值的类型
- 同时指定参数、返回值的类型
如果函数没有返回值
使用函数实现某个功能时,参数可以传也可以不传,这种情况下,再给函数参数指定类型时,就用到可选参数了
可选参数只能出现在参数列表的最后
对象类型
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关键字来声明接口
- 接口名称可以是任意合法的变量名称
- 声明接口后,直接使用接口名称作为变量的类型
- 因为每一行只有一个属性类型,因此,属性类型后没有分号隔开
接口与类型别名对比
- 相同点:都可以给对象指定类型
- 不同点:
- 接口:只能为对象指定类型
- 类型别名:可以为任意类型指定别名
// 都可以为对象指定类型
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);
解释
- 使用enum关键字定义枚举
- 约定枚举名称,枚举中的值以大写字母开头
- 枚举中的多个值以逗号分割
- 定义好枚举后,直接使用枚举名称作为类型注解
问题:我们把枚举成员作为了函数的实参,它的值是什么呢?
解释:通过将鼠标移入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()
- Point和Point2D是两个名称不同的类
- 变量p的类型被显示标注为Point类型,但是它的值确实Point2D的实例对象,并且没有类型错误
- 因为TS是结构化类型系统,只检查Point和Point2D的结构是否相同
- 但是,如果在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>("帝王");
- 在函数名称的后面添加<>(尖括号),尖括号中添加类型变量,比如此处的Type
- 类型变量Type是一种特殊类型的变量,它处理类型而不是值
- 该类型变量相当于一个类型容器,能够捕获用户提供的类型
- 因为Type是类型,因此可以将其作为函数参数和返回值的类型,表示参数和返回值具有相同的类型
- 类型变量Type,可以是任意合法的变量名称
// 创建泛型函数
function id<Type>(value: Type): Type {
return value;
}
id<string>("帝王");
// 简化调用泛型函数
let num = id(10);
let str = id("帝王");
泛型约束
默认情况下,泛型函数的类型变量Type可以代表多个类型,这导致无法访问任何属性
添加泛型约束收缩类型,主要有以下两种方式
- 指定更加具体的类型
// 泛型约束
function id<Type>(value: Type[]) {
return value.length;
}
- 添加约束
interface ILength {
length: number;
}
function id<Type extends ILength>(value: Type) {
return value.length;
}
- 创建描述约束的接口,该接口要求提供length属性
- 通过extends关键字使用该接口,为泛型添加约束
- 该约束表示传入的参数必须具有length属性
多个泛型变量的情况
泛型的类型变量是可以有多个的,并且类型变量之间还可以约束
比如:创建一个函数来获取对象中的属性的值
function getProp<Type, Key extends keyof Type>(obj: Type, key: Key) {
return obj[key];
}
let person = { name: "jack", age: 18 };
getProp(person, "name");
- 添加了第二个类型变量,两个类型变量用逗号分割
- keyof关键字接收一个对象类型生成其键名称的联合类型
- 类型变量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中的两种文件类型
- .ts文件:
既包含类型信息又可以执行代码
可以被编译为.js文件,然后执行代码
用途:编写程序代码的地方
- .d.ts文件
只包含类型信息的类型声明文件
不会生成.js文件,仅用于提供类型信息
用途:为JS提供类型信息
类型声明文件的使用
在使用TS开发项目时,类型声明文件的使用包含下面两种方式
- 使用已有的类型声明文件
- 创建自己的类型声明文件
- 使用已有的类型声明文件
- 内置类型声明文件
TS为JS运行时可以的所有标准化内置API都提供了声明文件
比如:在使用数组时,数组所有方法都会有相应的代码提示以及类型信息
实际上这都是TS提供的内置类型声明文件
- 第三方库的类型声明文件
目前,几乎所有的常用第三方库都有自己的类型声明文件,第三方库的类型声明文件有两种存在形式1. 库自带类型声明文件2. 由DefinitelyTyped提供
如果是库自带的类型声明文件,正常导入该库,TS就会自带加载库自己的类型声明文件,以提供该库的类型声明
如果是DefinitelyTyped提供,则官网可以查看
- 创建自己的类型声明文件
- 项目内共享类型
如果多个.ts文件中都用到同一个类型,此时可以创建.d.ts文件提供该类型,实现类型共享
操作步骤:
- 创建index.d.ts类型声明文件
- 创建需要共享的类型,并导出
- 在需要使用共享类型的.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,
};
- 为已有JS文件提供类型声明
在将JS项目迁移到TS项目时,为了让已有的.js文件有类型声明
成为库作者,创建库给他人使用
在导入JS文件时,TS会自动加载与JS文件同名的.d.ts文件,以提供类型声明
declare关键字:用于类型声明,为其他地方以存在的变量声明类型,而不是创建一个新的变量
在React中使用TypeScript
使用CRA创建支持TS的项目
创建支持TS的项目命令:npx create-react-app my-app --template typescript
项目根目录中增加了tsconfig.json配置文件,指定TS的编译选项
react-app-env.d.ts文件:React项目默认的类型声明文件
三斜线指令:指定依赖的其他类型声明文件,types表示依赖的类型声明文件包的名称
解释:告诉TS帮我加载react-scripts这个包提供的类型声明
react-scripts的类型声明文件包含了两部分类型
- react、react-dom、node的类型
- 图片、样式等模块的类型,以允许在代码中导入图片,SVG等文件
TS会自动加载.d.ts文件,以提供类型声明(通过修改tsconfig.json文件中的include配置来验证)
tsconfig.json文件:项目文件和项目编译所需的配置项
React中的常用类型
- 函数组件,主要包括以下内容
- 组件的类型
- 组件的属性
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的类型推断机制即可
- 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 />);