TypeScript学习

167 阅读12分钟

ts是一种弱类型的类型协商约束的偏静态的动态语言。

基本用法

属性:类型 对于属性进行声明

简单数据类型的声明

简单类型包括:string,number ,null,undefined,boolean

let name:string='测试'
let age:number=12

引用类型数据声明

复杂数据类型:array object

//数组声明
let testdata:string[]=['我是测试数据']
//如果知道了数组的长度和数组中的数据类型的话我们可以使用元组类型

let testdata:[string,number]=['测试元组',23]

//对象声明
type userinfo={
name:string,
age?:number //可选属性
[propName:string]:string  //其他未知的字符串的属性
}
let student:userinfo={name:'测试学生',age:12}
//接口定义对象类型
interface userinfo{
name:string,
age:number
}
let student:userinfo={name:'测试学生',age:12}

其他类型

枚举

所谓的枚举就是事先定义一组 有名称的数字变量或者特殊指定 通过关键字enum 进行定义

  enum  testenum{
      testA='男'
      testB='女'
        }

//在使用的时候我们可以通过testenum.testA来获取属性值
还有一种类数组的方式
enum testarr{
  up,
  down,
  left,
  right

}
testarr[0]获取up
元祖
//可以理解长度和类型固定的数组 只能通过下标修改对应元祖的值

let tet:[number,string]=[1,'sdf']
tet[0]=12
混合类型
let a:string |number ='12'/a的类型可能是字符串也可能是数组
交叉类型
type userInfo={
 name:string
}
type person={
 body:string
}
type children=userInfo&perosn  ///交叉类型

let children:chilren={name:"交叉",body:"交叉body"}

还有any unknown类型等。 any值的是任意类型可以绕开ts中的类型检测 unknown也可以表示任意类型但是其必须在使用的时候进行类型的判断后才可以使用 never 代表的是函数永远不会执行完或者抛出异常 则函数的返回值是never

函数

