TypeScript快速上手

文章目录


前言

哔哩哔哩课程链接: https://www.bilibili.com/video/BV14Z4y1u7pi?p=89&vd_source=92ee1c593b77f3dfb92819ea75999078


一、TypeScript是什么

TypeScript是JavaScript的超级 在JS的基础上增加了类型支持


二、TypeScript的好处

添加类型支持的好处:提高开发效率 减少找Bug的时间


三、TypeScript的优势

TS的优势:之一 类型推断机制 不需要在每个地方都显示标注类型


四、TypeScript的开发环境

安装环境 浏览器/node 只认识js代码 不认识ts代码 需要先将ts——>js 才能运行


五、编译并运行TS代码

1创建TS 2编译TS 3执行TS
执行指令 node .\test.js
注意:由TS编译生成的JS文件 代码中没有类型信息
ts:
let a : number = 100
console.log(a)
js:
var a = 100;
console.log(a);

六、 简化运行TS的步骤

问题:每次修改代码后 都要重复执行两个命令 才能运行TS 繁琐
简化方式:使用ts-node包 直接在node.js中执行ts代码
安装命令:npm i -g ts-node
使用方式:ts-node hello.ts
分析:ts-node命令在内部已经将TS->JS 再运行js代码


七、TS常用的类型

number、string、boolean、null、undefined、symbol


八、类型注解

为变量约定了类型 传值不符合规范会报错


九、常用的类型

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

十、数组类型


十一、联合类型

需求 数组中既有number类型 又有string类型


十二、pandas是什么?

类型别名(自定义类型):为任意类型起别名
使用场景:当同一类型(复杂)被多次使用时 可以通过类型别名 简化该类型的使用

type CustomArray = (number | string)[ ]
let arr1: CustomArray = [1,'a',3,'b']
let arr2: CustomArray = ['x','y',6,7]

解释:
《1》使用type关键字来创建类型别名
《2》类型别名 比如 此处的CustomArray 可以是任意合法的变量名称
《3》创建类型别名后 直接使用该类型别名作为变量的类型注解即可


十三、函数类型 实质就是给函数参数和返回值加类型

两种方式:

《1》单独指定参数 返回值的类型

function add(num1:number,num2:number):number{
return num1+num2
}

const add = (num1:number,num2:number):number=>{
return num1+num2
}

const add = () =>{} //箭头函数

《2》同时指定参数 返回值的类型

const cat: (num1: number, num2: number) => number = (num1, num2) => {
    return num1 - num2
}

console.log(cat(2, 1))

十四、void类型

函数没有返回值 返回值类型为void


十五、函数类型

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

function mySlice(start?:number,end?:number):void{
console.log('起始索引:',start,'结束索引:',end)
}

可选参数:在可传可不传的参数名称后面添加?
注意:可选参数只能出现在参数列表的最后 就是说可选参数后面不能出现必选参数


十六、对象类型(有什么类型的属性和方法)

写法:

let person: { name: string, age: number, sayHi(): void } = {
    name: "张三",
    age: 10,
    sayHi: () => { }
}

console.log(person)

注意: <1>如果一行代码只指定一个属性类型(通过换行来分割多个属性类型)可以去掉分号 <2>方法的类型也可以使用箭头函数的形式 sayHi:()=>void


十七、对象类型

可选属性的语法与函数可选参数的语法是一致的 都使用?来表示

function myAxios(config: { url: string, method?: string }) {
    console.log(config.url)
}
myAxios({
    url: 'https:www.baidu.com'
})

十八、接口

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

interface PersonI {
    name: string
    age: number
    sayHi: () => void
}

let personi:PersonI ={
    name: "personi",
    age: 20,
    sayHi: function (): void {
        throw new Error("Function not implemented.")
    }
}

解释:
《1》使用interface关键字来声明接口
《2》接口名称可以是任意合法的变量名称
《3》声明接口后 直接使用接口名称作为变量的类型
《4》因为每一行只有一个属性类型 因此 属性类型后没有;号


十九、interface 和type的对比

相同点: 都可以给对象指定类型
不同点:类型别名 不仅可以为对象指定类型 实质上可以为任意类型指定别名


二十、接口继承

interface Point2D {
    x: number
    y: number
}

interface Point3D extends Point2D {
    z: number
} 

二十一、元组

场景:在地图中 使用经纬度坐标来标记位置信息。
可以使用数组来记录坐标 那么 该数组中只有两个元素 并且这两个元素都是数值类型

let position: number[] = [39.5427,116.2317]

使用number的缺点:不严谨 因为该类型的数组中可以出现任意多个数字
更好的方式:元组(Tuple)
元组类型是另一种类型的数组,它确切地知道包含多少个元素 以及特定索引对应的类型

let position : [number,number]=[39.5427,116.2317]

解释:
《1》元组类型可以确切地标记出有多少个元素 以及每个元素的类型
《2》该示例 元素中有两个元素 每个元素的类型都是number


二十二、类型推论

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

let age = 13

function addL(num1: number, num2: number) {
    return num1 + num2
}

二十三、类型断言

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

