TypeScript

189 阅读6分钟

1、TS的变量声明

        声明了类型之后,TS就会去进行类型的检测

    var/let/const 标识符 :数据类型 = 赋值;
    
    let message : string = 'hello world'

 2、变量的类型推断

        默认的情况下,会将赋值的值的类型,作为前面标识符的类型

let age = 18
console.log(age)

const height = 1.88 //类型的字面量1.88
console.log(height)

        注意:使用 let 去声明的变量,推断的就是通用类型

                     使用 const 声明的变量,推断出来的是字面量类型    

  3、JS中的类型声明

        其中number、string、boolean、null、undefined就直接写就可以了

        对于数组和对象,需要去指定每一个具体是声明

// 平时的写法
 let list: number[] = [1,2,3];

// 泛型数组
 let got: Array<number> = [1,2,3];

// 元组 :是代表一个已知元素数量和类型的数组 就是可以放2个不同类型的元素
  let midde: [String,number];
      midde=['Ktton',18];

// 对象
    const user:{
        name:string,
        age:number
    } = {
        name:'crt',
        age:18
    }

 4、TypeScript的数据类型

        any类型

         在一些情况下我们无法一开始就确定一个变量的类型,它可能在中途会出现一些变化,

         所以我们就会去使用any类型

let asg: any = "as写法";
any = 123
any = true
any = {}

       unknow类型

      和any类似,不同的是,unknown类型的值上做任何操作都是不合法的

 let unk :unknown = 'aaa'
     unk = 123
     // console.log(unk.length);//报错
     if(typeof unk === 'string'){
        console.log(unk.length); //3        
    }

        void类型

        跟java一样 没有返回值但是它有形参   

 function sun (num1:number,num2:number){
        console.log(num1+num2);
    }
    sun(20,30)

       never类型

// never跟它的中文意思一样,永不发生
    function foo() :never{
        while(true){}
    }
    function bar():never{
        throw new Error()
    }

        tuple类型 ---元组  

        元组中每个元素都有自己特性的类型,根据索引值获取到的值可以确定对应的类型      

    let arr1 :any[] = ['zxy',18,true]
    let arrAge = arr1[1]; //后面会详细讲

二、TS的语法细节

         1、 可选类型

                可以去指定某个属性是否可选(或者不传),加个?就解决

 function print(point:{x:number; y:number; z?:number}){
            console.log(point.x);
            console.log(point.y);
            console.log(point.z);
        }
 // 直接传值
        console.log({x:123}); //因为y并没有设置是否可传 所以就会报错
        console.log({x:123,y:321}); //123,321

       2、 联合查询

                联合类型意思就是可以指定多个类型,表示可以是这些类型中任何一个值

function move(id:number | string){
            // 应用场景:如果我们想要到id的长度那么,我们就要去类型进行指定
            if(typeof id === 'string'){
                console.log(id.length);
            }else{
                console.log(id);
            }
        }
        move(123)
        move('abc')

        3、类型的别名

                使用type关键字  或者是interface(接口)       

  type Idtype = String | number
        function getid(id:Idtype){
            console.log(id);
        }

        type pointType = { x: number; y: number; z?: number }
        function printPoint(point: pointType) {
          console.log(point.x)
          console.log(point.y)
          console.log(point.z)
        }

       4、 type和interface的区别

/*区别在于type类型的使用范围更广,interface只能声明对象
          但是interface在声明对象的时候可以去进行追加
          而且interface支持继承,继承的效果跟追加是一样的
        */ 
          interface Person{name:string,age:number}
          interface Son extends Person{habit:string}
          const  familed:Son = {name:'丹尼',age:18,habit:'孩子'}

        5、交叉类型

                交叉相当于就是满足多个类型条件,交叉类型使用&符号

 interface man {
            name:string
        }
        interface work{
            work:()=>void
        }
        const me1:man|work={
            name:'zzy'
        }
        const me2:man&work={
            name:'zzy',
            work:()=>{console.log('吃');
            }
        }
        // 也可以用type关键字去进行别名
        type Iperson = man&work

        6、类型断言 as(重点)

                可以手动改来指定一个值的类型,允许变量从一种类型更改成另一种类型,但是这个转换的类型只能是 更具体 || 不太具体 可以防止不可能的强制转换

let myvalue :any = "hello world";
        let value:number = (myvalue as string).length;
        console.log(value);
        
        const mingzi :string ='zxy';
        // const num:number = mingzi as number; 这种情况编译器会直接报错
        const num :number = mingzi as any as number  //这种情况就可以
        console.log(num);
        
        // 字面量类型(literal types)
        const mess :"hell world" = "hell world";
        let num1 : 123 = 123;

        7、类型缩小

                typeof、instanceof、in、switch-case等         

