TS是TypeScript的缩写,是JavaScript的超集 (JS有的TS都有),也可以说TS是 type + js,加了一个类型。比如:JS:
let a = 0
,TS:let a:number=0
为什么需要给JS添加type呢?因为js的类型系统存在“先天缺陷”,JS代码中绝大部分的错误都是类型错误,那么就会存在一个问题,那就是找bug、改bug的时间增加,严重影响了开发效率。
从编程语言的动静来区分;TS属于静态类型的编程语言,JS属于动态的编程语言。
TS相对于JS的优势:
let obj :(name:string;age:number;sayWorld(a:string):void)= {name:'你好',age:22,sayWorld(a){}}
,type stateArray = ( number | string )[]
let a: stateArray =[1,2,'a']
let b: stateArray =[3,4,'a',5]
//1、单独制定参数、返回值类型
function fn(num1:number,num2:number):number{
retuen num1+num2
}
const fun = (num1:number,num2:number):number=>{
retuen num1+num2
}
//2、同时指定参数和返回值的类型
const fun1:(num1:number,num2:number)=>number = (num1,num2)=>{
reurn num1+num2
}
//可选参数 没有返回值
const fun = (num1:number,num2?:number):void=>{
}
interface state {
name:string;
age:number;
sayHi():void
number?:number;
}
let obj:state ={
name:'你好',
age:'22',
sayHi(){},
}
我们发现 type和interface的用法和写法非常像,其实两者是有区别的,
interface只能为对象指定类型
type不仅可以为对象指定类型,实际上可以为任何类型指定类型。
interface state2D{x: number; y: number}
interface state3D extends state2D{z:number}
let d3: state3D ={
x:1,
y:2,
z=3
}
元组类型是另一种类型的数组,他知道数组中到底有多少个元素,以及特定索引对应的类型
let array:[number,number]=[1,2]
在TS中,某些没有明确指出类型的地方,TS中的类型推论机制会帮助提供类型。也就是有些地方可以省略不写!
发生类型推论的常见地方有两个:1、声明变量并初始化的时候 2、决定函数返回值
在实际开发中,能省则省
//1\声明变量并初始化的时候
let num = 10 //number类型
let str = 'string'//string类型
num = '10'//报错
//2、决定函数返回值
function add(num1:number,num:number){ //类型推断是 number
return num1 + num2
}
到这里,又有人迷糊了,这不是和js是一样的吗?其实还是不一样的,在ts中 变量声明并且马上给初始值时。ts的类型推断会自动加上,在后面就不能赋值其他类型,否则会报错,js则不会
有时候你会比TS更加明确一个值的类型,此时,可以使用 类型断言 来指定更具体的类型。比如:
//html
<a herf="http://baidu.com" id="linl">百度</a>
//TS
const aLink = document.getElementById('link) //aLink : HTMLElement
//此时 aLink是HTMLElement 类型,只有一些基本的属性,访问不到 a标签的herf等属性
// 此时我们需要用类型断言来解决这个问题
const aLink = document.getElementById('link) as HTMLAnchorElement
//使用 as 关键字实现类型断言
//关键字 as 后面的类型是一个更加具体的类型 (HTMLAnchorElement 是 HTMLElement 的一个子类型)
//通过类型断言,aLink的类型变得更加具体,这样可以访问a标签的特有属性和方法了
//另一种写法
const aLinl = <HTMLAnchorElement>document.getElementById('link') //react中用不了 冲突
到这里就有人会问了,我怎么知道这些到底 具体是什么元素呢 这时候我们就可以利用浏览器打印来知道具体的类型,在审查元素选中标签, console.dir($0)
打印,saajbv点击打开 滑倒最下面可以看到了,英语比较好的 直接写就好了。
let str1 = 'Hello TS' //String 数据类型 const str2 = 'Hello TS' // Hello TS 数据类型
'运行
这是为什么呢?因为 let声明的是一个变量,可以更改值,所以类型为:string . 而const声明的是一个常量,不能更改值,值只能是Hello TS,所以TS默认他的数据类型是Hello TS
使用场景:用来表示一组明确的可选值列表,比如需要声明一个函数,参数只能是 上下左右
function fun (direction:'up'|'down'|'left'|'right'){
}
fun('up')
//此时 fun函数只能接收 up down left right 四个参数 否则报错
枚举类型 就是字面量类型+联合类型组合的功能,也可以表示一组明确的可选值
枚举:定义一组命名常量。他描述一个值,改值可以是这些命名常量中的一个。
enmu Direction { Up,Down,Left,Right} //逗号分隔,不是| 枚举值以大写字母开头
function fun (direction :Direction){
}
//形参direction 的类型为枚举Direction,那么实参的值就应该是Direction 中成员的任意一个。
//枚举成员是有值的,是从0开始的自增数 Up:0 Down:1 Left:2 Right:3
// 当然,也可以也可以给枚举中的成员初始化值。 enmu Direction { Up=2,Down=4,Left=6,Right=8}
fun(Direction .Up)
//如果enmu Direction { Up=2,Down,Left,Right} 那么 Down:3 以此类推
//访问枚举成员直接使用点(.)语法。类似js中的对象.
当然,枚举成员的值也可以是字符串,如果是字符串的话 那么枚举所有成员的初始值都需要被定义,因为字符串没有自增长行为。
这会让 'TypeScript’变成 ‘AnyScript’ (失去TS的类型保护的优势),因为当值的类型是any时,可以对该值进行任意操作,并且不会有代码提示,
具有隐式 any 类型的情况:声明变量不提供类型也不提供默认值,函数参数不加类型。
众所周知,在js中 typeOf 是用来获取数据类型的,实际上,TS也提供了 typeOf 操作符:可以在类型上下文中引用变量或属性的类型(类型查询)。当然在TS中也可以使用JS中的用法。两者区别如下:
let p = {
a: 1,
b: 2,
}
//js
console.log(typeof p) //打印 'object'
//TS
function fun(point:{a:number,b:number}){}
fun(p)
//可以用typeof 进行优化
function fun (point: typeof p){}
fun(p)
//当然 也可也获取对象中的类型
let c:typeof p.a //c的类型就是number
相对于JS中class类,在TS中添加了几种方法: implements(实现接口)、可见性修饰符:pubilc(公有的),protected(受保护的),private(私有的),readonly(只读)。
//JS中的基本使用
class Person{
name: string //没有默认值的 需要添加类型 如果没有添加 会默认为 any
//age = 18 //如果有默认值,不需要添加类型,有类型推断 会自动默认为number
// age:number = 18 //这样写也是可以的 不过我们能省则省
age :number
//构造函数
constructor(name:string,age:number){
this.name = name
this.age = age
}
//实例方法
AddAge(n:number){
this.age +=n
}
}
const p = new Person('张三',18) //实例对象 P 的类型就是 Person
p.AddAge(2)
console.log(p)// Person { name: '张三', age: 20 } 用 . 可以访 具体属性'
// 继承 extends
class Son extends Person{
rideAge(n:number){
this.age *=n
}
}
const s = new Son('李四',18)
console.log(s.name) //李四
console.log(s.age) //18
s.AddAge(1)
console.log(s.age) //19
s.rideAge(2)
console.log(s.age) //38
// implements
interface state{
sing():void
}
class Person implements state{
sing(){
console.log('唱歌')
}
}
// 默认就是 public(公开的) 写不写都可以
class Example {
public move(){
console.log('移动了')
}
}
//protected 受保护的 只能在class本身 或者子类中使用
class Person {
protected move(){
console.log('移动了')
}
}
class Son extends Person{
run(){
this.move() // 正常 可以调用到
}
}
const p =new Person()
p.move // 报错 属性“move”受保护,只能在类“Person”及其子类中访问
const s =new Son()
s.move // 报错 属性“move”受保护,只能在类“Person”及其子类中访问
//private(私有的) 只能在当前class内部使用
class Person1 {
private move(){
console.log('移动了')
}
}
class Son1 extends Person1{
run(){
this.move() // 报错 属性“move”为私有属性,只能在类“Person1”中访问
}
}
const p1 =new Person1()
p1.move // 报错 属性“move”为私有属性,只能在类“Person1”中访问
const s1 =new Son1()
s1.move // 报错 属性“move”为私有属性,只能在类“Person1”中访问
//readonly 只读属性
class Person2{
//如果属性设置为 readonly 需要指定类型
// 不指定类型又有初始值时 类型会默认为初始值
// 不指定类型又没有初始值时,类型为any
readonly age:number = 18
constructor(age:number){
this.age = age
}
// 错误演示 readonly不能对方法使用
// readonly say(){} //报错"readonly" 修饰符仅可出现在属性声明或索引签名中。
}
// readonly 不仅仅可以使用在class类里面 还可以在接口interface 和{}类型声明 里面使用
interface state {
readonly name:string
}
let state:state = {
name:'1'
}
state.name = '2' // 报错 无法分配到 "name" ,因为它是只读属性。
let obj:{ readonly name:string} = {
name:'1'
}
obj.name = '2' // 报错 无法分配到 "name" ,因为它是只读属性。
两种类型系统 1、Structural Type System(结构化类型系统) 2、Nominal Type System(标明类型系统)
TS采用的是 结构化类型系统,类型检查关注的值所具有的形状
在TS中,主要是 对象、接口、函数的类型兼容性。
交叉类型(&):功能类似于接口继承(extends),用于组合多个类型为一个类型(常用于对象类型)
// 交叉类型
interface Person{
name:string
}
interface Son{
age:number
}
type SonD = Person & Son
let S :SonD ={
name:'张三',
age:18,
}
let S1:(Person & Son)={
name:'张三',
age:18,
}
在这里可以看出 交叉类型和 继承好像没有什么不同,那我们来说一下两者不同的地方,如果属性或者方法又冲突的时候,两者处理的方式不一样,看下面代码
interface Person{
say:(a:number)=>string
}
// 继承
interface Son extends Person{
say:(a:string)=>string
}
// 报错
// 接口“Son”错误扩展接口“Person”。
// 属性“say”的类型不兼容。
// 不能将类型“(a: string) => string”分配给类型“(a: number) => number”。
// 参数“a”和“a” 的类型不兼容。
// 不能将类型“number”分配给类型“string”。
//交叉类型
interface Son1{
say:(a:string)=>string
}
type SonD = Person & Son1
let S1:SonD ={
say(value:number|string){
return ''
}
// (property) say: ((a: number) => string) & ((a: string) => string)
}
泛型是可以在保证类型安全前提下,让函数等与多种类型一起工作,从而 实现复用,常用于:函数,接口,class中。
function fun<Type>(val:Type):Type{
return val
}
fun<number>(1) //function fun(a: number): number
fun<string>('1') //function fun(val: string): string
// 简化
fun(1) //function fun<1>(val: 1): 1
主要有两种方式:1、指定更加具体的类型 2、添加约束
如果不添加类型约束,那么参数可以是任何的类型,所以没有办法调用任何的方法 所以需要添加类型约束。
两种方式代码如下:
// 1、指定更加具体的类型 比如指定数组,我们就可以访问数组中的.length属性
function fun <Type>(val:Type[]):Type[]{
console.log(val.length)
return val
}
fun<string>(["1","2"])
// 2、添加约束
interface ILength{
length:number
}
// 只要含有length属性都可以
function fun1<Type extends ILength>(val:Type):Type{
console.log(val.length)
return val
}
fun1([1,2])
fun1('123456')
fun1({length:50,age:18})
fun1(123) // 报错 类型“number”的参数不能赋给类型“ILength”的参数
// 泛型变量可以是多个 看下面代码
// keyof 关键字 接收一个对象类型,生成其键名称(可能是字符串或数字)的联合类型
function fun2<Type ,Key extends keyof Type>(obj:Type,key:Key){
return obj[key]
}
// 一般情况
let person = {name:'张三',age:18}
fun2(person,'name')
// 了解补充
fun2('asd','charAt')
fun2(12,'toFixed')
fun2('asd',1)
泛型接口和泛型类
// 泛型接口
interface stateId<Type>{
id:(val:Type)=>Type
ids:()=>Type[]
}
let obj:stateId<number>={
id(val){
return val
},
ids(){
return [1,2,3]
}
}
// 其实 数组就是一个泛型接口
let arr = [1,2,3]
arr.forEach(item=>{ //(method) Array.forEach(callbackfn: (value: number, index: number, array: number[]) => void, thisArg?: any): void
})
// 泛型类
class Person<Type>{
defaultValue:Type
add:(num1:Type,num2:number)=>number
}
let p = new Person<number>()
p.defaultValue = 10
//可以省略不写
class Person1<Type>{
defaultValue:Type
constructor(value:Type){
this.defaultValue = value
}
}
let p1 = new Person1(10)
泛型工具类型
在TS中,内置了一些泛型工具类型,来简化TS中的一些常见操作主要知道4个就行:
Partial< Type > 、 Readonly< Type >、 Pick< Type,Key >、Record < Keys,Type >
// Partial< Type > 把已有的全部转换成可选属性
interface state {
id:number,
ids:number[]
}
type PartialState = Partial<state>
// 变换成这样 全部属性变成可选
// type PartialState = {
// id?: number | undefined;
// ids?: number[] | undefined;
// }
let P:PartialState = {}
// Readonly< Type >
interface state1 {
id:number,
ids:number[]
}
type ReadonlyState1 = Readonly<state1>
// 变换成这样 全部属性变成可选
// type PartialState = {
// id?: number | undefined;
// ids?: number[] | undefined;
// }
let P1:ReadonlyState1 = {
id:1,
ids:[1]
}
P1.id = 10 //报错 无法分配到 "id" ,因为它是只读属性。
// Pick< Type,Key > 选择一些属性来构造新的类型
interface state2 {
id:number,
ids:number[]
}
type PickState2 = Pick<state2,'id'>
// 变换成这样 全部属性变成可选
// type PartialState = {
// id?: number | undefined;
// ids?: number[] | undefined;
// }
let P2:PickState2 = {
id:1,
ids:[1] // 报错 不能将类型“{ id: number; ids: number[]; }”分配给类型“PickState2”。对象文字可以只指定已知属性,并且“ids”不在类型“PickState2”中。
}
//Record 构建一个对象类型,属性的键为Keys,属性类型为 Type
type RecordObj = Record<'a'|'b'|1,string[]>
let obj:RecordObj = {
a:['1'],
b:['1'],
1:['1']
}
在开发中,当无法确定对象中有哪些属性的时候,就要用到索引签名类型了。
// 对象 对象中的键值一定是字符串类型
interface state {
[key:string]:string
}
let obj:state={
a:'1',
b:'2',
}
// 在TS中 数组是一个特殊的对象 特殊在数组的键(索引)是数值类型
interface MyArray<T>{
[n:number]:T
}
let arr:MyArray<number> = [1,2,3]
映射类型:基于旧类型创建新类型,减少重复书写、提升开发效率。
映射类型 没办法在interface 接口中使用
type propsKey = 'X'|'Y'|'Z'
// type1 type2 实现效果一样
type Type1 = {x:number,Y:number,z:number}
type Type2 = {[key in propsKey]:number}
// type Type2 = {
// X: number;
// Y: number;
// Z: number;
// }
// 基于对象 keyof 获取键值 相当于 'X'|'Y'|'Z'
type propsKey2 = {x:number,Y:string,z:boolean}
type Type3 = {[key in keyof Type1]:number}
// type Type3 = {
// x: number;
// Y: number;
// z: number;
// }
索引查询(访问)类型
type a = {x:number,b:string,c:boolean}
type b = a['x'] //type b = number
//模拟 Partial 实现
type MyPartial<T>{
[p in keyof T]?:T[p]
}
type PartialStates = MyPartial<a>
// type PartialStates = {
// x?: number | undefined;
// b?: string | undefined;
// c?: boolean | undefined;
// }
// ---
// 同时查询多个索引的类型
type c = a['b'|'c'] //type c = string | boolean
type d = a[keyof a] //type d = string | number | boolean