console.log($0)
console.dir($0)

解释:
《1》使用as关键字 实现类型断言
《2》关键字as后面的类型是一个更加具体的类型
《3》通过类型断言 aLink的类型变得更加具体 这样访问a标签特有的属性和方法
// const aLink = document.getElementById(‘link’) as HTMLAnchorElement
const aLink = document.getElementById(‘link’)
aLink.href


二十四、字面量类型

let str1 = "Hello TS"
// let str1: string
const str2 = "Hello TS"
// const str2: "Hello TS"

使用模式:字面量类型配合联合类型一起使用
使用场景:用来表示一组明确的可选值列表

比如 在贪吃蛇游戏中 游戏的方向的可选值只能是上,下,左,右

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

changeDirection('up')

解释:参数direction的值只能是up/down/left/right
优势:相比于string类型 使用字面量类型更加精确 严谨


二十五、枚举

枚举的功能类似于字面量类型+联合类型的组合功能 也可以用于表示一组明确的可选值
枚举:定义一组命名常量 它描述一个值 该值可以是这些命名常量中的一个

enum Direction { Up, Down, Left, Right }

function changeDirection1(direction: Direction) {
    console.log(direction)
}

解释:
《1》使用enum 关键字定义枚举
《2》约定枚举名称 枚举中的值以大写字母开头
《3》枚举中的多个值之间通过(逗号)分割
《4》定义好枚举之后 直接使用枚举名称作为类型注解

注意:枚举成员是有值的 默认为:从0开始自增长的数值

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


二十六、字符串枚举

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

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

function changeDirection1(direction: Direction) {
    console.log(direction)
}

changeDirection1(Direction.Up)
changeDirection1(Direction.Down)
changeDirection1(Direction.Left)
changeDirection1(Direction.Right)

二十七、枚举的特点及原理

枚举是TS为数不多的非JavaScript类型级扩展(不仅仅是类型)的特性之一。
因为:其他类型仅仅被当作类型 而,而枚举不仅用作类型 还提供值(枚举成员都是有值的)。
也就是说 其他的类型会在编译为JS代码时自动移除 但是 枚举类型会被编译为JS代码

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

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

二十八、any类型

不推荐使用any 这会让TypeScript变为 AnyScript 失去TS类型保护的优势

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

解释:
《1》以上操作都不会有任何类型错误提示,即使可能存在错误
《2》其它 隐式具有any类型的情况
1.声明变量不提供类型也不提供默认值
2.函数参数不加类型


二十九、typeof

用来在JS中获取数据的类型

console.log(type "Hello World") //打印string

实际上 TS也提供了typeof操作符 可以在类型上下文中引用变量或属性的类型(类型查询)
使用场景:根据已有变量的值 获取该值的类型 来简化类型书写

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

function formatPoint(point:typeof p) {}


let p = { x: 1, y: 2 }

// function formatPoint(point: { x: number; y: number }) {console.log("hello") }
function formatPoint(point: typeof p) { console.log("hello") }
formatPoint(p)

解释:
《1》使用typeof操作符来获取变量p的类型 结果与第一种(对象字面量形式的类型)相同
《2》typeof 出现在类型注解的位置(参数名称的冒号后面)所处的环境就在类型上下文(区别于JS代码)
《3》注意:typeof只能用来查询变量或属性的类型 无法查询其他形式的类型(比如 函数调用的类型)


三十、TypeScript高级类型概述

1.class类

TypeScript全面支持ES2015中引入的class关键字 并为其添加了类型注解和其他语法(比如 可见性修饰符等)
class基本使用 如下:

class Person {}
const per = new Person()

解释:
1》根据TS中的类型推论 就可以知道Person类的实例对象p的类型是Person
2》TS中的class 不仅提供了class的语法功能 也作为一种类型存在

实例属性初始化

class Person{
age: number
gender = '男'
// gender : string = '男'
}

解释:
1》声明成员age 类型为number(没有初始值)
2》声明成员gender 并设置初始值 此时 可省略类型注解(TS类型推论为string类型)

2.类型兼容性

3.class类的构造函数

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

解释:
《1》成员初始化(比如 age:number)后 才可以通过this.age来访问实例成员。
《2》需要为构造函数指定类型注解 否则会被隐式推断为any 构造函数不需要返回值类型

4.class实例方法

class Point {
    x = 10
    y = 10

    scale(n: number): void {
        this.x *= n
        this.y *= n
    }
}

const point = new Point()
point.scale(10)
console.log(point.x, point.y)

解释:方法的类型注解(参数和返回值)与函数用法相同

5.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
dog.move()
dog.bark()

解释:
《1》通过extends关键字实现继承
《2》子类Dog继承父类Animal 则Dog的实例对象dog就同时具有了父类Animal和子类Dog的所有属性和方法

6.class继承 接口

interface Singable {
    sing(): void
}

class Person implements Singable {
    sing(): void {
        console.log('我是你的小呀小苹果')
    }
}

const per = new Person()
per.sing()

解释:
《1》通过implements关键字让class实现接口
《2》Person类实现接口Singable意味着 Person类中必须提供Singable接口中指定的所有方法和属性。