三、 TS函数类型    

        1、函数调用签名

                通过编写函数类型的表达式,来表示函数类型使用到的是TS函数中的函数调用签名

 type min = (sum1:number,sum2:number) =>number
    // 其中=>number 代表的是他们返回值的类型
    // 有了函数调用签名就可以在声明变量或函数参数时,指定它们的类型
    const sum:min=(a,b)=>a+b
    function demo(ropz:min){
        console.log(ropz(1,2));
    }
    demo(sum) //3

        2、参数的可选类型

                可以指定某个参数不是必传的,可是类型必须写在必传类型的后面

function foo(x:number,y?:number){
            console.log(x,y);
        }
        foo(1,2)
        foo(20)
        // 这里的y的类型是一个联合类型 number|undefined
        // 如果不传数据的话就是undefined

        3、默认参数/剩余参数

// 默认参数
        // 必传参数 -> 有默认值的参数 -> 可选参数
        function oof(x:number,y:number =100){
            console.log(x,y);
        }
        oof(20) //200,100

    // 剩余参数
        function less(one:number, ...nums:number[]){
            let two = one
            for(const sums of nums){
                two += sums
            }
            return two
        }
        console.log(less(20,20)); //40
        console.log(less(1,2,3)); // (1+2)+3
        console.log(less(1,2,3,4)); 

        4、可推导的this类型

                在类或者对象具体类型无法确定的时候可以使用any类型也可以使用this来表示当前对象的类型       

 const info = {
            name:'tody',
            eating(){
                console.log(this.name +''+'Apple');
            }
        }
            info.eating()

        5、不确定的this类型                               

                 这里强调一下TypeScript进行类型检测的目的是让我们的代码更加的安全

class MyClass {
            private data = 0;
          
            public add(value: number): this {
              this.data += value;
              return this;
            }
          
            public multiply(value: number): this {
              this.data *= value;
              return this;
            }
          
            public getData(): number {
              return this.data;
            }
          }
          const myObj = new MyClass();
          const result = myObj.add(10).multiply(5).getData();
          console.log(result);   // 输出:50
          /*
            在这个例子中,Class 类有两个方法 add 和 multiply,
            它们都返回了当前对象的类型 this。这样一来,我们就可以在链式调用方法的时候,
            按照任意顺序调用这两个方法,并且结果都会返回一个 MyClass 类型的对象。
            最终,我们通过 getData 方法获取计算结果,并输出到控制台中。
            需要注意的是,在使用 this 类型时,编译器并不能完全确定 this 的具体类型,
            因此需要在代码中谨慎使用,避免出现类型错误或者运行时异常。
            同时,在 TypeScript 中,我们也可以使用泛型来代替使用 this 类型,
            这样可以更加灵活和安全地处理对象类型的问题。
          */ 

         6、函数的重载    

//错误的示范编写一个函数,可以队字符串和数字类型进行相加
        //   type addtype = number | string
        //   function add(a:addtype,b:addtype){
        //     return a + b  //这样直接写是错误的,编译器都直接报错
        //   }

            // 可以通过联合类型的逻辑判断去完成
            // 但是很麻烦,而且返回值的类型也无法确定
            function add (a:string | number, b:string | number){
                if(typeof a == 'number' && typeof b == 'number'){
                    return a + b
                }else if(typeof a == 'string' && typeof b == 'string'){
                    return a + b
                }
            }
            
            // 我们可以通过重构签名(overload signatures)的方式去进行
                function ten (num1:string,num2:string):string
                function ten (num1:number,num2:number):number
                function ten(num1:any,num2:any):any{
                    return num1 + num2
                }
                const cen2 = ten('abc','cba');
                const cen1 = ten(10,20);
                console.log(cen2); //abccba
                console.log(cen1); //30

