自己学习ts时的笔记

133 阅读14分钟
/**
 * 1. boolean,number,string
 * 2. undefined,null默认情况是所有类型的子集,所以可以给任意类型赋值null,undefined而不报错。(如果指定了--strictNullChecks标记,null和undefined就只能
 *  赋值给void和他们各自,不然会报错)
 * 3. any:不清楚什么类型,可以使用any类型,这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。(不建议使用any,不然ts就失去了意义)
 * 4. unknow:这个类型代表任何类型,是一个安全类型,他让ts可以把潜在的风险暴露出来,声明为unknow的类型做任何事情都是不合法的,只有配合类型断言
 *  才能成功。例子:function devide(param: unknow) { return param / 2 }//会报错,因为param可能不是数字类型,这个时候需要类型断言配合
 *  比如: function devide(param: unknow) { return param as number / 2 }这样就成功了
 * 5. void: 表示没有任何类型。比如函数没有任何明确返回值,默认返回void类型 function welcome(): void { console.log('hello') }//void可以赋值undefined
 * 6. never:这个类型表示那些用不存在的值的类型,有些情况值永远不存在,比如抛出异常的函数,或者无限循环的函数,例子: function fn(msg: string): never { throw new Error(msg) }
 * 7. 数组类型: let list: number[] = [1,2,3]//数字类型数组,只能存放数字类型,其余类型会报错
 * 8. 元组类型: let list: [number,string] = [1,'2']//元祖类型的数组,已经定义好各个索引的值的类型,比如符合类型定义,不然会报错
 *  值超出定义个数也会报错,但是push的时候超出定义个数不会报错。如果push没有定义的数据类型会报错
 * 9. 函数类型: ts定义函数类型需要定义输入参数类型和输出类型。输出类型也可以忽略,因为ts可以根据返回语句自动推断出返回值类型。
 *  例子: function add(x:number,y:number):number { return x + y }
 *  如果没有返回值,可以默认返回void, 例子: function welcom():void { console.log('hello') }
 *  9-1: 可选参数:参数后面加个问号,证明这个参数可以不传。(可选参数必须放在函数参数的末尾)function d(a:number,b?:number):void {}  d(1,2)或d(1)
 *  9-2: 默认参数:和js一样的写法,可以不放在参数最后的位置,但是如果不在最后一个参数的位置,传值的时候没值的情况必须要传undefined,否则会以为少传一个位置的参数
 *      例子: function fn(a:number = 100, b:number):void {} fn()
 *  9-3: 函数赋值:在ts中给变量赋值的时候,因为有类型的限制,所以不能赋值原类型外的值,所以函数类型也一样,只能赋值相同类型的函数
 * 10. interface: 接口(是ts设计出来用于定义对象类型的,可以对对象的形状进行描述。定义interface的时候一般首字母大写)
 *  例子: interface Person { //必填的属性少写多写都会报错
 *      name: string
 *      age: number
 *  }
 *  const p1:Person = {
 *      name: 'hhh',
 *      age: 11
 *  }
 *  10-1. 可选属性,通用,属性名后+?
 *  10-2. 只读属性,如果希望某个属性不被改变,可以在属性前面写readonly 属性名,这个只读属性只能赋值一次,之后无法改变,改变会报错 例子: interface Person { readonly id: number }
 *  10-3. interface可以用来描述函数类型 例子: 
 *      interface IFunc {
 *          (x:number,y:number): number
 *      }
 *      const add:IFunc = (num1,num2) => {
 *          return num1 + num2
 *      }
 *  10-4. 自定义属性(属性必须和类型定义的时候完全一致,如果一个对象上有多个不确定的属性,可以用这个自定义属性,定义属性的key类型和值类型)
 *      例子: interface Read {
 *          [ propName: string ]:string
 *      }
 *      const obj: Read = {
 *          a: 'hello',
 *          b: 'ooo'
 *      }
 *      (如果属性key被定义为数值类型,那么对象就会变成一个类数组,因为key变成了数值,就和下标差不多了,这应该也是对象的键最好不是数值类型的原因)
 *      (所以interface可以描述很多类型,比如描述一个函数,并且另外添加一些属性)
 * 11. 类(ts中通过public,private,prtected三个修饰符来增强js的类。好像这些修饰符并不能真的限制访问,只是在ts层面会报错,但真的编译为js的话,也是可以显示的。。)
 *  11-1:public(公开的属性和方法,实例和子类都可以访问)
 *  11-2:private(私有的属性和方法,只有当前类自己可以访问,子类和实例都不能访问)
 *  11-4: static (静态属性或方法,子类实例都访问不到,只有用当前类才能调用)
 *  11-3:protected(私有的属性和方法,子类可以访问,实例不能访问)
 *      例子: class Person {
 *          public name: string
 *          static age: number
 *          public constructor(name: string) {
 *              this.name = name
 *              this.age = age
 *          }
 *          protected speak() {
 *              console.log(this.name)
 *          }
 *          private talk() {
 *              console.log(this.name)
 *          }
 *      }
 *  11-5:abstract(抽象类,1:抽象类不允许实例化,会报错。  2:抽象类中的抽象方法必须被子类实现,子类不定义这个父类的方法,也会报错)
 *      抽象类的用法是定义一个基类,声明共有属性和方法,拿去被继承。(优点是可以抽离出事物的共性,有利于代码的复用)
 *      例子:
 *          abstract class Animal {
 *              constructor(name:string) {
 *                  this.name = name
 *              }
 *              public name: string
 *              public abstract sayHi():void
 *          }
 *          class Dog extends Animal {
 *              constructor(name: string) {
 *                  super(name)
 *              }
 *              public sayHi() {
 *                  console.log('wang')
 *              }
 *          }
 *          class Cat extends Animal {
 *              constructor(name: string) {
 *                  super(name)
 *              }
 *              public sayHi() {
 *                  console.log('miao')
 *              }
 *          }
 *  11-6: interface也可以约束class,需要implements(实现的意思)关键字
 *      例子:
 *          interface Mu { //interface定义class的形状
 *              playMu(): void
 *          }
 *          class CallPhone implements Mu { //使用implements来实现被定义的形状,interface上定义的属性方法都要在这里实现,不然会报错
 *              playMu() {}
 *          }
 *      (
 *          这里讲的不太清楚
 *              1.interface定义的类,在有new的时候new好像是构造函数,这个时候想用interface就需要使用implements关键字,并且声明的属性是静态属性。
 *              2.没有new的时候,属性好像就成了普通属性,并且调用的时候可以直接用const x: interfaceKey = class className{来调用}
 *      )
 * 12. 枚举(enum,可以依次递增并且反向映射的类型,递增从初始值开始,初始值没有的话就是从0开始)
 *  例子:
 *      enum Direction {
 *          up = 6,
 *          down,
 *          left,
 *          right
 *      }//从初始值6开始递增,Direction.up = 6,Direction.down = 7,Direction.left = 8,Direction.right = 9
 *      //反向映射 Direction[6] = 'up',Direction[7] = 'down'依此类推
 *  12-1:枚举的值也可以手动赋值(
 *      enum p {
 *          buy = 100,
 *          send = 10,
 *          end = 3
 *      }
 * *  12-2: 计算成员(没看出作用)
 *  12-3: 字符串枚举(就是把递增的数值手动赋值成字符串了,说是可以更有语义化,方便调试)
 *  12-4: 常量枚举,const enum Test { A: 'ppp' }   (常量枚举在编译的时候更快,编译形成的代码也更简洁。使用常量枚举的时候不可以使用计算成员,因为常量是固定的)(暂时看不出什么用)
 *  (枚举的意义在于,可以定义一些带名字的常量集合,清晰地表达意图和语义,更容易的理解代码和调试。
 *      常用于和后端联调时,区分后端返回的一些代表状态语义的数字或字符串,降低阅读代码时的心智负担)
 * 13.类型推断(如果写ts时,定义变量却没有给类型,那么ts会根据上下文自动推断数据类型,并且根据该类型对代码加以约束)
 *  (如果声明了数据,却没赋值也没给类型,那么ts会判断为any。如果声明数据没给类型,但是却赋值了,ts会根据这个值的类型为定义类型去约束代码。)
 *  (既然用了ts,就尽量定义好类型,不要依靠类型推断)
 * 14. ts 8种内置类型:string,number,boolean,undefined,null,object,bigint,symbol
 *  14-1. DOM和BOM,比如 HTMLElement,NodeList,MouseEvent
 * 
 * (
 *    枚举(递增,手动赋值,常量枚举),
 *    类(public,private,protected,interface定义class,abstract抽象类),
 *    interface(定义对象或函数),
 *    一些类型,never,void,undefined,null,string,number,object,boolean,unknow,any
 **/
 
  * 高级
 * 1.联合类型:let num: number | string = '' //代表num的类型可以是number或者string
 *  (在联合类型的时候,调用该变量的方法时,这个方法需要是几种联合类型的类型上都包含的方法,不然就需要使用if判断,讲类型分开,各自调用方法)inter
 * 2.交叉类型:如果要对对象进行扩展,可以用交叉类型&
 *  (比如Person有name和age属性,而Student在name和age的基础上还有grade属性,就可以这么写
 *      interface Person {
 *          name: string
 *          age: number
 *      }
 *      type Student = Person & { grade: number }
 *  //Student也是一个interface
 * * 3.类型别名:(给一种或多种组合起来的类型重新命名,
 *      type Name = string;  
 *      type S = number; 
 *      type All = Name | Number;
 *      const x: All = 'ppp' //这个All就是类型别名定义的类型
 *  )
 *  3-1. 类型别名和interface的区别:
 *      1.interface是用于定义对象类型的,可以对对象的形状进行描述
 *      2.type是类型别名,用于给各种类型定义别名,,让ts更简洁,清晰
 *      3.type可以声明基本类型,联合类型,交叉类型,元祖,interface不行
 *      4.interface可合并重复声明,type不行
 *      5.interface可以通过重复声明来合并属性,type不行,type重复定义会报错
 *      6.组合类型或交叉类型的用type,extends或implements用interface
 * 4.类型断言(as,不是很理解)(
        有两种方式
        1.<类型>值 <string>v.length //意思就是确定v是string类型,可以执行string类型相关的任何操作
        2.值 as 类型 return (v as string).length 意义同上
        (在jsx里只认可第二种,所以推荐使用第二种)
    )
 * 5.字面量类型(用一些固定的值当做类型,例子: type BtnStatus = 'mini' | 'small' | 'normal' | 'large')
 * (promise类型,Promise<T> 定义了一个T形状的Promise类型)
 * 6.泛型 (中心思想应该是通过形参来控制类型,让类型更灵活,应该各种类型都可以使用形参,只要是声明类型的地方都可以用形参来代替,所以任何声明类型的地方都可以使用泛型,目前是这么认为的)
 *  例子:
 *      class testClass<T> {
            private data: T[] = []
            constructor() {
                this.data
            }
            pushFunc(data: T): void {
                this.data.push( data )
            }
            pop(): T | undefined {
                return this.data.pop()
            }
        }
        const obj = new testClass<string>()
        obj.pushFunc('sss')
        console.log(obj)
        const obj2 = new testClass<number>()
        obj2.pushFunc(222)
        console.log(obj2)
 *      Array<number>
 *  6-1. type来写泛型没有讲清楚
 *  6-2. interface(
 *      例子:
 *      interface Print<T = string> {
 *          (arg: T): T
 *      }
 *      const myFunc: Print<number> = function<T>(arg: T): T {
 *          return arg
 *      }
 *      const myFunc2: Print = function<T>(arg: T): T {
 *          return arg
 *      }
 *      (这是interface的泛型声明,可以在声明的时候给形参一个默认值,也可以不给)
 *  6-3. 处理多个函数参数(可以通过解构赋值的方法)
 *      例子(两种):  
 *          1.  interface SwapSet<T,U> { //定义一个函数泛型来方便调用去约束函数
 *                  (arr: [T,U]): [U,T]
 *              }
 *              const swap: SwapSet<number,string> = function<T,U>(arr: [T,U]): [U,T] { //使用函数泛型
 *                  return [arr[1],arr[0]]
 *              }
 * 
 *          2.  function swapFunc<T,U>(arr: [T,U]): [U,T] { //定义一个泛型约束的函数
 *                  return [arr[1],arr[0]]
 *              }
 *  6-4. 约束泛型(说白了就是通过给泛型的形参添加一些特殊的属性,来限制形参的类型)
 *      例子:  
 *          interface lengthInterface {
 *              length: number //通过length属性讲形参限制为具有length属性的js类型,比如字符串,数组
 *          }
 *          function testFunc<T extends lengthInterface>(arg: T): void {
 *              console.log(T.length) //如果不通过length的interface给形参添加限制,这里访问length属性就会报错,因为ts不确定这里的具体数据类型,所以有些类型可能没有length属性,ts不想看到这种情况发生,就会报错
 *          }
 *  )
 * 7. keyof(获取目标的所有key,生成一个用这些key组成的联合类型,来约束目标)
 *  例子: interface IPerson {
 *          name:string
 *          age: number
 *        }
 *        type Test = keyof IPerson; //'name' | 'age'
 * 8. T[K] (表示接口T的属性K所代表的类型,‘索引访问’)
 *  例子:  interface IPserson {
 *              name: string
 *              age: number
 *          }
 *          let type1: IPerson['name'] = 'ddd'  //string
 * 9. extends (泛型约束,T extends U,表示泛型变量T可以通过继承某个类型(这里是U)来获得某些属性(U里的所有属性))
 *  例子:  interface ILength {
 *              length: number
 *          }
 *          function getLength<T extends ILength>(arg: T): T {
 *              console.log(arg.length)//因为T继承了ILength的length属性,所以约束力包含了length属性,这里访问length属性也不会报错
 *              return arg
 *          }
 * 10. in(对联合类型进行遍历)
 *  例子: 
 *      type Person = "name" | "school" | "major"
 *      type obj = {
 *          [p in Person]: string //这个[]的作用应该类似于...外面包的[]
 *      }//type obj = { name: string;   school: string;   major: string; }
 * 11. Partial(Partial<T>将T的所有属性映射为可选的,就是在每个key的后面加上?)
 *  实现原理(通过keyof拿到目标所有key的联合类型,in操作符对联合类型进行遍历,便利的时候在末尾加上?)
 *  interface crtItem {
 *      name: string
 *      age: number
 *  }
 *  //type after = Partial<crtItem> //正常使用
 *  type obj<C> = { //原理
 *      [K in keyof C]?: C[K]
 *  }
 * 12. Readonly (Readonly<T>将T的所有属性设置为只读类型)
 *  原理和Partial一样,通过keyof in进行遍历,遍历生成的时候在key前面加入readonly
 *  interface obj {
 *      name: string
 *      age: number
 *  }
 *  //Readonly<obj>
 *  type crtAfter<C> = {//原理
 *      readonly [ K in keyof C ]: C[K]
 *  }
 * 13. Pick (用于抽取对象子集,挑选一组属性并组成一个新的类型)
 *  例子:
 *      interface IPerson {
 *          name: string
 *          age: number
 *          sex: string
 *      }
 *      //type IPersonAfter = Pick<IPerson, 'name' | 'sex'> //正常调用
 *      type IPersonAfter2<CO,CL extends keyof CO> = {
 *          [ K in CL ]: CO[K]
 *      }
 * (搞不清楚exnteds继承一些属性后,嗷,应该确实是增加上面的属性,之前哪里之所以无法添加别的属性,是因为在extends之前,发起extends的对象是空的,所以
 * 那里extends之后才只能添加extends的目标拥有的属性)
 * 14. 条件类型:(extends用作条件判断来完成一系列操作)
 *  14-1. extends(可以配合三目运算符进行一些条件的判断,比如exclude和include)
 *      例子: T extends U ? X : Y //若类型T可以被赋值给类型U,那么结果类型就是X类型,否则就是Y类型
 *          T可以赋值给U的前提是U包含T(这个包含好像只能是联合类型的包含),传入一个确定的值的时候ts可以根据三目运算符给出准确的答案,而如果传了一个联合类型进去,ts也不知道怎么办,所以会
 *              把三目运算符的两个值通过联合类型的方式返回
 *          例子:  
 *          type AB<T> = T extends 'x' ? 'a' : 'b'
 *          type All = AB<'x' : 'y'>//非确定条件,可能是x或y
 *          //这个时候ts也不知道怎么办,只能返回一个'a' | 'b'来作为值
 *  14-2. exclude(意思是不包含,Exclude<T,U>会返回联合类型T中不包含联合类型U的部分)
 *      例子: type Test = Exclude<'a'|'b'|'c','a'> //得到 'b' | 'c'  (原理应该是借助never和extends来实现的,但是现在没有想清楚)
 *  14-3. Extract (取交集,取两个目标中的交集)
 *      例子: type Test = Extract<'a' | 'b', 'b' | 'c'> //获得'b'  (原理应该是借助never和extends来实现的,但是现在没有想清楚)
 * 15. 类型体操:通过类型之间的计算得到新的类型的过程,就是类型体操,不是官方术语,是论坛戏称。(写业务代码的话不太必要去死记硬背,了解逻辑即可,遇到了再去查去积累)
 * 16. declare (当使用第三方库时,很多第三方库不是用ts写的,我们需要引用他的声明文件(.d.ts结尾的文件),才能获得对应的代码补全,接口提示等功能。)
 *  (declare关键字等于告诉ts编译器,这个变量的类型已经在其他地方定义了,你直接拿去用,别报错)
 * 17. .d.ts文件 (一般来说单独存放声明语句的文件),把.d.ts文件放在项目目录中,其他所有.ts文件就都可以获得Vue的类型定义
 * 18. 自己写ts声明文件
 *  加入现在新项目用了ts,要在新项目中继续使用之前不是ts的一些js文件时,有两种选择:
 *      1. 用ts重写,新项目引入重写的js文件
 *      2. 直接引入老的js文件,给他写声明文件
 *  如果选择添加声明文件,这个文件有两种存放方式
 *      1. 存放在node_modules/@types/xxx/index.d.ts文件中,这样存放文件的话不需要额外配置,但是node_modules目录不稳定,代码也不会放在仓库,所以统一代码和版本比较不容易,所以不推荐
 *      2. 在项目里创建一个目录来专门存放这类说明文件(比如types目录),这种方式需要配置下tsconfig.json中的paths和baseUrl字段。
 19. 属性值后面加上! 表示告诉ts这个属性一定有值。
    例子:obj.value!.header = 'fff' //告诉tsobj.value这个属性一定有值