7.类成员可见性

类成员可见性:可以使用TS来控制class的方法或属性对于class外的代码是否是可见的
可见性修饰符包括:
《1》public 公有的 公开的
《2》protected 受保护的
《3》private 私有的

class Animal {
   public move() {
        console.log('Moving along!')
    }
}

解释:
《1》在类属性或方法前面添加public关键字 来修饰该属性或方法是共有的
《2》因为public是默认可见性 所以 可以直接省略

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

class Animal{
protected move(){console.log('Moving along!')}
}
class Dog extends Animal{
bark(){
console.log('汪!')
this.move()
}
}

解释:
《1》在类属性或方法前面添加protected关键字 来修饰该属性或方法是受保护的。
《2》在子类的方法内部可以通过this来访问父类中受保护的成员 但是对实例不可见!

class类的可见性修饰符
private 表示私有的 只在当前类中可见 对实例对象以及子类也是不可见的

class Animal {
private move(){ console.log('Moving along!')}
walk(){
   this.move()
}
}

解释:
《1》在类属性或方法前面添加private关键字 来修饰该属性或方法是私有的
《2》私有的属性或方法只在当前类中可见 对子类和实例对象也都是不可见的

readonly 只读修饰符

除了可见性修饰符之外 还有一个常见的修饰符就是:readonly(只读修饰符)
readonly:表示只读 用来防止在构造函数之外对属性进行赋值

class Person {
    readonly age: number = 18
    constructor(age: number) {
        this.age = age
    }
}

const z = new Person(11)
z.age =1
// 无法分配到 "age" ,因为它是只读属性。ts(2540)

解释:
《1》使用readonly关键字修饰该属性是只读的 注意只能修饰属性不能修饰方法
《2》注意:属性age后面的类型注解(比如 此处的number)如果不加 则age的类型为18(字面量类型)
《3》接口或者{}表示的对象类型 也可以使用readonly


三十一、类型兼容性

两种类型系统:
《1》Structural Type System(结构化类型系统)
《2》 Nominal Type Syatem (标明类型系统)
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的结构是否相同(相同 都具有x和y两个属性 属性类型也相同)
《4》但是 如果在Nominal Type System中 (比如C# Java等)它们是不同的类 类型无法兼容


三十二、对象之间的兼容性

注意:在结构化类型系统中,如果两个对象具有相同的形状 则认为它们属于同一类型 这种说法并不准确
更准确地说 对于对象类型来说 y的成员至少与x相同 则x兼容y (成员多的可以赋值给少的)

class Point {x:number | undefined ; y:number | undefined}
class Point3D {x:number | undefined ; y:number | undefined ; z:number | undefined}

解释:
《1》Point3D的成员至少与Point相同 则Point兼容Point3D
《2》所以 成员多的Point3D可以赋值给成员少的Point


三十三、类型兼容性

除了class之外 TS中的其他类型也存在相互兼容的情况
包括:

《1》接口兼容性

接口之间的兼容性 类似于class 并且 class和interface之间也可以兼容
多的给少的

interface Point {
    x: number
    y: number
}

interface Point2D {
    x: number
    y: number
}

interface Point3D {
    x: number
    y: number
}

let p1: Point
let p2: Point2D
let p3: Point3D

//正确:
// p1 = p2
// p2 = p1
// p1 = p3

// 错误演示:
// p3 = p1

class Point3D {x:number ; y:number ; z:number}
let p3:Point2D = new Point3D()

《2》函数兼容性

函数之间的兼容性比较复杂 需要考虑
1》参数个数 参数多的兼容参数少的 或者说 参数少的可以赋值给多的

   type F1 = (a:number) 	=> void
    type F2 = (a:number,b:number) => void

    let f1 : F1
    let f2 : F2

    // 正确
    f2 = f1
    // 错误示范 不能将类型“F2”分配给类型“F1”。
    f1 = f2

解释:
1. 参数少的可以赋值给参数多的 所以f1可以赋值给f2
2.数组forEach方法的第一个参数是回调函数 该示例中类型为:
(value:string,index:number,array:string[])=>void
3.在JS中省略用不到的函数参数实际上是很常见的 这样的使用方式 促成了TS中函数类型之间的兼容性
4.并且因为回调函数是有类型的 所以 TS会自动推导出参数item index array的类型
const arr = [‘a’,‘b’,‘c’]
arr.forEach(()=>{})
arr.forEach((item)=>{})

2》参数类型

type F1 = (a:number)=>string
type F2 = (a:number)=>string
let f1: F1
let f2: F2 = f1

解释:函数类型F2兼容函数类型F1 因为F1和F2的第一个参数类型相同

参数类型复杂的情况

interface Point2D {x:number ; y:number}
interface Point3D {x:number ; y:number ; z:number}
type F2 = (p: Point2D)=> void
type F3 = (p: Point3D)=> void
let f2:F2
let f3:F3 = f2
f2=f3

解释:
1.此处与前面讲到的接口兼容性冲突
2.技巧:将对象拆开 把每个属性看作一个个参数 则参数少的可以赋值给参数多的
3》返回值类型
只关注返回值类型本身即可

type F5 = () => string
type F6 = () = > string
let f5 : F5
let f6 : F6 = f5

//
type F7 = ()=>{name:string}
type F8 = () =>{name:string ; age:number}
let f7 : F7
let f8 : F8
f7 = f8

解释:
1》如果返回值类型是原始类型 此时两个类型要相同 比如 左侧类型是F5和F6
2》如果返回值类型是对象类型 此时成员多的可以赋值给成员少的 比如 右侧类型F7和F8

交叉类型
类似于之前的接口继承
功能类似于接口继承(extends)用于组合多个类型为一个类型(常用于对象类型)
比如:

interface Person { name: string }
interface Cotact { phone: string }

type PersonDetail = Person & Cotact

let obj: PersonDetail = {
    name: 'Jack',
    phone: '12'
}

console.log(obj)
//{ name: 'Jack', phone: '12' }

解释:
使用交叉类型后 新的类型PersonDetail就同时具备了Person和Contact的所有属性类型
相当于:
type PersonDetail = {name:string; phone:string}


三十四、交叉类型和接口之间的对比

相同点:都可以实现对象类型的组合
不同点:两种方式实现类型组合时 对于同名属性之间 处理类型冲突的方式不同

interface A {
    fn: (value: number) => string
}
interface B extends A {
    fn: (value: string) => string
}

type C = A & B

说明 以上代码 接口继承会报错(类型不兼容)
交叉类型没有错误 可以简单理解为
fn : (value : number } string) => string