四、TS类的使用

                其实不管是TS还是JS类的使用跟JAVA并没有太大的差别

                同行通常开玩笑的说:没有对象?那就new一个

                其实没什么好说的,java的基础  科班出身应该都学过

 // 类的定义
                class Person {
                    name: string
                    age: number
                  
                    constructor(name: string, age: number) {
                      this.name = name
                      this.age = age
                    }
                  
                    eating() {
                      console.log(this.name + ' eating')
                    }
                  }
                  
                  const p = new Person('why', 18)
                  console.log(p.name)
                  console.log(p.age)
                  p.eating()
                  
                //   类的继承
                // 其实就是在类定义好之后加上一个extends的关键字就可以了
                  class teacher extends Person{}
                //继承了之后,可以使用super关键字来去调用父类 super.Person()

                // 成员的修饰,在java里面常用就是private public

                //只读属性 readonly
                // getters/setters  一个是获取/ 一个是监听
                // 静态成员,前面加一个static关键字就可以了

 五、接口

        1、接口的声明 

                在前面我们通过type可以用来声明一个对象类型 

                对象的另外一种声明方式就是通过接口来声明         

   // 接口的声明
    interface Person{
        name:string,
        age:number
    }
    const info:Person = {
        name:'why',
        age:14
    }
    console.log(info);
    
    interface plan{
        lable:string
    }
    function print(obj:plan){
        console.log(obj.lable); //输出类型string
    }
    let myObj = {size:10,lable:"Size 10 obj"}
    print(myObj);

        2、接口的可选属性

    interface man{
        name:string,
        age:number,
        friend?:{
            name:string
        }
    }
    const info:man = {
        name:'hello',
        age:15,
        friend:{
            name:'world'
        }
    }
    console.log(info.friend?.name);
    

        3、接口的只读属性

                接口也可以设置只读属性这样就意味着我们再初始化的时候,这个值无法修改

// 这样就意味着我们再初始化的时候,这个值无法修改
        interface man{
               readonly name:string,
               readonly age:number,
                friend?:{
                name:string
                }
            }
                const info:man = {
                name:'hello',
                age:15,
                friend:{
                name:'world'
                }
            }
           console.log(info.friend?.name); //world
            console.log(info.name);  //hello
            //info.name = '123'; 如果在外面修改,编译器就会出现报错

        4、接口的索引类型

 //通过 interface 定义索引类型
            //[...:类型]:类型
            interface IndexLanguage {
                [index: number]: string
            }
            const frontLanguage: IndexLanguage = {
                0: 'HTML',
                1: 'CSS',
                2: 'JavaScript',
                3: 'Vue'
            }
            
            interface LanguageYeaar {
                [name: string]: number
            }
            
            const languageYear: LanguageYeaar = {
                C: 1972,
                Java: 1995,
                JavaScript: 1996,
                TypeScript: 2014
            }
            console.log(languageYear['C']); //1972 
            console.log(frontLanguage[0]); //HTML

        5、函数类型

                了解一下,非特殊情况通常用类型别名来定义函数

//复习一下类型别名
            //type java = {f1:number,f2:number} => number
            
            interface java{
                (f1:number,f2:number) :number
            }
            function  classroom (num1:number,num2:number,room:java){
                return room(num1,num2) //f1=num1 f2=num2
            }
            const add:java = (num1,num2)=>num1+num2
           console.log(classroom(20,30,add)); //50

        6、接口继承

                接口是可以多继承,但是类不支持多继承

                而且接口继承的同时,这个继承也可以成为限制

  interface ISwin{
                swimming:()=>void
            }
            interface ISfoot{
                football:()=>void
            }            
            interface ISall extends ISwin,ISfoot{}
            const action :ISall={
                swimming(){},
                football(){}
             }

        7、交叉类型

                原型相当于是联合类型,但是联合类型是只需要符合其中一个就可以 |

                但是交叉类型 既可以选择只符合一个,也可以选择同时符合两个 &

type boy = number|string
            //交叉类型
            interface ISswim{
                swimming:()=>void
            }
            interface ISfly{
                flying:()=>void
            }             
            type plan1 = ISswim | ISfly
            type plan2 = ISswim & ISfly 
            const man :plan2={
                swimming(){},
                flying(){}
            }
        

        8、接口的实现

                接口定义后,也是可以被类实现的

                如果被一个类实现,那么在之后需要传入接口的地方,都可以将这个类传入

