目录
前言:学完了立超老师的typescript,立超老师确实讲的不错,但是最近做了一个ts+hooks的项目的时候,感觉ts里还有很多泛型等相关的知识立超老师没有深入去讲,然后又去看了黑马的,所以在此结合两个视频的内容,做了相关笔记内容,希望对大家有帮助。
1.Typescript前置介绍
1.1Typescript是什么?
- Typescript是javascript的超集(JS有的TS都有,相当于一个包含关系)
1.2TS在JS上增加了什么?
- Typescript=Type+Javascript(可以这么理解,在JS的基础上,为其添加了类型支持)还增添了很多其他的功能。
1.3Typescript为什么优于Javascript?
js中不会检查变量的类型是否会发生变化,从而会导致很多bug,但是Ts会检查,消除了开发中的许多常见的bug。
2.Typescript环境搭建
不论是node环境还是浏览器环境只认识js,不认识ts。所以我们要将ts编译成js,才能运行。所以我们就要借用typescript包中的tsc命令将其转化为js
2.1安装typescript包
- 全局安装typescript
npm install typescript -g
- 判断是否安装成功
tsc -v
没有出现“xxx”不是内部或外部命令,也不是可运行的程序或批处理文件就证明没有出错
2.2编译ts文件
- 编译ts文件
1.创建test.ts文件
console.log("hello")
2.在终端中运用tsc命令将test.ts文件编译为test.js文件
tsc test.ts
2.3简化TS运行
方式一:
tsc test.ts --watch
会监视到你每次修改ts文件,一旦修改则运行 tsc test.ts生成对应的js文件
方式二:
安装ts-node包(检测是否安装成功同上安装typescript),提供ts-node命令,相当于在node环境里运行ts,但是其内部原理也是在内部转换成js然后再运行
- 全局安装ts-node
npm install ts-node -g
- 运行对应ts文件
ts-node test.ts
3.Typescript的常用类型
3.1类型注解
let a:number
a=18
let b:string="abc"
:number就是类型注解,用来给变量添加类型约束,类型约束就是约定了是什么类型,就只能给该变量赋值什么类型
3.2类型分类
js已有类型
- (原始类型)基本数据类型:number,string,boolean,undefined,null,symbol
- (对象类型)引用数据类型:object(对象,数组,函数,类等对象)
ts新增类型
- 联合类型,自定义类型(类型别名),接口,元祖,字面量类型,枚举,void,any等
3.3原始类型
1.number
let a:number=19
2.string
let a:string="abc"
3.boolean
let a:boolean=true
4.undefined
let a:undefined=undefined
5.null
let a:null=null
6.symbol
let a:symbol=Symbol()
3.4数组类型
数组类型的两种写法
写法一:类型[]
let c:number[]
c=[1,2]
写法一:Array<类型>
let d:Array<string>
d=['q','e']
3.5联合类型
竖线(|)在Ts中表示联合类型,由多个其他类型组成的类型,表示可以是这些类型中的任意一种
需求:如何让一个ts数组里面即有number类型又有string类型的数组呢?就要通过联合类型来实现。
let e:(number|string)[]
e=[1,"r"]
3.6类型别名
类型别名(自定义类型):为任何类型起别名,简化该类型的应用。
- 创建类型别名:type 自定义类型名=
- 使用:直接使用该类型别名作为变量的类型注解即可
type f=(number|string)[]//小括号一定要加
let g:f
g=[1,"c"]
3.7函数类型
函数的类型其实是指函数的参数和函数的返回值的类型
3.7.1单独指定参数和返回值类型
- 函数表达式和函数声明式是一样的
//函数声明式
function fun(value:number,value1:number):number{
return value+value1
}
//函数表达式
const fun1=(value:number,value1:number):number=>{
return value+value1
}
3.7.2同时指定参数和返回值类型
- 只适用于函数表达式(也可以用在后面的对象属性为方法的定义上)
fun2:(value:number,value1:number)=>number
const fun2:(value:number,value1:number)=>number=(value,value1)=>{
return value+value1
}
3.7.3函数返回值类型
void类型
- 表示没有返回值,或者返回值为undefined
function fun3(value:number,value1:number):void{
console.log(123)
}
不指定返回值类型的时候默认就是void类型
never类型
- 连undefined和null都不能有,一般用在函数内报错
function fun3(value:number,value1:number):never{
throw('123')
}
3.7.4函数可选参数
需求:函数中有的参数可以传有的可以不传,就要用到可选参数
- 用法:形参名?:类型
function fun4(name:string,age?:number){
console.log(name,age)
}
fun4("cx")//cx
fun4("cx",12)//cx 12
注意可选参数只能出现在参数列表的最后
3.8对象类型
3.8.1对象类型表示
- 用法:使用{}描述对象的结构,属性采用属性名:类型的形式,方法采用方法名(形参名:类型):返回值类型的形式或者方法名:(形参名:类型)=>返回值类型。
let obj:{name:string;sayName(name:string):void}
obj={
name:"cx",
sayName:(name)=>{
console.log(name)
}
}
obj.sayName("ct")
对象类型中的通过分好来分割属性。如果一行只指定一个属性类型,可以通过换行来代替分号
3.8.2对象的可选属性(跟函数可选参数语法一样)
用法:属性?:类型
let config:{url:string;method?:string}
config={
url:"http://localhost:3000",
}
3.9接口类型
当一个对象类型被多次引用时,一般会使用接口(interface)来描述对象的类型,达到复用的目的。
用法:interface 自定义接口名 对象类型表示
interface config{url:string;method?:string}
let config1:config={
url:"http://localhost:3000",
}
let config2:config={
url:"http://localhost:3000",
method:"POST"
}
可以通过接口获取接口内某一属性的值嘛?可以,通过接口["属性名"]来获取
3.9.1接口类型和类型别名的区别
相同点:都可以给对象指定类型
不同点:interface只能为对象指定类型
类型别名,不仅可以为对象指定类型,还可以为任意类型指定别名,同样也可以为接口指定类型,就是给接口起别名。
3.9.2接口类型的继承(extends)
如果两个接口有相同的属性或方法,可以将公共的属性或者方法抽离出来,通过继承来实现复用。
interface point2D{x:number;y:number}
//interface ponint3D{x:number;y:number;z:number}
interface point3D extends point2D{
z:number
}
let p3:point3D={
x:1,
y:2,
z:3
}
接口的继承叫继承,泛型的继承就叫约束了。
3.10元祖
元组是特殊的数组,它确切的知道包含多少个元素,以及特定索引对应的类型。
let position:[number,number]=[39.5427,116.2317]
3.11类型推断
常用的地方:
- 1声明变量并初始化
- 2.巨鼎函数返回值时
上面这两种情况下可以免去类型注解,还有其他的地方后面会讲
let age=19
3.12类型断言
有时候类型太宽泛了就无法操作具体类型身上的方法和属性了,所以这个时候就要用类型断言指定更加具体的类型。
如getElementById方法返回值的类型是HTMLElement,该类型只包含所有标签公共的属性或方法,不包含a标签特有的href属性。
当你比Ts更加明确一个值的类型,这个时候可以使用类型断言来指定更加具体的类型。
as +更加具体的类型
//方式一
const aLink=document.getElementById("link") as HTMLAnchorElement
//方式二(不常用,因为在React中与jsx语法冲突)
const aLink=<HTMLAnchorElement>document.getElementById("link")
HTMLAnchorElement是HTMLElement的子类型,可以在浏览器Elements界面选中a标签,然后在console界面通过console.dir($0)打印这个对象,在最后的prototype属性上就可以看到其具体类型。
3.13字面量类型
let str1="Hello TS"
const str2="Hello TS"
第一个类型ts会类型推论出来是字符串,它的值是任意值,第二个表达式类型就是"Hello TS",因为const定义的,所以是常量,不能随意改变,所以它的类型就是"Hello TS"。任意的JS字面量,比如对象,数字等都可以作为类型使用。
let str1 = "Hello TS"
const str2 = "Hello TS"
const str3: "Hello TS" = "Hello TS"
const n1 = 123
let n2: 123 = 123
使用场景,字面量类型配合联合类型一起使用,用来表示一组明确的可选值列表。如下:
function changeDirection(direction:"up"|"down"|"left"|"right"){
console.log(direction)
}
方向只能选上下左右,使用字面量类型更加精确严禁。
3.14枚举类型
枚举的功能类似于字面量类型+联合类型组合功能,也可以表示一组明确的可选值。
3.14.1枚举
定义:
- 用enum定义一组命名常量
- 约定枚举名称以及枚举中的值以大写开头
- 枚举值以逗号分隔
- 用枚举名称做类型注解
enum Direction{
Up,
Down,
Left,
Right
}
function changeDirection1(direction:Direction){
console.log(direction)
}
//访问 类似于JS的对象,通过.语法访问枚举的成员
changeDirection1(Direction.Up)
3.14.2枚举的值
枚举成员是有值的,默认是从0开始自增,党员也可以给枚举中的成员初始化值
enum Direction{
Up=10,
Down,
Left,
Right
}
enum Direction{
Up=6,
Down=8,
Left=10,
Right=19
}
3.14.3字符串枚举
枚举成员的值都是字符串就是字符串枚举。
因为字符串没有自增长行为,所以每一个枚举成员都要设置对应的值。
enum Direction{
Up="Up",
Down="Down",
Left="Left",
Right="Right"
}
3.14.4枚举的特点和原理
枚举不仅用作类型,还提供值,也就是说其他类型在编译时会自动移除,而枚举类型会被编译为如下js代码。
var Direction
(function(Direction){
Direction["Up"] = "Up",
Direction["Down"] = "Down",
Direction["Left"] = "Left",
Direction["Right"] = "Right"
})(Direction||Direction={})
//也可以通过在ts中打印Direction查看这个对象
一般情况下推荐使用字面量类I型那个+联合类型组合的方式。因为比枚举更加直观。
3.15any类型
不推荐使用any类型,使用any类型就会失去了TS的类型保护。
当值的类型为any时,可以对该值进行任意操作,并且不会有代码提示。如下:
- 访问不存在的属性或者赋值
- 当做函数调用(会报错,但是是运行时报错)
- 赋值给其他类型的的变量
let obj1:any={x:0}
obj1.name
obj1.age=19
obj1()
let n:number=obj1
隐式any类型:1.声明变量时不提供类型也不提供默认值2.函数参数不加类型。因为不推荐使用any,所以这两种情况下要提供类型。
使用any的情况:当类型过长,过于复杂时,先零时使用any,然后之后再来对将类型换成具体的。
3.16typeof运算符
在Ts中既可以使用原来的js提供的typeof,也可以在类型上下文(类型注解的位置,相当于冒号之后)中引用变量或者类型的属性(类型查询,只能查询变量或者属性的类型,其他形式的类型无法查询,如函数调用。
console.log(typeof "abc")
let p={x:11,y:23}
function formatPoint(point:typeof p):number{
return point.x+point.y
}
let p1:typeof p.x
4.Typescript的高级类型
class类
类型兼容性
交叉类型
泛型和keyof
索引签名类型和索引查询类型
映射类型
4.1class类
Ts全面支持ES6中的class,并增强了ES6中的Class,作为一种类型存在,为其添加了类型注解和可见性修饰符等其他语法。
4.1.1class类的基本使用
class Person{
//属性也可以在这初始化,就是默认值
name:string
gender:string
age=13
// 构造函数不能写返回值类型,且参数要显示指明类型,不然就为any了。
constructor(name:string,gender:string){
this.name=name
this.gender=gender
}
// 跟对象里面的方法写法一样
sayName(n:number):void{
console.log(this.name)
console.log(n)
}
}
const p2=new Person("ct","male")
console.log(p2)
p2.sayName(2)
4.1.2class类的继承extends
通过继承,就可以拥有父类的属性和方法了,在继承方面跟js里面的clas一样
class Anmial{
move():void{
console.log("I can move.")
}
}
class Dog extends Anmial{
name="二哈"
bark():void{
console.log("I can Bark!")
}
}
const dog=new Dog()
4.1.3class类的继承implements
实现接口,js中没有implements,实现是类和接口的关系,也就是接口是规则,然后用该接口实现的类中必须要有接口中定义的属性和规则。
interface Plant{
watered():void
name:string
}
class Tree implements Plant{
name:"松树"
watered(){
console.log("I need some water!")
}
}
const t1=new Tree()
4.1.4class类中成员的可见性
通过可见性修饰符来控制类中的属性或者方法对于class外的代码是否可见。
public:类和子类,以及两者的实例中都可以访问到。(默认不写就是public)
protected:类和子类中可以看的到,就是说在类中和子类中可以通过this访问的到,但是在两者的实例中都是访问不到的。
private:在类中可以访问的到(也就是在类中可以通过this访问到,而且在protected方法中也可以通过this访问到),在子类和两者的实例中都是访问不到的。
4.1.4class中的readonly修饰符
用来防止在构造函数之外对属性进行赋值,但是默认的值可以在构造函数中进行修改,在别的方法里面都不能修改。
而且readonly只能修饰属性,不能修饰方法。
在readonly后面的属性类型注解如果没有写,就变成了一个字面量类型,就变成了一个跟常量类型的值,所以此时在构造函数里面也不能修改。故用readonly属性有默认值时,要手动指定类型。
readonly不仅可以用在class中,也可以用在interface中和{}对象中。
4.2类型兼容性
4.2.1class和interface的兼容性
class和interface兼容性规则其实是一样的。
两种类型系统:
结构化类型系统:也叫鸭子类型,如Ts所采用的。类型检查观察的是值所具有的形状,也就是如果两个对象类型形状相同,则认为它们属于同一类型。更加精确的说,成员多的可以赋值给成员少的,成员多的兼容成员少的。
标明类型系统:如java,c#所采用的。简单理解就是两对象要求他们连名字都要一样。类名不同的类则认为它们是不同的类。
class Point1{x:number;y:number}
class Point2D1{x:number;y:number}
class Point3D1{x:number;y:number;z:number}
// Point3D1可以赋值给Point
const p4:Point=new Point3D1()
// Point3D1可以赋值给Point2D
const p5:Point2D=new Point3D1()
interface Point{x:number;y:number}
interface Point2D{x:number;y:number}
interface Point3D{x:number;y:number;z:number}
let p6:Point
let p7:Point2D
let p8:Point3D
p6=p7
p6=p8
p7=p8
class和interface之间也是相互兼容的,因为他们都是对对象进行限制。
const p9:Point2D=new Point3D1()
4.2.2函数之间的兼容性
函数之间的兼容性比上面interface和class的兼容性要复杂,因为它要考虑如下三个因素。
- 参数个数
- 参数少的可以赋值给参数少的(也就是是参数多的兼容参数少的),跟对象的兼容性是相反的。我们的forEach可以传入0个1个2个3个都可以,省略用不到的参数这样js中很常见,所以促成了ts中函数类型之间的兼容性。
- 参数类型
- 原始类型:相同位置的参数要相同
- 对象类型:相同位置的参数要兼容。而且为了满足函数参数少的可以赋值给多的这一特点,我们要从函数兼容性的特点考虑,我们可以考虑可以将对象拆开,就是把接口中的属性看成一个一个的属性。所以这个地方的对象类型是少的可以赋值给多的。如数组的大多数回调函数都存在这种函数之间的兼容性。函数之间的兼容性和函数调用没有关系,只是说我定义一个函数变量类型,用这个变量类型的变量可以接受参数比其少的函数。函数的参数有多少个就要传多少个,跟函数之间的兼容性和函数调用没有任何关系。但是函数参数如果是对象(不论是类还是接口都是为对象服务)的话调用的时候会设计到对象类型的兼容性,所以对象类型属性多的可以赋值给对象类型属性少的,但是如果传的是字面量的话就要一模一样,函数式组件其实就可以看成是函数类型的兼容,所以函数式组件传递参数的时候对象属性少的可以传给对象属性多的。
-
type Ab=(value:string,name:string)=>void function callback(value:string):void{ console.log("这里是函数的兼容性测试"+value) } function funccccc(callback1:Ab){ callback1('12','13') } funccccc(callback)
- 返回值类型
- 只关注返回值类型本身,对于原始类型,则要相同
- 如果返回值时对象类型,则按照对象自身类型兼容性的特点,可以属性多的可以赋值给属性少的。要与函数参数中对象类型的兼容性区分开。
4.3交叉类型
4.3.1交叉类型的基本使用
功能类似于接口继承(extends),用于组合多个类型为一个类型(常用于对象类型)
//对象同时具有Type1和Type2的所有属性方法
interface Type1{name:string;say():void}
interface Type2{gender:string}
type Type3=Type1&Type2
const obj2:Type3={name:"ct",gender:"male",say(){}}
4.3.1交叉类型与接口继承的对比
相同点:都可以实现对象类型的组合
不同点:两种方式实现类型组合,对于同名属性,处理类型冲突的方式不同
接口继承会报错,也就是两个函数不兼容
interface A {
fn:(value:number)=>string
}
interface B extends A {
fn:(value:string)=>string
}
交叉类型不会报错,相当于函数的重载。可以理解为fn:(value:string|number)=>string
interface A {
fn:(value:number)=>string
}
interface B {
fn:(value:string)=>string
}
type C=A&B
let Fun:C
Fun.fn(1)
Fun.fn("a")
但是也不是说接口继承比交叉类型弱,各自有各自对应的场景。简单的用交叉类型,如果交叉类型实现不了,我们可以想一想可不可以用接口继承来实现。
非常感谢您的阅读,欢迎大家提出您的意见,指出相关错误,谢谢!越努力,越幸运!