三十五、泛型的使用

泛型是可以在保证类型安全前提下 让函数等与多种类型一起工作 从而实现复用 常常用于 函数 接口 class中
需求:创建一个id函数 传入什么数据就返回该数据本身 (也就是说 参数和返回值类型相同)

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

比如 id(10)调用以上函数就会直接返回10本身 但是该函数只接收数值类型 无法用于其他类型
但是也不能够加any 便会失去ts的保护机制

function id<Type>(value:Type):Type {return value}

创建泛型函数:

《1》语法 在函数名称的后面添加<>(尖括号)尖括号中添加类型变量 比如此处的Type
《2》类型变量Type 是一种特殊类型的变量 它处理类型而不是值
《3》该类型变量相当于一个类型容器 能够捕获用户提供的类型(具体是什么类型
由用户调用该函数时指定)
《4》因为Type是类型 因此可以将其作为函数参数和返回值的类型 表示参数和返回值具有相同的类型
《5》类型变量Type 可以是任意合法的变量名称

function id<Type>(value: Type): Type { return value }

const id_1 = id<number>(1)
const id_2 = id<string>('123')

console.log(id_1, id_2)
//1 123

解释:
《1》语法:当函数名称的后面添加<>(尖括号)尖括号中指定具体的类型 比如 此处的number
《2》当传入类型number后 这个类型就会被函数声明时指定的类型变量Type捕获到
《3》此时 Type的类型就是number 所以 函数id参数和返回值的类型也都是number
同样的 如果传入类型string 函数id参数和返回值的类型都是string
这样 通过泛型就做到了让id函数与多种不同的类型一起工作 实现了复用的同时保证了类型安全


三十六、简化泛型函数调用

解释:
《1》在调用泛型函数时,可以省略<类型>来简化泛型函数的调用
《2》此时 TS内部会采用一种叫做类型参数推断的机制 来根据传入的实参自动推断出类型变量Type的类型
《3》比如 传入实参10 TS会自动推断出变量num的类型number 并作为Type的类型

推荐:使用这种简化的方式调用泛型函数 使代码更短 更易于阅读
说明:当编译器无法推断类型或者推断的类型不准确时 就需要显示都传入类型参数


三十七、泛型约束

默认情况下 泛型函数的类型变量Type可以代表多个类型 这导致无法访问任何属性。
比如 id(‘a’)调用函数时获取参数的长度:

function id<Type>(value:Type):Type {
console.log(value.length)
return value
}

解释:
Type可以代表任意类型 无法保证一定存在length属性 比如number类型就没有length。
此时 就需要为泛型添加约束来收缩类型(缩窄类型取值范围)
function id(value: Type): Type {
console.log(value.length)
return value
}

1》指定更加具体的类型

function id<Type>(value: Type[]): Type[] {
    console.log(value.length)
    return value
}
比如 将类型修改为Type[] (Type类型的数组) 因为只要是数组就一定存在length属性
因此便可以访问了
//
function id<Type>(value: Type[]): Type[] {
    console.log(value.length)
    return value
}

console.log(id([1, 2, 3, 4, 5]))

2》添加约束

interface ILength { length: number }

function id<Type extends ILength>(value: Type): Type {
    console.log(value.length)
    return value
}

console.log(id([1, 2, 3, 4, 5]))

解释:
《1》创建描述约束的接口ILength 该接口要求提供length属性
《2》通过extends关键字使用该接口 为泛型(类型变量)添加约束
《3》该约束表示:传入的类型必须具有length属性
注意:传入的实参(比如 数组)只要有length属性即可 这也符合前面讲到的接口的类型兼容属性。