//固定参数函数
funtion(a:string='测试数据'){
}
//可选参数函数(可选参数一定在最后)
function (b:number,a?:stirng){

![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/181f80e97b4f421cae2e3c5fdd641355~tplv-k3u1fbpfcp-watermark.image?)
}
//剩余参数
function (a:number,...arg:string[]){
}
//函数重载
//同一个函数提供多个函数类型定义来进行函数重载
Function getdata(a:number):number;’
Function getdata(b:string):string;
Function getdata(a:any,b?:any):any{函数体
//通过函数中进行逻辑判断可是实现不同的重载函数
if(typeof b = number){
///逻辑
}else{
//逻辑
}

函数中this的问题

函数this参数 ,我们在ts中使用this的时候可能会遇到意想不到的效果 this 的类型不是我们想要使用的对象或者函数,而是any 此时我们要通过this参数指定当前this为那个对象或者函数

//通过如下的方式可以实现this的类型是Deck
interface Card {
      suit: string;
      card: number;
    }
    interface Deck {
      suits: string[];
      cards: number[];
      createCardPicker(this: Deck): () => Card;
    }
    let deck: Deck = {
      suits: ["hearts", "spades", "clubs", "diamonds"],
      cards: Array(52),
      // NOTE: The function now explicitly specifies that its callee must be of type Deck
      createCardPicker: function (this: Deck) {
        return () => {
          let pickedCard = Math.floor(Math.random() * 52);
          let pickedSuit = Math.floor(pickedCard / 13);

          return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
        };
      },
    };

typescript中的类

默认类中的属性或者方法都是public的我们可以给属性和方法定义 private 定义private的属性和方法只能在class内部方法 在外部不能访问 protected定义的属性和方法 只能在自己class中或者由自己派生出的子类中访问,但不能在外面访问(实例化访问)

参数属性

所谓的参数属性就是将参数的定义和初始化放在了一起

//一般写法
class testcls{
    //参数声明
    name:string
    constructor(name:string){
        this.name=name
    }
}
//参数声明
class testcls{
    constructor(public name:string){
        console.log(this.name)//类中可以直接去使用该属性     属性的修改符 可以是public private protected
    }
}

静态属性和方式直接通过类命访问 声明静态方法的时候用static

抽象类

抽象类通过abstract声明 其中通过abstract声明的方法称为抽象方法, 抽象方法只写声明不写实现,通过抽象类的派生子类去实现抽象方法

//抽象类
    abstract class testab {
      test(name: string) {
        console.log(name);
      }
      abstract test1(): number;
    }
    //继承抽象类
    class imab extends testab {
      test1(): number {
        return 1;
      }

接口

接口只定义类型规则不进行功能实现,对于接口中定义的属性类型不受顺序的限制。 eg:

interface labelValue{
    name:string
    sex?:string//可选属性,
    readonly age:number //只读属性
}
function printLabel(obj:labelValue){
    console.log(obj)
}
printLabel({name:"test"})

const和readonly的使用const 用于声明变量,readonly用于声明属性

绕开属性检测

1. 通过[propName:string]:any(函数签名的方式) 方式定义其他属性都是any类型的方式
2. 通过断言的方式 直接指定我们在printLabel()函数中传入的对象是interface 定义的类型  即使传递的对象的属性不存在与interface中 程序也不会报错
3. 对于一些复杂的对象绕开属性检测的方式就是将对象赋值给变量,再用变量代替对象去执行参数传递 也可以实现。

函数接口

函数接口只有参数类型定义和返回值类型定义 ,使用函数接口的时候也不要函数参数名称和接口参数名称一致

 interface fun{
     (name:string,age:number):number 
 }

let funtest:fun=function(serviceName:string,serviceAge:number){
 return 'ww'
}

可索引类型接口

可以通过索引的之去指定索引对应元素的类型

interface stringArray{
    [index:number]:string//定义了一个索引其每个元素的值都是字符串类型的数据
}

索引接口有两种:number类型接口和string类型接口 在定义的时候 number类型的接口一定要是string类型接口的子类 因为number 会转换成string

eg:
class Animal {
    name: string;
}
class Dog extends Animal {
    breed: string;
}

//这种方式不被允许 string类型不是number的父类
interface NotOkay {
    [x: number]: Animal;
    [x: string]: Dog;
}

//这种则被允许
interface NotOkay {
    [x: string]: Animal;
    [x: number]: Dog;
}

类类型接口

接口定义的方法和属性都是类的公用方法和属性,私有的获取不到并且不会检测类中私有的方法

interface ClockInterface {
     currentTime: Date;
     setTime(d: Date);
   }
//通过类去实现接口
   class Clock implements ClockInterface {
     currentTime: Date;
     setTime(d: Date){
       this.currentTime = d;
       
     }
     constructor(h: number, m: number) {
       

     }
   }
  let data= new Clock(1,2)
  let aa=new Date(2020-12-12)
  data.setTime(aa)
  console.log(data.currentTime)

接口扩展

接口可以通过继承接口的方式实现接口属性的合并

  interface Shape {
    color: string;
}

interface Square extends Shape {
    sideLength: number;
}
let testSquar:Square
testSquar.color='red'
testSquar.sideLength=12

混合类型

一个对象拥有多种类型的时候,可以使用

//一个对象可以作为函数也可以作为对象使用
  interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
  }  

   function getCounter(): Counter {
    let counter = <Counter>function (start: number) { };
    counter.interval = 123;
    counter.reset = function () { };
    return counter;
  }

    let c = getCounter();
   c(10);
   c.reset();
   c.interval = 5.0;

接口继承类

接口继承类的时候其实继承了类中公用的属性和方法,无论属性和方法是私有的还是公有的同意实现继承 接口之作为属性方法规则的定义限制 ,不作为具体功能的实现 ,在实现接口的时候只有继承类或者继承类的子类才能实现 继承接口

class Control {
    private state: any;
}

interface SelectableControl extends Control {
    select(): void;
}

class Button extends Control implements SelectableControl {

    select() { }

}

class TextBox extends Control {

    select() { }

}

//Image 不是control类或者子类 因此他不能去实现 通过类继承过来的接口

class Image implements SelectableControl {

    select() { }

}

class Location {

}

泛型

泛型变量

声明一个函数是T 参数也是T 类型 返回值也是T 类型 泛型的写法 function test(arg:T):T{ return arg }

泛型接口(接口就是对于定义值得校验)

interface Indentify<X,Y>{
    ids:X,
    name:Y
}
function testinde<T,U>(a:T,b:U):Indentify<T,U>{
  let data:Indentify<T,U>={
      ids:a,
      name:b
  }
  return data
}

泛型类 (泛型类和泛型接口类似)

class testindex<T>{
    constructor(public name:T){
     
    }
    add(x:T,y:T):T{
    return x
    }
}

泛型约束

通过泛型继承的方式对于泛型进行约束

interface testinter{
    length:number
}
function testdata<T extends testinter>(arg:T):T{
    console.log(arg.length)
}

类类型泛型

定义一个类作为泛型的类型传递

//定义类
class user{
    name:string|undefined
    age:number|undefined
}
//定义泛型类
class inderUser<T>{
    add(arg:T){
        console.log(arg)
    }
}
let userdata=new user()
userdata.name='sdsds'
userdata.age=12
//使用泛型定义泛型的类型是user
let inderusers=new inderUser<user>()
inderusers.add(userdata)//传入user类型的实例

类型兼容

所谓的类型兼容就是赋值对象中包含被赋值对象的属性类型

eg://对象
interface objectNamed{
    name:string
}
let testobjA:pbjectNamed
let testobjB={name:"cool",age:213}
testobjA=testobjB
//testobjB的类型包含testobjA 中的属性类型  则在赋值的时候出现了类型兼容
function funtest1(name:string){

}
function funtest2(name:string,age:number){}

//functest2 可以通过类型兼容的方式赋值给funtest1 反之不行


命名空间

对于导出的type规则通过命名空间的方式 去做区分管理,防止全局下type规则命名冲突的问题

 export namespace vaildtion{
     export interface validName {
        name:string
     }
 }
 //使用的时候通过 vaildtion.validName 使用
 import {vaildtion} from './type.ts'

声明合并

对于interface 还是namespace 声明 存在多个重复声明的话 ts则会进行声明的合并, 前提是重复的声明占用不存在相互重复的属性,命名空间的合并中 没有导出的属性值是不支持合并的 他的使用只能在当前的命名空间中使用

interface Document {
    createElement(tagName: any): Element;
}
interface Document {
    createElement(tagName: "div"): HTMLDivElement;
    createElement(tagName: "span"): HTMLSpanElement;
}
interface Document {
    createElement(tagName: string): HTMLElement;
    createElement(tagName: "canvas"): HTMLCanvasElement;
}
//合并后
interface Document {
    createElement(tagName: "canvas"): HTMLCanvasElement;
    createElement(tagName: "div"): HTMLDivElement;
    createElement(tagName: "span"): HTMLSpanElement;
    createElement(tagName: string): HTMLElement;
    createElement(tagName: any): Element;
}


namespace Animals {
    export class Zebra { }
    let myPrototype:string='ceshi'
    export function testexproit(){
       console.log(myPrototype)
    }
}

namespace Animals {
    export interface Legged { numberOfLegs: number; }
    export class Dog { }
}
//合并后
namespace Animals {
     let myPrototype:string='ceshi'
    export interface Legged { numberOfLegs: number; }
    export function testexproit(){
       console.log(myPrototype)
    }
    export class Zebra { }
    export class Dog { }
}
//在Legged 和Dog中不能使用myPrototype属性   因为没有去抛出 只能在自己的Zebra中进行使用

装饰器

所谓的装饰器就是通过注入的方式 来扩展 类,属性,方法的功能 1.类装饰器 在类声明前 紧靠类声明 可以为修改类定义 传入一个参数 这个参数则是当前的装饰类 类的装饰器只有一个参数 参数就是装饰的当前的类

 function logClass(params: any) {
  console.log(params);    //params是传入装饰器的参数                              
  params.prototype.addurl = "我是修饰符";                                                    
   params.prototype.eat=function(){
   console.log('sfdsf')
   }
   return functon (target:any){//target就是调用装饰器的类 在本案例中就是HttpClient类
}
 }

 @logClass('测试') 
 class HttpClient {
   constructor() {}                                                                                                                                                                            
   getdata() {}
 }

2.属性装饰器 属性修饰器则对于属性进行修饰的方法 这个方法接收连个参数一个是类或者说是实例的原型对象 另一个是属性名称

function logClass(params: any) {
  console.log('---',params);
 return function(target:any){
   console.log('++++',target)
   target.prototype.addurl=params
 }
}
//定义属性装饰器
function logPropty(params:any){//属性装饰器传入的值
return function (taraget:any,att:string){//当前类或者实例对象和需要装饰的属性
  taraget[att]=params
}

}
@logClass('我是工厂装饰器')
class HttpClient {
  @logPropty('属性修饰器')//紧挨着属性进行使用
  addurl:string|undefined
  constructor() {}
  getdata() {}
}
let http: any = new HttpClient();
console.log(http.addurl)

3.方法装饰器 可以修改替换监视方法定义 方法装饰器需要传入三个参数 1类的构造函数或者实例的原型对象 2成员名称 3成员的属性描述

function getlogClass(params: any) {
  return function(target: any, mthodName: any, desc: any) {
    console.log(target);
    console.log(mthodName);
    console.log(desc);//desc.value获取的是当前修饰的方法
    //用装饰器重写方法
    let nodesapce = desc.value;/;/我们将当前方法保存

    desc.value = function(...args: any[]) {//通过定义去重写方法
      args = args.map((items) => {
        return String(items);
      });
      console.log(args);
      nodesapce.call(this, args);//然后将重写获取的值 在传入到之前的方法 保证之前方法中的函数体正常可用 此时我们就实现了方法的修改    
    };
  };
}

class HttpClienst {
  addurl: string;
  constructor() {
    this.addurl = "修改构造函数中的数据";
  }
  @getlogClass("ssss")
  getdata() {
    console.log(this.addurl);
  }
}
let https: any = new HttpClienst();
console.log(https.getdata("sss", 12));

装饰器的执行顺序: 1.有多个参数装饰器时,从最后一个参数一次向前执行 2. 方法和方法参数器 中参数装饰器先执行 3. 类装饰器总在最后执行 4. 方法和属性装饰器,谁在前面谁先执行。因为参数属于方法一部分,所有参数会一直紧紧挨着方法执行

typescript声明文件(.d.ts)

我们实际的开发中尝尝会用到第三方库 虽然可像js一样直接的去使用他们但是无使用ts进行类型的检测 因此我们需要ts的描述文件去声明第三方度的类型以达到统一类型规范的效果,此外如果我们项目中频繁的用到我们可以将这些类型声明到.d.ts文件中 形成全局的类型 在对应的文件中直接使用即可。 这些.d.ts文件默认自动会加载的

//test.d.ts
语法规则
declare  module 模块名称{} 
范例:
我们扩展vue 使得vue中可以使用$axios
import Vue form  'vue'
decalare module "vue/types/vue"{
interface Vue{
$axios:AxiosInstace
}
}
//我们描述vue-router
import VueRouter from ‘vue-router’
import {store} from 'vuex'
declare module "vue/type/options"{
interface ComponentOptions<V extends Vue>{

router?:VueRouter,
store?:Store(<any>)
}
}

ts中的类型推断

//对于let申明的变量采用的是适当扩充的方式 对const声明的变量采用的是适当收紧的方式  const声明的类型则是字面量声明
let a=12 //类型numbner
let b='12' //类型字符串
const c='apple' //类型apple类型   进行类型的收缩
let d={name:"测试"} //类型{name:"测试"}
let f=null  ///类型any   进行类型的扩充

可赋值类型有:数组,布尔,数字,对象,函数,类,字符串,字面量。 如果a类型是b类型的子类型 则A可以赋值给B 或者a是any类型

类型声明

类型声明一般有两种方式一种是通过type的方式一种就是interface的方式 type方式声明的变量侧重于描述类型 interface声明的变量侧重于类型的结构

//type
type test={testname:string}
type a=numer
let obj:test   let testa:a
//interface
interface test{
name:string,
age:number
}
//相同点:
1.都可以描述一个对象或者函数
2.都可进行扩展,type 用&关键字进行类型扩展   interface 可以通过extend 扩展type   type 也可以通过extend 扩展 interface
eg:
//interface
interface testInterface{
 name:string,
 age:number
}
//type

type testType={
 name:string,
 age:number
}
//type 继承
type extendType={sex:string}&testType
//接口扩展
interface extendInterface extend testInterface{
sex:string
}

//接口继承type
interface test extend testType{
sex:string
}

不同点: 1.类型别名用户其他类型(联合类型,元祖类型,基本类型(原始值)),interface 不支持 2.interface 可以多次被定义 并视为合并所有声明成员 type 不支持

interface testone{
name:string,
age:number
}
interface testsecond{
sex:string
}
//合并后
interface alltest{
name:string,
age:number,
sex:string
}

3.type 能使用in关键字 生成映射类型 但是interface不行

type keys='firstname'|'surname'
type DudeType = {
[key in Keys]: string;
};
const test: DudeType = {
firstname: 'Pawel',
surname: 'Grzybek',
};

后期持续更新