interface ISwim {
                swimming: () => void
              }
              const a: ISwim = {
                swimming() {}
              }
              
              function foo(swim: ISwim) {}
              
              interface IEat {
                eating: () => void
              }
              
              // 类实现接口
            //   class Animal {}
              
              // 继承:只能实现单继承
              // 实现:实现接口,类可以实现多个接口
              class Fish extends Animal implements ISwim, IEat {
                // 这里是继承了Animal类,并且实现了ISwim,IEat这两个接口
                swimming() {
                  console.log('Fish Swimmig')
                }
                eating() {
                  console.log('Fish Eating')
                }
              }
              
              class Person implements ISwim {
                swimming() {
                  console.log('Fish Swimmig')
                }
              }
              
              // 编写一些公关的API:面向接口编程
              function swimAction(swimable: ISwim) {
                swimable.swimming()
              }
              // 1. 所有实现了接口的类对应的对象,都是可以传入
              swimAction(new Fish())
              swimAction({ swimming() {} })
              swimAction(new Person())

        9、interface和type区别              

                我们会发现interface和type都可以用来定义对象类型,

                如果是定义非对象类型,通常推荐使用type,比如Direction、Alignment、一些Function

                如果是定义对象类型,那么他们是有区别的:interface 可以重复的对某个接口来定义属                  性和方法;而type定义的是别名,别名是不能重复的;

                接口的特性:

                不能被实例化、含有声明但未实现的方法

                一个类可以实现多个接口,接口支持多继承

                接口定义的成员必须要实现,接口中所有的成员都默认是public

六、枚举

            枚举允许定义一组命名常量,常量可以是数字也可以是字符

             通过enum关键字去实现枚举定义

             枚举就是讲一组可能出现的值,一个一个的列举出来,定义在一个类型中

enum move{
                LEFT,
                RIGHT,
                TOP,
                BOTTEM
            }
            function DD(ming:move){
                switch(ming){
                    case move.LEFT:
                        console.log('向左移动');
                        break
                    case move.RIGHT:
                        console.log("向右移动");
                        break
                    case move.TOP:
                        console.log("向顶部移动");
                        break
                    case move.BOTTEM:
                        console.log("向下移动");
                        break
                    default:
                        console.log("结束");
                        
                }
            }
                console.log(DD(move.LEFT));
                console.log(DD(move.RIGHT));
                console.log(DD(move.BOTTEM));
                console.log(DD(move.TOP));

        1、泛型的类型参数化

                就是一开始不去刻意的定义类型,而且在调用参数的时候,再去决定它的类型是什么

                具体的写法是:<类型>方法

 function sum<Type>(num:Type):Type{
        return num
    }
    
    // 同时对于参数的调用方法有2种
        //1. 明确的去传入类型
        sum<number>(20)
        sum<{ name: string }>({ name: 'why' })
        sum<any[]>(['abc'])

        // 2.通过类型推导
        sum(20)
        sum('abc')

        2、泛型的类型补充

                T:Type的缩写,类型

                K、V:key和value的缩写,键值对

                E:Element的缩写,元素

                O:Object的缩写,对象    

function foo<T, E, O>(arg1: T, arg2: E, arg3: O) {}
        foo<number, string, boolean>(10, 'abc', true)

        3、泛型接口

// 使用泛型去创建接口
        interface Person<T1 = string, T2 = number>{
            name:T1,
            age:T2
        }

        const kid:Person<string,number>={
            name:'why',
            age:15
        }

        const p:Person={
            name:'go',
            age:14
        }

        4、泛型的约束

                有时候我们希望传入的类型有某些共性,但是这些共性可能不是在同一种类型中

                比如string和array都是有length的,或者某些对象也是会有length属性的

                那么只要是拥有length的属性都可以作为我们的参数类型,那么应该如何操作呢

 interface ISlength{
            length : number
        }
        
        function getLegth<T extends ISlength>(age:T){
            return age.length
        }

        //泛型函数getLegth,它所接受参数的类型为 T ,并且该参数被要求实现ISlength接口
        // 就是必须要拥有一个length属性
        // 所以为了满足这一个条件,所返回的类型属性必须要有length这一属性才能传递给函数getLegth
        
        console.log(getLegth('abc')); //因为字符串具有length这一属性 3
        console.log(getLegth({length:100})); // 100
        //console.log(getLegth(100)); //数字就不行了 会报错

        5、模块开发和命名空间

                 TS可以通过模块开发/命名空间来控制作用域           

// 模块开发
            // TS可以通过模块开发来控制作用域
                export function f1(num1:number,num2:number){
                    return num1 + num2
                }

                export function f2(num1:number,num2:number){
                    return num1 -num2
                }
                
        // 命名空间
                // 命名空间namespace
                // 在vuex仓库中通过true 和false来去控制
                export namespace time{
                    export function time1(foo:string){
                        return '2023-1-1';
                    }
                }

                export namespace what{
                    export function  time2(foo:string){
                        return '2023-2-1';
                    }
                }
        
                console.log(time.time1('')); // 2023-1-1
                console.log(what.time2('')); // 2023-2-1