三十八、多个泛型变量的情况

泛型变量可以有多个 并且类型变量之间还可以约束(比如 第二个类型受第一个类型变量的约束)
比如 创建一个函数来获取对象中属性的值:

function getProp<Type, Key extends keyof Type>(obj: Type, Key: Key) {
    return obj[Key]
//返回key中所对应的值
}

let person1 = { name: 'jack', age: 18 }

getProp(person1, 'name')

解释:
《1》添加了第二个类型变量Key 两个类型变量之间使用(,)分隔
《2》keyof关键字接收一个对象类型 生成其键名称 (可能是字符串或数字)的联合类型
《3》本示例中keyof Type实际上获取的是person对象所有键的联合类型 也就是:‘name’|‘age’
《4》类型便可i昂Key受Type约束 可以理解为:Key只能是Type所有键中的任意一个 或者说只能访问对象中存在的属性


三十九、泛型接口

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

interface IdFunc<Type>{
id : (value:Type)=>Type
ids:() = > Type[]
}

let obj : IdFunc<number> = {
id(value) {return value},
id(){return [1,3,5]}
}

解释:
<1>在接口名称的后面添加<类型变量>那么 这个接口就变成了泛型接口
<2>接口的类型变量 对接口中所有其他成员可见 也就是接口中所有成员都可以使用类型变量
<3>使用泛型接口时 需要显示指定具体的类型
<4>此时 id方法的参数和返回值类型都是number ids方法的返回值类型是number[]

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

const strs = ['a', 'b', 'c']
strs.forEach(item => {})
//(method) Array<string>.forEach(callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any): void
const nums = [1, 3, 5]
nums.forEach(item=>{})
//(method) Array<number>.forEach(callbackfn: (value: number, index: number, array: number[]) => void, thisArg?: any): void

解释:
《1》当我们在使用数组时 TS会根据数组的不同类型 来自动将类型变量设置为相应的类型
《2》技巧 可以通过Ctrl + 鼠标左键 来查看具体的类型信息


四十、泛型类(class也可以配合泛型来使用)

比如:React的class组件的基类Component就是泛型类 不同的组件有不同的props和state

解释:
React.Component泛型类两个类型变量 分别指定props和state类型
interface IState { count: number }
interface IProps { maxLength: number }

class InputCpunt extends React.Component<IProps, IState>{
state: IState = {
count: 0
}

render() {
    return <div>{ this.props.maxLength }</div>
}

}

创建泛型类:

class GenericNumber<NumType>{
    defaultValue: NumType | undefined
    add: ((x: NumType, y: NumType) => NumType) | undefined

}

解释:
《1》类似于泛型接口 在class名称后面添加<类型变量>这个类就变成了泛型类
《2》此处的add方法 采用的就是箭头函数形式的类型书写方式
const myNum = new GenericNumber()
console.log(myNum.defaultValue = 10)

省略参数的写法

class GenericNumber<NumType>{
    defaultValue: NumType | undefined
    add: ((x: NumType, y: NumType) => NumType) | undefined

    constructor(value: NumType) {
        this.defaultValue = value
    }
}

const myNum = new GenericNumber(100)
console.log(myNum.defaultValue)

类似于泛型接口 在创建class实例时 在类名后面通过<类型>来指定明确的类型


四十一、泛型工具类型

TS内置了一些常用的工具类型 来简化TS中的一些常见操作
说明:它们都是基于泛型实现的(泛型适用于多种类型 更加通用)并且是内置的 可以直接在代码中使用。
主要学习以下几个

《1》Partial

泛型工具类型 Partial用来构造(创建)一个类型 将Type的所有属性设置为可选。

interface Props {
    id: string
    children: number[]
}

type PartialProps = Partial<Props>

//Partial
type PartialProps = {
    id?: string | undefined;
    children?: number[] | undefined;
}

调用:

let p1: Props = {
    id: "1",
    children: [1, 2, 3, 4]
}

let p2: PartialProps = {
    id: '123'
}

《2》Readonly

用来构造一个类型 将Type的所有属性都设置为readonly(只读)

interface Props {
    id: string
    children: number[]
}

type PartialProps = Readonly<Props>

let p1: PartialProps = {
    id: "1",
    children: [1, 2, 3, 4]
}
//无法分配到 "id" ,因为它是只读属性。
p1.id = '3'

解释:当我们重新给id属性赋值时 就会报错 无法分配到“id”因为它是只读属性

《3》Pick<Type,Keys>

《4》Record<Keys,Type>

type RecordObj = Record<'a' | 'b' | 'c', string[]>

// type RecordObj = {
//     a: string[]
//     b: string[]
//     c: string[]
// }


let obj: RecordObj = {
    a: ['1'],
    b: ['2'],
    c: ['3']
}

console.log(obj)
//{ a: [ '1' ], b: [ '2' ], c: [ '3' ] }

解释:
《1》Record工具类型有两个类型变量
1.表示对象有哪些属性
2.表示对象属性的类型
《2》构建的新对象类型RecordObj表示:这个对象有三个属性分别为a/b/c 属性值的类型都是string[]

