1.Typescript介绍
1.1 Typescript为什么要为JS添加类型支持?
JS的类型系统本身就存在“不足”,JS代码中绝大部分错误都是类型错误(Uncaught TypeError)。 从编程语言的动静来区分,TypeScript属于静态类型的编程语言,JS属于动态类型的编程语言。 静态类型:编译期做类型检查;动态类型:执行期做类型检查。 代码编译和代码执行的顺序:1 编译 2执行。
1.2 Typescript相比JS的优势?
1.更早(写代码的同时)发现错误,减少找bug、改bug时间,提升开发效率。 2.程序中任何位置的代码都有代码提示,随时随地的安全感,增强了开发体验。 3.强大的类型系统提升了代码的可维护性,使得重构代码更加容易。 4.支持最新的ECMAScript语法,优先体验最新的语法,让你走在前端技术的最前沿。 5.TS类型推断机制,不需要在代码中每个地方显示标注类型,让你在享受优势的同时,尽量降低了成本。
2.Typescript初体验
2.1 安装编译TS的工具包
用来编译TS代码的包,提供了tsc的命令,实现了TS->JS的转化。 验证是否安装成功:tsc -v(查看typescript的版本)。
npm install -g typescript
2.2 编译并运行TS代码
- 创建hello.ts文件(注意:TS文件的后缀名为.ts)
- 将TS编译为JS: 在终端中输入,tsc hello.ts(此时,在同级目录中会出现一个同名的JS文件)。
- 执行JS代码:在终端中输入命令,node hello.js
2.3 简化运行TS代码
问题描述:每次修改代码后,都要执行两个命令,才能运行TS代码,非常繁琐。 简化方式:
npm i -g ts-node
使用方式: ts-node hello.ts
解释:ts-node命令其实内部还是偷偷的将TS->JS,然后,再运行JS代码。
3.Typescript常用类型
3.1 常用基础类型概述
可以将TS中的常用基础类型细分为两类,1.JS已有类型。2.TS新增类型。
1.JS已有类型:number,string,boolean,null,undefined,symbol,object
2.TS新增类型:联合类型,自定义类型(类型别名),接口,元祖,字面量类型,枚举,void,any等。
3.2 联合类型
let arr: (number | string)[] = [1, 'a', 3, 'b']
解释:|(竖线)在TS叫做联合类型(由2个或多个其他类型组成的类型,表示可以是这些类型中的任意一种)。
3.3 类型别名
type CustomArray = (number | string)[];
let arr1: CustomArray = [1, 'a', 3, 'b'];
3.4 接口
当一个对象类型被多次使用时,一般会使用接口来描述对象的类型,达到复用的目的。 解释:
- 使用interface关键字来声明接口。
- 接口名称(比如:此处IPerson),可以是任意合法的变量名称。
- 声明接口后,直接使用接口名称作为变量的类型。
interfance IPerson {
name: string;
age: string;
sayHi: () => void;
}
let person: IPerson = {
name: 'jack',
age: 19,
sayHi: () => {
console.log('say')
}
}
3.5 接口和类型别名区别
相同点:都可以给对象指定类型。 不同点: 接口,只能为对象制定类型。 类型别名,不仅可以为对象制定类型,实际上可以为任意类型指定别名。
interface IPerson {
name: string;
age: number;
sayHi(): void;
}
type IPerson = {
name: string;
age: number;
sayHi(): void;
}
type NumStr = number | string;
3.6 元组
元组类型是另一种类型的数组,它确切地知道包含了多少个元素,以及特定索引对应的类型。
let position: [number, number] = [39.5427, 116.2317]
3.7 类型断言
有时候你会比TS更加明确一个值的类型,此时,可以使用类型断言来指定更具体的类型。 比如:
const alink = document.getElementById('link') as HTMLAnchorELement
HTML类型如何确定: 就是当你想看哪个HTML的时候,就会有$0
然后
console.dir($0)
3.8 字面量类型
let str1 = 'Hello TS'
const str2 = 'Hello TS'
通过TS类型推论机制,可以得到答案:
- 变量str1的类型为:string
- 变量str2的类型为:'Hello TS' 解释:
- str1是一个变量(let),它的值可以是任意字符串,所以类型为:string。
- str2是一个常量(const),它的值不能变化只是‘Hello TS’,它的类型为:‘Hello TS’ 除了字符串外,任意的JS字面量(比如对象、数字等)都可以作为类型使用。
function changeDirection(direction: 'up' | 'down' | 'left'|'right') {
console.log(direction)
}
3.9 枚举
enum Direction { Up, Down, Left, Right }
function changeDirection(direction: Direction) {
console.log(direction)
}
changeDirection(Direction.Up)
enum Direction {
Up = 2,
Down = 4,
Left = 8,
Right = 16
}
changeDirection(Direction.Up)
// 字符串枚举
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT'
}
console.log(Direction['Up']) // 'UP'
注意:字符串枚举没有自增长行为,因此,字符串枚举的每个成员必须有初始值。
// 反向映射
enum role {
student,
teacher,
admin
}
console.log(role.admin) // 2
console.log(role['teacher']) //1
console.log(role[0]) // 'student'
3.10 typeof
let p = {x: 1, y: 2}
function formatPoint(point: typeof p) {
}
// typeof p 就会转换为{x: number, y: number}
3.11 unknown
unknown和any的区别:unknow具备any的功能同时也保留着静态检查的能力,any就不会静态检查了。
const test: unknown = 'string';
test.substr(1) // 这个时候就会报类型错误
const test2: any = 1000
1000.substr(1) // 不会报错
3.12 void
void和undefined的区别就在于undefined是void的一个子集。void不关注具体返回什么值,都可以。
3.13 never
never是指没法正常结束返回的类型,一般是用在报错或者死循环的函数里。
function test1(): never { throw new Error('error')}
function test2(): never {while(true) {}}
type Test3 = 'n' | never // 返回'n'
function test4 {
test1()
console.log('永远都不会执行到这条语句,因为上面这个函数返回了never')
}
4. Typescript 高级类型
4.1 TS中的高级类型有很多
1.重点学习以下高级类型:
class类
类型兼容性
交叉类型
泛型和keyof
索引签名类型和索引查询类型
映射类型
4.2 class类
class Person {
age: number
gender: string
constructor(age: number, gender: string) {
this.age = age;
this.gender = gender
}
scale(n: number): void {
this.x* = n;
this.y* = n;
}
}
const p = new Person(18, '男')
4.3 class类继承
1.extends(继承父类)2.implements(实现接口) 说明JS中只有extends,而implements是TS提供的。
class Animal {
move() {
console.log('Moving along')
}
}
class Dog extends Animal {
bark() {
console.log('汪')
}
}
const dog = new Dog()
interface Singale {
sing(): void;
}
class Person implements Singale {
sing() {
console.log('object')
}
}
4.4 class类的protected
protected:表示受保护的,仅对其声明所在类和子类中(非实例对象)可见。
class Animal {
protected move() {
console.log('Moving along')
}
}
class Dog extends Animal {
bark() {
console.log('汪')
this.move()
}
}
4.5 class类的private
private: 表示私有的,只在当前类中可见,对实例对象以及子类也是不可见的。
class Animal {
private move() {
console.log('moving')
}
walk() {
this.move()
}
}
4.6 class类的readonly
readonly: 表示只读,用来防止在构造函数之外对属性进行赋值。
class Person {
readonly age: number = 18
constructor(age: number) {
this.age = age
}
}
使用readonly关键字修饰该属性是只读的,注意只能修饰属性不能修饰方法。 注意:属性age后面的类型注解(比如,此处的number)如果不加,则age的类型为18(字面量类型)。 接口或者{}表示的对象类型,也可以使用readonly
interface NameArr {
readonly name: string;
}
const abc = (test: NameArr) => {
}
let obj: {readonly name: string} = {
name: 'jack'
}
obj.name = 'rose' // 报错
4.7 交叉类型
交叉类型(&):功能类似于接口继承(extends),用于组合多个类型为一个类型(常用于对象类型)。
interface Person {
name: string;
}
interface Contact {
phone: string;
}
type PersonDetail = Person & Contact
let obj: PersonDetail = {
name: 'jack',
phone: '1234343434'
}
4.8 交叉类型(&)和接口继承(extends)的对比:
- 相同点:都可以实现对象类型的组合。
- 不同点:两种方式实现类型组合时,对于同名属性之间,处理类型冲突的方式不同。
interface A {
fn: (value: number) => string;
}
interface B extends A {
fn: (value: string) => string;
}
// 以上代码,接口继承会报错。
type C = A & B
C = {
fn: ( Value: string | number) => string
}
交叉类型不会报错
4.9 泛型:
泛型在保证类型安全的同时,可以让函数等与多种不同的类型一起工作,灵活可复用。
function id<Type>(value: Type): Type {
return value;
}
id<number>(10); // 类型number
id<string>('10'); // 类型string
4.10 简化泛型函数调用
function id<Type>(value: Type): Type {
return value;
}
let num = id(10)
解释:
- 在调用泛型函数时,可以省略<类型>来简化泛型函数的调用。
- 此时,TS内部会采用一种叫类型参数推断的机制,来根据传人的实参自动推断出类型变量Type的类型。
- 比如,传入实参10,TS会自动推断出变量num的类型number,并作为Type的类型。
4.11 泛型约束
添加泛型约束收缩类型,主要有以下两种方式:1.指定更加具体的类型。2.添加约束。
指定更加具体的类型,如下:
function id<Type>(value: Type[]): Type[] {
console.log(value.length)
return value
}
添加约束,如下:
interface ILength {
length: number
}
function id<Type extends ILength>(value: Type): Type {
console.log(value.length)
return value
}
解释:
1.创建描述约束的接口ILength,该接口要求提供length属性。
2.通过extends关键字使用该接口,为泛型(类型变量)添加约束。
3.该约束表示:传入的类型必须具有length属性。
所以在这里extends不是继承的意思了,表示传入的类型变量需要满足ILength
Type必须是Ilength的子集。
4.11.2 泛型条件
这里就不限制T一定要是U的子类型,如果是U子类型,则将T定义为X类型,否则定义为Y类型
T extends U ? X : Y
还有一种情况,如果把X换成T,如此形式:T extends U ? T:never 此时返回的T,是满足原来的T中包含U的部分,可以理解为T和U的交集
type Test<T> = T extends {t: infer B} ? B: string
Test<{a: number, t: number}> // 返回 number
4.12 泛型变量多个情况
泛型的类型变量可以有多个,并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量约束)。比如,创建一个函数来获取对象中属性的值:
function getProp<Type, Key extends keyof Type>(obj: Type, key: Key) {
return obj[key]
}
let person = {name: 'jack', age: 18}
getProp(person, 'name')
解释:
1.keyof关键字接收一个对象类型,生成其键名称(可能是字符串或数字)的联合类型。
2.本例子中keyof Type实际上获取的是person对象所有键的联合类型,也就是: 'name' | 'age'。
3.类型变Key受Type约束,可以理解为:Key只能是Type所有键中的任意一个,或者说只能访问对象中存在的属性。
4.13 泛型接口
interface IdFunc<Type> {
id: (value: Type) => Type;
ids: () => Type[];
}
let obj: IdFunc<number> = {
id(value) {return value},
ids() {
return [1, 3, 5]
}
}
解释:
- 在接口名称的后面添加<类型变量>,那么,这个接口就变成了泛型接口。
- 接口的类型变量,对接口中所有其他成员可见,也就是接口中所有成员都可以使用类型变量。
- 使用泛型接口时,需要显示指定具体的类型(比如,此处的IdFunc)。
- 此时,id方法的参数和返回值类型都是number;ids方法的返回值类型是number[]
4.14 泛型类
class GenericNumber<NumType> {
defaultValue: NumType
add: (x: NumType, y: NumType) => NumType
}
const myNum = new GenericNumber<number>();
myNum.defaultValue = 10;
4.14 泛型工具类型--Partial
将type的所有属性设置为可选。
interface Props {
id: string
children: number[]
}
type PartialProps = Partial<Props>
4.15 泛型工具类型-Readonly
将type的所有属性设置为只读。
interface Props {
id: string
children: number[]
}
type PartialProps = Readonly<Props>
4.16 泛型工具类型-Pick
interface Props {
id: string;
title: string;
children: number[];
}
type PickProps = Pick<Props, 'id' | 'title'>
4.17 泛型工具类型-Record
type RecordObj = Record<'a' | 'b' | 'c', string[]>
let obj: RecordObj = {
a: ['1'],
b: ['2'],
c: ['3']
}
4.18 索引签名类型
使用场景:当无法确定对象中有哪些属性(或者说对象中可以出现任意多个属性),此时,就用到索引签名类型了。
interface AnyObject {
[key: string]: number
}
解释:
1.使用key来约束该接口中允许出现的属性名称。表示只要是string类型的属性名称,都可以出现在对象中。
2.这样,对象obj中就可以出现任意多个属性(比如,a,b等)。
3.key只是一个占位符,可以换成任意合法的变量名称。
4.隐藏的前置知识:JS中的对象({})的键是string类型的。
在js中数组是一类特殊的对象,特殊在数组的键(索引)是数值类型。
并且,数组也可以出现任意多个元素。所以,在数组对应的泛型接口中,也用到了索引签名类型。
interface MyArray<T> {
[n: number]: T
}
let arr: MyArray<number> = [1, 3, 5]
4.19 映射类型
映射类型:基于旧类型创建新类型(对象类型),减少重复,提升开发效率。
// bad
type PropKeys = 'x' | 'y' | 'z';
type Type1 = {x: number; y: number; z: number};
// good
type PropKeys = 'x' | 'y' | 'z';
type Type2 = { [Key in PropKeys]: number };
// 报错
interface Type3 {
[Key in PropKeys]: number
}
注意:映射类型只能在类型别名中使用,不能在借口中使用。
映射类型除了根据联合类型创建新类型之外,还可以根据对象类型来创建:
type Props = {
a: number;
b: string;
c: boolean;
}
type Types = {
[key in keyof Props]: number
}
解释:
- 首先,先执行keyof Props获取到对象类型Props中所有键的联合类型即:'a'|'b'|'c'。
- 然后,key in ...表示Key可以是Props中所有的键名称中的任意一个。
泛型工具类型(比如,Partial)都是基于映射类型实现的。
// 冒号后面的T[P]表示获取T中每个键对应的类型。
type Partial<T> = {
[P in keyof T]?: T[P]
}
type Props = {
a: number;
b: string;
c: boolean;
}
type PartialProps = Partial<Props>
刚刚用到的T[P]语法,在TS中叫做索引查询(访问)类型。 作用:用来查询属性的类型。
type Props = {
a: number;
b: string;
c: boolean;
}
type TypeA = Props['a']; //number类型
索引查询类型的其他使用方式:同时查询多个索引的类型
type Props = {
a: number;
b: string;
c: boolean;
}
type TypeA = Props['a' | 'b'] // string | number
type TypeB = Props[keyof Props] // string | number | boolean
5.类型声明文件
今天几乎所有的js应用都会引入许多第三方库来完成任务需求。 这些第三方库不管是否是用TS编写的,最终都要编译成JS代码,才能发布给开发者使用。 我们知道是TS提供了类型,才有了代码提示和类型保护等机制。 但在项目开发中使用第三方库时,你会发现它们几乎都有相应的TS类型,这些类型是怎么来的呢?类型声明文件。 类型声明文件:用来为已存在的JS库提供类型信息。 这样在TS项目中使用这些库时,就像用TS一样,都会有代码提示,类型保护等机制了。
5.1 第三方库的类型声明文件
第三方库的类型声明文件有2种存在形式:1.库自带类型声明文件。2.由DefinitelyType提供。
1.库自带的类型声明文件: 比如,axios。
这种情况下,正常导入该库,TS就会自动加载库自己的类型声明文件,以提供该库的类型声明。
2.由DefinitelyType提供
基本上都是以@types/*提供类型声明包。
举个例子,使用lodash时,发现没有自带的类型声明文件,有2种方案可以解决。
// 第一种方案就是安装@type/lodash,你一旦安装完后,TS就会自动加载该类声明包,以提供该库的类型声明。
pnpm install @type/lodash -D
// 第二种方案
直接在项目中创建一个类型声明文件,通过使用declare module 'lodash'即可。
5.2 自己创建一个类型声明文件
创建自己的类型声明文件:1. 项目内共享类型 2.为已有JS文件提供类型声明。
1.项目内共享类型:如果多个.ts文件中都用到同一个类型,此时可以创建.d.ts文件提供该类型,实现类型共享。
操作步骤:
- 创建index.d.ts类型声明文件。
- 创建需要共享的类型,并使用export导出。
- 在需要使用共享类型的.ts文件中,痛过import导入即可。
// index.d.ts
export type Type1 = {
a: string;
}
// 定义一个模块,模块里面可以放该模块下的类型
declare module "url" {
export interface Url {
protocol?: string;
hostname?: string;
pathname?: string;
}
export function parse(
urlStr: string,
parseQueryString?: string,
slashesDenoteHost?: string,
): Url;
}
// 当然你也可以简写,如果一旦简写了,那么里面所有的导出类型都是any;
declare module "url"
// test.tsx
import Type1 from index;
5.3 为已有js文件提供类型声明。
在将JS项目迁移到TS项目时,为了让已有的.js文件有类型声明。 在导入.js文件时,TS会自动加载与.js同名的.d.ts文件,以提供类型声明。 delcare关键字:用于类型声明,为其他地方(比如,.js文件)已存在的变量声明类型,而不是创建一个新的变量。
- 对于type、interface等这些明确就是TS类型的,可以省略delcare关键字
- 对于let,function或者是函数表达式等具有双重含义(在JS,TS中都能用),应该使用declare关键字,明确指定此处用于类型声明。
// utils.js
let count = 10;
let position = {
x: 0,
y: 0
}
function add (x, y) {
return x+y;
}
function changeDirection(direction) {
console.log(direction)
}
const pointInfo = point => {
console.log('当前坐标', point)
}
export { count, add, changeDirection, pointInfo }
// utils.d.ts,要写在同一个目录下面,且文件名和js一致,则不需要export也可以,如果不在同一个目录下面,需要通过export进行导出才可。react脚手架最外层的声明文件名字是可以改动的,都会读到。
declare let count: number
interface Point {
x: number;
y: number;
}
declare let position: Point
declare function add(x: number, y: number): number
type Direction = 'up' | 'down' | 'left' | 'right'
declare function changeDirection(direction: Direction): void;
declare const pointInfo: (point: Point) => void;
export {
count,
add,
changeDirection,
pointInfo
}
6.React中使用TypeScript
使用CRA创建支持TS的项目 react-app-env.d.ts:React项目默认的类型声明文件。 三斜线指令:指定依赖的其他类型声明文件,types表示依赖的类型声明文件包的名称。
/// <reference types="react-scripts"/>
解释:告诉TS帮我加载react-scripts这个包提供的类型声明。 react-scripts的类型声明文件包含了两部分类型:
- react、react-dom、node的类型
- 图片、样式等模块的类型,以允许在代码中导入图片、SVG等文件。 TS会自动加载该.d.ts文件,以提供类型声明(通过修改tsconfig.json中的include配置来验证)。
6.1 React中的tsconfig.json
tsconfig.json可以自动生成,命令:tsc --init
{
"compilerOptions": {
// 生成js代码的语言版本
"target": "es5",
// 指定要包含在编译中的library
"lib": [
"dom",
"dom.iterable",
"esnext"
],
// 允许ts编译器编译js文件
"allowJs": true,
// 跳过声明文件的类型检查
"skipLibCheck": true,
// es模块互操作,屏蔽ESModule和CommonJS之间的差异
"esModuleInterop": true,
// 允许通过import x from y 即使模块没有显示指定default导出
"allowSyntheticDefaultImports": true,
// 严格模式
"strict": true,
"forceConsistentCasingInFileNames": true,
// switch语句错误提示
"noFallthroughCasesInSwitch": true,
// 生成代码的模块化标准
"module": "esnext",
// 模块解析(查找)策略
"moduleResolution": "node",
// 允许导入扩展名为.json的模块
"resolveJsonModule": true,
// 是否将没有import/export的文件视为旧(全局而非模块化)脚本文件。
"isolatedModules": true,
// 编译时不生成任何文件(只进行类型检查),使用babel进行处理,相关的loader
"noEmit": true,
// 指定jsx编译成什么形式
"jsx": "react-jsx"
},
// 指定允许ts处理的目录
"include": [
"src"
]
}
6.2 React中的常用类型
react是组件化开发模式,React开发主要任务就是写组件,两种组件:1.函数组件 2.class组件。
1.函数组件,主要包括以下内容:
1.组件类型
2.组件的属性(props)
3.组件属性的默认值(defaultProps)
const Hello:FC<Props> = ({name, age=18}) => {
return (
<>
<div{name}</div>
<div>{age}</div>
</>
)
}
4.事件绑定和事件对象
<button onClick={onClick}></button>
const onClick = () => {}
const onClick1 = (e: React.MouseEvent<HTMLButtonElement>) => {}
再比如,文本框:
<input onChange={onChange}></input>
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {}