哔哩哔哩课程链接: https://www.bilibili.com/video/BV14Z4y1u7pi?p=89&vd_source=92ee1c593b77f3dfb92819ea75999078
TypeScript是JavaScript的超级 在JS的基础上增加了类型支持
添加类型支持的好处:提高开发效率 减少找Bug的时间
TS的优势:之一 类型推断机制 不需要在每个地方都显示标注类型
安装环境 浏览器/node 只认识js代码 不认识ts代码 需要先将ts——>js 才能运行
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-node包 直接在node.js中执行ts代码
安装命令:npm i -g ts-node
使用方式:ts-node hello.ts
分析:ts-node命令在内部已经将TS->JS 再运行js代码
number、string、boolean、null、undefined、symbol
为变量约定了类型 传值不符合规范会报错
JS已有的类型:
原始类型:number/string/boolean/null/undefined/symbol
对象类型:object (数组 对象 函数)
TS新增类型
联合类型 自定义类型(类型别名) 接口 元组 字面量类型 枚举 void any等等
需求 数组中既有number类型 又有string类型
类型别名(自定义类型):为任意类型起别名
使用场景:当同一类型(复杂)被多次使用时 可以通过类型别名 简化该类型的使用
type CustomArray = (number | string)[ ]
let arr1: CustomArray = [1,'a',3,'b']
let arr2: CustomArray = ['x','y',6,7]
解释:
《1》使用type关键字来创建类型别名
《2》类型别名 比如 此处的CustomArray 可以是任意合法的变量名称
《3》创建类型别名后 直接使用该类型别名作为变量的类型注解即可
两种方式:
function add(num1:number,num2:number):number{
return num1+num2
}
const add = (num1:number,num2:number):number=>{
return num1+num2
}
const add = () =>{} //箭头函数
const cat: (num1: number, num2: number) => number = (num1, num2) => {
return num1 - num2
}
console.log(cat(2, 1))
函数没有返回值 返回值类型为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 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 这会让TypeScript变为 AnyScript 失去TS类型保护的优势
因为当值的类型为any时 可以对该值进行任意操作 并且不会有代码提示
解释:
《1》以上操作都不会有任何类型错误提示,即使可能存在错误
《2》其它 隐式具有any类型的情况
1.声明变量不提供类型也不提供默认值
2.函数参数不加类型
用来在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全面支持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类型)
class Person{
age : number
gender : string
constructor(age:number,gender:string){
this.age = age
this.gender = gender
}
}
解释:
《1》成员初始化(比如 age:number)后 才可以通过this.age来访问实例成员。
《2》需要为构造函数指定类型注解 否则会被隐式推断为any 构造函数不需要返回值类型
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)
解释:方法的类型注解(参数和返回值)与函数用法相同
类继承的两种方式
《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的所有属性和方法
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接口中指定的所有方法和属性。
类成员可见性:可以使用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中的其他类型也存在相互兼容的情况
包括:
接口之间的兼容性 类似于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()
函数之间的兼容性比较复杂 需要考虑
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.forEach(callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any): void
const nums = [1, 3, 5]
nums.forEach(item=>{})
//(method) Array.forEach(callbackfn: (value: number, index: number, array: number[]) => void, thisArg?: any): void
解释:
《1》当我们在使用数组时 TS会根据数组的不同类型 来自动将类型变量设置为相应的类型
《2》技巧 可以通过Ctrl + 鼠标左键 来查看具体的类型信息
比如:React的class组件的基类Component就是泛型类 不同的组件有不同的props和state
解释:
React.Component泛型类两个类型变量 分别指定props和state类型
interface IState { count: number }
interface IProps { maxLength: number }
class InputCpunt extends React.Component
state: IState = {
count: 0
}
render() {
return { this.props.maxLength }
}
}
创建泛型类:
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中的一些常见操作
说明:它们都是基于泛型实现的(泛型适用于多种类型 更加通用)并且是内置的 可以直接在代码中使用。
主要学习以下几个
泛型工具类型 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'
}
用来构造一个类型 将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”因为它是只读属性
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》类型声明文件的使用说明
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》创建自己的类型声明文件
项目内共享类型
为已有JS文件提供类型声明
项目内共享类型
如果多个.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关键字 明确指定此处用于类型声明
在前端项目开发中使用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
相对于非TS项目 目录结构主要由以下三个变化
1.项目根目录中增加了tsconfig.json配置文件 指定TS的编译选项 (比如 编译时 是否移除注释)
2.React组件的文件扩展名变为:*.tsx
3.src目录中增加了react-app-env.d.ts:React项目默认的类型声明文件。
react-app-env.d.ts:React项目默认的类型声明文件
三斜线指令 : 指定依赖的其它类型声明文件 types表示依赖的类型声明文件包的名称
///
解释:
告诉TS帮我加载react-scripts这个包提供的类型声明。
react-scripts的类型声明文件包含了两部分类型
《1》react react-dom node的类型
《2》图片 样式等模块的类型 以允许在代码中导入图片 SVG等文件。
TS会自动加载该.d.ts文件 以提供类型声明(通过修改tsconfig.json中的include配置来验证)
指定项目文件和项目编译所需的配置项
注意: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 配置文件
前提说明:现在 基于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是组件化开发模式 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>
)
函数组件属性的默认值(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>
)
事件绑定和事件对象
<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=>{}}>
《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)
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
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组件
//后面没必要看了