四十二、索引签名类型

绝大多数情况下 我们都可以在使用对象前就确定对象的结构 并为对象添加准确的类型
使用场景:当无法确定对象中有哪些属性(或者说对象中可以出现任意多个属性)此时 就用到索引签名类型了

interface AnyObject {
    [key: string]: number
}

let obj: AnyObject = {
    a: 1,
    abc: 124,
    abde: 12345
}
//{ a: 1, abc: 124, abde: 12345 }
console.log(obj)

解释:
《1》使用[key:string]来约束该接口中允许出现的属性名称 表示只要是string类型的属性名称 都可以出现在对象中
《2》这样 对象obj中就可以出现任意多个属性(比如 a b等等)
《3》key 只是一个占位符 可以换成任意合法的变量名称
《4》隐藏的前置知识 JS中对象 ({})的键是string类型的
在js中数组是一类特殊的对象 特殊在数组的键(索引)是数值类型
并且 数组也可以出现任意多个元素 所以在数组对应的泛型接口中 也用到了索引签名类型
解释:
《1》MyArray接口 模拟原生的数组接口 并使用[n:number]来作为索引签名类型。
《2》该索引签名类型表示:只要是number类型的键(索引)都可以出现在数组中 或者说数组中可以有任意多个元素
《3》同时也符合数组索引是number类型这一前提

interface MyArray<T> {
    [n: number]: T
}

let arrs: MyArray<number> = [1, 3, 5]

//[ 1, 3, 5 ]
console.log(arrs)

四十三、映射类型

映射类型:基本旧类型创建新类型 (对象类型),减少重复 提升开发效率
比如:类型PropKeys有x/y/z 另一个类型Type1 中也有x/y/z 并且Type1中x/y/z的类型相同

type PropKeys = 'x' | 'y' | 'z'
type Type1 = {x:number; y:number;z:number}

这样书写没错 但是x/y/z重复书写了两次 像这种情况 就可以使用映射类型来进行简化

type PropKeys = 'x' | 'y' | 'z'
type Type2 = {[Key in PropKeys]:number}

解释:
《1》映射类型是基础索引签名类型的 所以 该语法类似于索引签名类型 也使用了[]
《2》Key in PropKeys 表示Key可以是PropKeys联合类型中的任意一个 类似于forin(let k in obj)
《3》使用映射类型创建的新对象类型Type2和类型Type1结构完全相同
《4》注意:映射类型只能在类型别名中使用 不能在接口中使用

type PropKeys = 'x' | 'y' | 'z'
type Type1 = { x: number; y: number; z: number }
type Type2 = { [Key in PropKeys]: number }

let obj1: Type1 = {
    x: 0,
    y: 0,
    z: 0
}

let obj2: Type2 = {
    x: 0,
    y: 0,
    z: 0
}

console.log(obj1)
console.log(obj2)
//
{ x: 0, y: 0, z: 0 }
{ x: 0, y: 0, z: 0 }


四十四、映射类型

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

type Props = {a:number;b:string;c:boolean}
type Type3 = {[Key in keyof Props]:number}
let obj: Type3 = {
    a: 0,
    b: 0,
    c: 0
}

console.log(obj)
//{ a: 0, b: 0, c: 0 }

使用[]来访问对象的属性 P是对象中的键
获取T对象中键为P的值

解释:
《1》keyof T 即keyof Props表示获取Props的所有键 也就是:‘a’|‘b’|‘c’
《2》在[]后面添加?(问号)表示将这些属性变为可选的 以此来实现Partial的功能
《3》冒号后面的T[P]表示获取T中每个键对应的类型 比如 如果是’a’则类型是number 如果是’b’ 则类型是string
《4》最终 新类型PartialProps和旧类型Props结构完全相同 只是让所有类型都变为可选了


四十五、索引查询类型

刚刚用到的T[P]语法 在TS中叫做索引查询(访问)类型
作用:用来查询属性的类型

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

解释:
《1》Props[‘a’]表示查询类型Props中属性’a’对应的类型number 所以 TypeA的类型为number
《2》[] 中的属性必须存在于被查询类型中 否则就会报错


四十六、索引查询类型(同时查询多个)

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

解释:
《1》使用字符串字面量的联合类型 获取属性a和b对应的类型 结果为string | number
《2》使用keyof操作符获取Props中所有键对应的类型 结果为string | number | boolean


四十七、第三方库

类型声明文件:用来为已存在的JS库提供类型信息
《1》TS的两种文件类型
《2》类型声明文件的使用说明


四十八、TS中的两种文件类型

1.ts文件 2 .d.ts文件

.ts文件:
《1》既包含类型信息又包含可执行代码
《2》可以被编译为.js文件 然后 执行代码
《3》用途:编写程序代码的地方

.d.ts文件:
《1》只包含类型信息的类型声明文件
《2》不会生成.js代码 仅用于提供类型信息
《3》用途:为JS提供类型信息
总结:
.ts是implementation(代码实现文件)
.d.ts是declaration(类型声明文件)
如果要为JS提供类型信息 要使用.d.ts文件

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

《1》使用已有的类型声明文件
《2》创建自己的类型声明文件

学习顺序:先会用(别人的)再会写(自己的)

《1》使用已有的类型声明文件
*1.内置类型声明文件
TS为JS运行时可用的所有标准化内置API都提供了声明文件。
比如 在使用数组时 数组所有方法都会有相应的代码提示以及类型信息

(method) Array<number>.forEach(callbackfn: (value: number, index: number, array: number[]) 
=> void, thisArg?: any): void

实际上这都是TS提供的内置类型声明文件
可以通过Ctrl + 鼠标左键来查看内置类型声明文件内容
比如 查看forEach方法的类型声明 在VSCode中会自动跳转到lib.es5.d.ts类型声明文件中
当然 像window document等BOM DOM API也都有相应的类型声明(lib.dom.d.ts)
2.第三方库的类型声明文件
《1》库自带类型声明文件
比如 axios
解释:在这种情况下 正常导入该库 TS就会自动加载自己的类型声明文件 以提供该库的类型声明
《2》由DefinitelyTyped提供
这是一个github仓库 用来提供高质量TypeScript类型声明
可以通过npm/yarn来下载该仓库提供的TS类型声明包 这些包的名称格式为:@types/

比如 @types/react @types/lodash
说明:在实际开发时 如果你使用的第三方库没有自带的声明文件 VSCode会给出明确的提示


解释:
《1》当安装@types/*类型声明后 TS也会自动加载该类声明包 以提供该库的类型声明
《2》补充:TS官方提供了一个页面 可以来查询@types/*库

第一步
在该链接中找到命令:
https://www.typescriptlang.org/dt/search?search=lodash

npm i lodash
npm i @types/lodash --save-dev

第一个是下载库的
第二个是下载声明文件的

在终端仅执行第一个指令后 在index.ts文件中引入
import _ from 'lodash'
报错:
无法找到模块“lodash”的声明文件。“D:/codeFiles/VsCodeProjects/vscode-typescript/node_modules/lodash/lodash.js”隐式拥有 "any" 类型。
  尝试使用 `npm i --save-dev @types/lodash` (如果存在),或者添加一个包含 `declare module 'lodash';` 的新声明(.d.ts)文件ts(7016)

其它范例:
npm i react
npm i @types/react --save-dev

《2》创建自己的类型声明文件

  1. 项目内共享类型

  2. 为已有JS文件提供类型声明

  3. 项目内共享类型

如果多个.ts文件中都用到同一个类型 此时可以创建.d.ts文件提供该类型 实现类型共享
操作步骤:
《1》创建index.d.ts 类型声明文件
《2》创建需要共享的类型 并使用export导出(TS中的类型也可以使用import/export实现模块化功能)
《3》在需要使用共享类型的.ts文件中 通过import导入即可(.d.ts后缀导入时 直接省略)

2.为已有JS文件提供类型声明

2.1 在将JS项目迁移到TS项目时 为了让已有的.js文件有类型声明。
2.2 成为库作者 创建库给其他人使用

注意: 类型声明文件的编写与模块化方式相关 不同的模块化方式有不同的写法 但由于历史原因 JS模块化的发展经历过多种变化(AMD CommonJS UMD ESModule等) 而TS支持各种模块化形式的类型声明 这就导致类型声明文件相关内容又多又杂

演示:
基于最新的ESModule (import/export)来为已有.js文件 创建类型声明文件。
开发环境准备:使用webpack搭建 通过ts-loader处理.ts文件

说明:TS项目中也可以使用.js文件
说明:在导入.js文件时 TS会自动加载与.js同名的.d.ts文件 以提供类型声明
declare关键字:用于类型声明 为其它地方(比如.js文件)已存在的变量声明类型 而不是创建一个新的变量
《1》对于type interface 等这些明确就是TS类型的(只能在TS中使用的)可以省略declare关键字
《2》对于let function 等具有双重含义(在JS TS中都能用)应该使用declare关键字 明确指出此处使用declare关键字 明确指定此处用于类型声明


四十九、在React中使用TypeScript概述

在前端项目开发中使用TS 还需要掌握React Vue Angular等这些库或框架中提供的API的类型 以及在TS中是如何使用的

接下来 以React为例 来学习如何在React项目中使用TS 包括以下内容:

《1》使用CRA创建支持TS的项目

React脚手架工具create-react-app(简称:CRA)默认支持TypeScript
创建支持TS的项目命令:npx create-react-app 项目名称 --template typescript
当看到以下提示时 表示支持TS的项目创建成功
We suggest that you begin by typing:
cd react-ts-basic
npm start


五十、React支持TS的项目目录结构

相对于非TS项目 目录结构主要由以下三个变化
1.项目根目录中增加了tsconfig.json配置文件 指定TS的编译选项 (比如 编译时 是否移除注释)
2.React组件的文件扩展名变为:*.tsx
3.src目录中增加了react-app-env.d.ts:React项目默认的类型声明文件。

react-app-env.d.ts:React项目默认的类型声明文件
三斜线指令 : 指定依赖的其它类型声明文件 types表示依赖的类型声明文件包的名称
/// <reference types="react-scripts" />
解释:
告诉TS帮我加载react-scripts这个包提供的类型声明。
react-scripts的类型声明文件包含了两部分类型
《1》react react-dom node的类型
《2》图片 样式等模块的类型 以允许在代码中导入图片 SVG等文件。

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


五十一、TS配置文件tsconfig.json

指定项目文件和项目编译所需的配置项
注意:TS的配置项非常多(100+)以CRA项目中的配置为例来学习 其它的配置项用到时查文档即可
《1》tsconfig.json文件所在目录为项目根目录(与package.json同级)
《2》tsconfig.json 可以自动生成 命令 tsc-init


五十二、通过命令行方式使用编译配置

使用演示:tsc hello.ts --target es6
注意:
1. tsc 后带有输入文件时(比如 tsc hello.ts)将忽略tsconfig.json
2. tsc 后不带输入文件时 (比如 tsc)才会启用tsconfig.json
推荐使用 : tsconfig.json 配置文件


五十三、React中的常用类型

前提说明:现在 基于class组件来讲解React+TS的使用 (最新的React Hooks 在后面讲解)
在不使用TS时 可以使用prop-types库 为React组件提供类型检查
说明:TS项目中 推荐使用TypeScript实现组件类型校验(代替PropTypes)
不管是React还是Vue 只要是支持TS的库 都提供了很多类型 来满足该库对类型的需求
注意:
1.React项目是通过 @types/react @types/react-dom 类型声明包 来提供类型的
2.这些包 CRA已帮我们安装好(react-app-env.d.ts)直接用即可


五十四、React函数组件的类型(1组件和属性类型)

React是组件化开发模式 React开发主要任务就是写组件 
两种组件:
《1》函数组件
1.组件的类型
2.组件的属性(props)
3.组件属性的默认值(defaultProps)
4.事件绑定和事件对象

type Props = {name:string;age?:number}
const Hello:FC <Props> = ({name,age})=>(
<div>你好,我叫:{name},{age} 岁了</div>
)
<Hello name="jack"/>
实际上 还可以直接简化为 (完全按照函数在TS中的写法)
const Hello = ({name,age}: Props) = >(
<div>你好,我叫:{name},{age} 岁了</div>
)


五十五、React函数组件的类型(2属性默认值)

函数组件属性的默认值(defaultProps)
const Hello:FC<Props> = ({name,age}) =>(
<div>你好,我叫:{name},我{age}岁了</div>
)
Hello.defaultProps = {
age: 18
}
实际上 还可以直接简化为(完全按照函数在TS中的写法)
const Hello = ({name,age=18}:Props) =>(
<div>你好,我叫:{name},我{age}岁了</div>
)


五十六、React函数组件的类型(3事件和事件对象)

事件绑定和事件对象
<button onClick={onClick}>点赞</button>
const onClick = () =>{}
const onClick1=(e:React.MouseEvent<HTMLButtonElement>) =>{}
再比如 文本框
<input onChange={onChange}/>
const onChange = (e:React.ChangeEvent<HTMLInputElement>) =>{}
技巧:在JSX中写事件处理程序(e=>{})然后 把鼠标放在e上 利用TS的类型推论来查看事件对象类型
<input onChange={e=>{}}>


五十七、React类组件的类型(1组件类型)

2》class组件 主要包含以下内容:
1.组件的类型 属性 事件
type State = {count:number}
type Props ={message?:string}

class C1 extends React.Component {}  //无props state
class C2 extends React.Component<Props> {} //有props 无state
class C3 extends React.Component <{},State>{}//无props 有state
class C4 extends React.Component <Props,State>{}//有props state

2.组件状态(state)


五十八、React类组件的类型(2组件属性)

class组件的属性和属性默认值
type Props = {name:string;age?:number}
class Hello extends React.Component<Props>{
   static defaultProps:Partial<Props> = {
	age:18
}

render(){
	const {name,age} = this.props
	return <div>你好,我叫:{name},我{age}岁了</div>
    }
}

const {name,age=18} = this.props

//解构
const {name,age} = this.props

五十九、React类组件的类型(3状态和事件)

class组件状态(state)和事件
type State = {count:number}
class Counter extends React.Component<{},State>{
	state:State = {
	count : 0
}
onIncrement=() =>{
	this.setState({
	count:this.state.count+1
})
}
}

<button onClick={this.onIncrement}>+1</button>

六十、任务列表案例介绍

TS+React实现todos案例
功能演示:
《1》展示任务列表
《2》添加任务

父组件:App
子组件:TodoAdd TodoList

展示任务列表:
思路:使用状态提升(为父组件提供状态 通过props传递给子组件)来实现父->子通讯

步骤:
《1》为父组件App 提供状态(任务列表数据)和类型
《2》为子组件TodoList指定能够接收到的props类型
《3》将任务列表数据传递给TodoList组件

//后面没必要看了

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

猿代码_QQ_3014067949

给小编价格鸡腿吧~

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

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

打赏作者

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

抵扣说明:

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

余额充值