ts各种详解

206 阅读15分钟

// ts 会根据等号右边的内容推到他自身的类型,没有类型就是any , 在ts中:后面跟着的都是类型
// any会导致类型检测失效,能不写any 就不要写any**

// 1.ts中的基础类型  如果用大写的类型 String /Number / Boolean 类, 类也可以充当类型  类类型
// 大写的类型用来描述实例的
// 字符串 数字 number
let str: string = "zf";
let age: number = 100;
let bool: boolean = true;

let str0: string = "zf";
let str1: String = "zf"; // 字符串也是String的一个实例,调用方法的时候会进行装箱
// 把简单类型转换成引用类型 是装箱  'zf' -> new String('zf')
// let str2: String = new String("xxx");
// let str3: string = new String("xxx"); // 我们将一个对象标识成了基本数据类型
// export {}; // 导出后这个变量就属于模块内了,不会被声明在全局上 不会污染全局ts声名

// 2.数组 什么叫数组,存放一类类型的集合
// 标识数组的类型
let arr: (string | number)[] = []; // any[] 表示的是数组里可以放任何类型, any 表示啥都行, 如果数组里存放多个类型可以采用联合类型
arr.push(100);

let item = arr[0]; // 联合类型无法调用具体的方法,只能调用公共的方法  ts重要特性是为了安全发明的


(item as string).length 因为不确定是不是字符串或者数字
// 3.可以通过泛型来约束数组
let arr1: Array<number | string> = []; // 泛型...  当使用这个类型的时候才能确定是什么类型

// 3.在ts中还新增了一种类型 元组 长度类型都是固定的
let tuple: [string, number, boolean] = ["zf", 13, true];
// 元组是可以通过调用数组方法来添加数据的但是要求是必须在这些类型中
tuple.push(123); // 特殊的
即使可以这样操作,
// 4. 默认枚举类型会被编译成对象
// 源码里面表示不同状况的参数
enum AUTH { // 状态码,请求路径 常量 , 可以放置混合类型,但是只有数字后面才能递增
  ADMIN,
  MANGAER = 3,
  USER,
  // ADMIN = 'admin',
  // MANGAER = 'manager',
  // USER = 'user'
}
console.log(AUTH.ADMIN);
// 常量枚举 最终编译后的结果会变成具体的结果, 只要不是数组都不能反举。
AUTH.ADMIN默认是等于0,同样反举的话AUTH[0] = 'ADMIN'

console.log(Object.keys(AUTH).length);
// 5. null undefined
// ts 中的null和undefined可以赋值给任何类型  , 一般在开发的时候尽量严格模式都是开启状态
let un: undefined = undefined;
let nu: null = null;
// 6.void表示返回值为空, 在非严格模式下 null可以赋予给任何类型, undefine是可以兼容void的
function sum(): void {
  return undefined;
}
// 类型无法和值运算
// 7.never 永远达不到的情况  3中情况会出现never  1) 程序出错走不到了 2)死循环  3) 条件永远走不到
function whileTrue(): never {
  while (true) {}
}
function throwError(): never {
  throw new Error();
}
function isType(str: string) {
  if (typeof str === "string") {
  } else {
    str; // 永远总不到就是never
  }
}



function create(o:object){ // 不是基本类型的全部用object用来标识

}
create({})
create([])
create(function(){})

// Symol bigInt

// interface ICiclr {
//   kind: "c";
//   r: number;
// }
// interface ISquare {
//   kind: "s";
//   width: number;
// }
// function asset(obj: never) {}  他是来校验的
// function getArea(val: ICiclr | ISquare) {
//   // ts 为了考虑安全
//   if (val.kind === "c") {
//     return val.r * val.r;
//   }
//   if (val.kind === "s") {
//     return val.width * val.width;
//   }
//   asset(val); // 可以用never 来标识永远不会发生的事情
// }

export {};加一个export表示是一个单独的模块  是不会影响到全局的ts声名,如果不加这个的话和全局的某个ts声名会冲突

let ele: HTMLElement | null = document.getElementById("app"); // 我是知道这个东西使用的

// ! 标识的是非空断言 ts里面的
// ele!.style.background = 'red'; // 这个东西一定不为空, 如果为空那就得自己承担错误,不用ts来检验了

// 断言 类型断言 只能断言已经存在的值

(ele as HTMLElement).style.background = "red"; // 断言后后果自理 ,不要ts的类型校验了
(<HTMLElement>ele).style.background = "red"; // 在使用jsx的时候还是使用as语法

// 考虑采用type关键字来自定义类型
// type是ts中用的时候 多数会配合联合类型来使用
type MyType1 = string | number | boolean | object;
type MyType2 = 'a' | 'b' | 'c' | 'd'; // 表示myType2 只能赋值成a,b,cd 中的4个
let n: MyType2 = 'b'; // 联合类型会根据赋值的值推导出具体的类型






// 不建议使用双重断言,会改变原有类型, 但是好处是可以断言成任何类型
// (ele as any as string).style.background = 'red'; // 断言后后果自理 ,不要ts的类型校验了

// -------------------
// js 本身语法
// ele?.style; // 类似于判断 如果有才能取值没有就直接返回了  如果需要转义低级语法 需要babel

// ?? js预算福
// console.log(null ?? 1); // 只要不是null或者undefined这个值就是ok的 解决的是||的问题,即使是false??1,返回的还是false
// 函数在ts中的声明 , 对于函数来说 我们一般会标识函数的本身类型,参数类型,返回值类型

// 函数关键字来声明
// function a(){} // function关键字声明的函数 无法在去给函数本身添加类型

// 表达式的方式来声明

// 如果你是直接赋值可以省略,如果你要限制sum的类型,在进行赋值, 那就需要写
type MySum = (a: number, b: number) => number;
let sum: MySum = (x, y) => x + y;

// 这里调用时 是根据类型来做提示的
// sum(1,2);

// 函数依然支持可选参数 支持默认参数 和 省略运算符  ? 会被转换成联合运算符但是联合运算必须是必填的
function optionalArguments(x: string, y?: string, z?: string) {}
optionalArguments("a");

function defaultArguments(x: string = "", y: string) {
  // 因为x 可能不能传递 才会出现默认值,x就能是undefined
}
defaultArguments(undefined, "123");

function spreadAruments(...args: number[]) {
  // 因为x 可能不能传递 才会出现默认值,x就能是undefined
}
spreadAruments(1, 2, 3, 4);


// 这边讲一个
interface fn {
  shiyan: () => void; //这个是调用原型上面的方法
  shiyan1():void //这个是调用实例上面的方法
}


// 函数在ts中实现了函数的重载  一个函数可以实现多个功能 。

// 你给我一个字符串我就给你转化一个数组 'abc' => ['a','b','c']
// 给我个数字 12  => [1,2]

// 除了根据= 右边来推断,还会根据函数的返回值进行推断
function toArray(value: string): string[]; // 对string的情况进行了细化
function toArray(value: number): number[]; // 对number的情况进行了细化
function toArray(value: string | number) {
  // 这里是真实的实现
  if (typeof value === "string") {
    return value.split("");
  } else {
    return value.toString().split("").map(Number);
  }
}
// 根据参数或者返回值的不同的 实现了不同的功能
let r1 = toArray("abc");
let r2 = toArray(123);
export {};

class Pointer {
  // x:number = 1 // 在定义x 和 y的时候初始化
  // y:number = 2
  constructor(public readonly x: number = 1, public y: number) {
    // 在构造函数中初始化
       this.x = x;
    //    this.y = y;
  }
  get c() {
    // Object.defineProperty 给原型上增加了一个c属性
    return 1000;
  }
}
let p = new Pointer(1, 2);
console.log(p.x);

// 我们期望把传入的参数直接作为实例属性可以增添public 关键字
// 类的修饰符 public(公开的) 这样操作的话就不用在constructor上面再声名实例属性的类型
protected(受保护的) 表示实例无法访问,但是子类可以访问
private(私有的)  表示实例,子类无法访问
readonly 表示无法修改的

interface IFruit {
  readonly taste: string; // readonly表示仅读属性
  color: string;
  size?: number;
  [xxx:string]:any 任意属性
}

let tomato: IFruit = {
  // 必须包含taste 和 color
  taste: "甜的",
  color: "黄色",
  a: 1,
} as IFruit; // 断言也要考虑安全性 必须得存在在定义的属性中,而且断言后多余的属性不能使用
interface ISpeakChinese {
  // 接口要求都必须是抽象的
  speakChinese: () => void; // 描述有一个属性speakChinese 是一个函数
}
interface ISpeakEnglish {
  speakEnglish: () => void;
}
// 这是实现 要求实现的类必须得有实现
class Speak implements ISpeakChinese,ISpeakEnglish {
    // 用接口来约束属性的返回值时 void表示不校验 不关心返回值
    speakChinese(){
        return ;
    }
    speakEnglish(){
        return 'abc'
    }
}
// 抽象类 就是专门为别人提供继承的接口
abstract class Animal {
  // 不能被new 抽象类可以有非抽象方法
  abstract eat(): void; // 让继承的子类来实现
  abstract name: string;

  drink() { //这个就是全局可以使用的
    console.log("dirnk");
  }
}

class Cat extends Animal {
  name: string = "";
  eat() {}
}
//这个drink表示可有可无
// 泛型 在定义的时候无法确定类型,只有在使用的时候才能确定类型 -》 使用泛型了

interface IArray1<T> { // 形参来接受
    [key:number]:T// “string”、“number”、“symbol
}
// 能用type 用type 用不了就用接口
type IArray<T> = {
    [key:number]:T
}
let arr1:IArray<number> = [1,2,3]; // 实参
let arr2:Array<string> = ['1','2']; // 实参


// 接口中限制类的抽象属性和方法 指代的是类中的原型方法或者实例方法
// 针对属性一样的东西 在ts中,会认为是一样的。 ts中的类型检测叫鸭子类型检测
class Cat {
  constructor(name: string, age: number) {}
}
class Dog {
  constructor(name: string, age: number) {}
}
// 1.类本身也是一个类型 只能描述梳理
// 2.这个变量就是类本身 我们可以把类型取出来使用 类本身的类型取出来

// ts中的类型标准并不会让代码执行, 静态检测
// type MyClazz<T> = {new (name:string,age:number):T}

function createAnimal<T>(clazz: typeof Cat, name: string, age: number) {
  return new clazz(name, age);
} // 这样写是不是完全对的,因为r的类型显示还是 Cat

// 应该这样写
type MyClazz<T> = new (name: string, age: number) => T; // 类型描述 不是真实的new 表示这个变量能被new

function createAnimal<T>(clazz: MyClazz<T>, name: string, age: number) {
  return new clazz(name, age);
}
r显示的类型怎么都是对的
let r = createAnimal(Dog, "Tom", 12);
// ts中的类型标准并不会让代码执行, 静态检测
type ICreateArray  =  <T>(times:number,val:T)=>T[]
或者
interface ICreateArray {
  //  写在接口后面表示使用接口的时候确定类型
  <T>(times: number, val: T): T[]; //  // 写在函数前面当赋予方法的时候才能确定类型定类型
}
这个T的类型是ts推导出来的
const createArray: ICreateArray = (times, val) => {
  let result = [];
  for (let i = 0; i < times; i++) {
    result.push(val);
  }
  return result;
};
let arr = createArray(3, "a");
//这个泛型是推导出来的
function swap<T, K>(tuple: [T, K]): [K, T] {
  // 声明多个参数来使用
  return [tuple[1], tuple[0]];
}

let r1 = swap(["a", 1]); 
泛型与泛型之间是不能相加的,即使直接传值也不行,为什么因为 ts中的类型标准并不会让代码执行, 静态检测,静态检测并不值到T和K 属于什么类型所以相加会报错,解决方案看下面的extends
let r1 = swap<string,number>(["a", 1]); 
interface ICb<T> {
  (item: T, key: number): void;
}
interface IForEach {
  <T>(arr: T[], cb: ICb<T>): void;
}
let forEach: IForEach = (arr, cb) => {
  for (let i = 0; i < arr.length; i++) {
    cb(arr[i], i);
  }
};
forEach([1, 2, 3, 4, 5, "string"], (item) => {});
注意看下这个地方,这里还传了string类型,所以T就是(string | number)

// 约束的概念 就是要求你得具有他的特性,但是不一定和extends后面限制全部一样
function sum<T extends  number|string>(a: T, b: T) { // 两个泛型间不具备相加的能力
    // 把多个类型断言成 一个类型  string | boolean | xxx
    return a as string +b; 
    //这个地方为什么as string可以,因为string和number怎么相加都是string
}
let rr1= sum(1,2); // 我想加的两个数 类型就是一样的


为什么说不一定和extends后面限制全部一样请看
function getFish<T extends {kind:string}>(fish:T){}
getFish({a:1,kind:'鲨鱼'}); // 对传入的参数进行了类型校验
// 我要求必须要有类型 


type MyType = keyof number  MyType就是数字类型上面的方法
type MyAny = keyof any; // string / number / symbol 只有这三个值能作为key
// Record 描述对象的key value格式  对象-> 映射
// type Record<K extends keyof any, V> = { [P in K]: V; } // 就是设计了一个映射的类型  就是任意属性 

function map<K extends keyof any,V,U>(obj:Record<K,V>,fn:(value:V,key:K)=>U ):Record<K,U>{
    let result = {} as Record<K,U>;
    for(let key in obj){
        result[key] = fn(obj[key],key)
    }
    return result
}
let returnValue = map({a:'a',b:'b'},(value,key)=>{  //{a:'a',b:'b'} =>  {a:'aa',b:ba}
    return value+'a'
});
如何合并ts两个接口,下面覆盖上面的
interface IPerson1 {
  name: string;
}
interface IPerson2 {
  name: number;
  age: string;
}
type Diff<T, K> = Omit<T, keyof K>; 
type Merge<T, K> = Diff<T, K> & K;
  // 自定义的类型 全局没有
  namespace fn {
    function extend(): void;
  }
}

declare function $(): {
  html(): void;
  css(key: string, value: string): void;
}; // declare 告诉ts 你可以声明这个变量 但是并不需要初始化

declare global {
// 给ts中已经声明过的类型进行扩展
interface Window {
  a: string;
}
interface Number {
  myFixed(): void;
}
}

// "moduleResolution": "node",  是干嘛的,我调用import语法 文件应该怎么样去解析文件
// "baseUrl": "./", 是表示怎么查找路径的 ./在当前目录下查找
// "paths": {"*":[  我们引入文件 指定文件去哪里找 
//   "types/"
// ]}, 


// 一般是在import a from 'a.vue'
declare module '*.vue' { // 用户引入vue默认引入后拿到的就是a这个变量
  export let a:string
}

declare module '*.gif'

export {} //这个如果没写 就表示是模块内部的
关于ts为什么用类,可以简化很多操作, 尤其深层次嵌套, 可以不用问号 还可以初始化值
class Person {
  name:string = '';
  age:number = 1;
}


class P {
  person: Person = new Person();
}

interface IState {
  test: P;
}

class Button extends React.Component<any, IState> {
  state = {
    test: new P(),
  };
  render() {
    const { test } = this.state;
    return <div>{test.person.name}</div>;
  }
}
interface X {
  (): void;
}
// type ReturnType<F extends (...args:any[])=>any> = F extends (...args:any[])=> infer R ? R: any; // 三元 条件
type MyReturnType = ReturnType<typeof sum>; // 取函数的返回值类型

// sum函数的参数 =》 [a:string,b:string]

type Parameters<F extends (...args: any[]) => any> = F extends (
  ...args: infer R
) => any
  ? R
  : any;
type MyParameters = Parameters<typeof sum>;

class Animal {
  constructor(a: string, b: string, c: number) {}
}

type ConstructorParameters<C extends new (...args: any[]) => any> =
  C extends new (...args: infer R) => any ? R : any;
type MyConstrcutorParams = ConstructorParameters<typeof Animal>;

type InstanceType<C extends new (...args: any[]) => any> = C extends new (
  ...args: any[]
) => infer R
  ? R
  : any;
type MyinstanceType = InstanceType<typeof Animal>;

// infer 就是可以根据代码的位置来推断结果   ReturnType

// node -> webpack  (每个阶段是一个半月)
// vite -》 vue3 + ts实战 -》 vue3源码

// unkown类型 用来取代any的  any是否安全呢?

let a: unknown = 1; // 我们尽量不要标识any 会导致后续的调用出错,如果你只是不知道他的类型,那么你就不要写any 写 unkown ,unkown是any的安全类型

// 任何类型都可以赋予给unkown
// unkown 不能使用

type r1 = unknown | string; // 并集  -》 还是不知道是什么东西
type r2 = unknown & string; // 交叉类型  任何类型和unknown 相交 都是其他类型

type r3 = keyof unknown; // keyof any 可以出现string  number  symbol  keyof unkown=》 never

// never 是unkown的子类型
// type r4 = never extends unknown ? true:false
// // 类型保护  (细分类型)  typeof  instanceof in

// typof
// function isType(val:string | number){
//     if(typeof val === 'string'){
//         val.
//     }else{
//         val.
//     }
// }
class Person {}
class Animal {}
function getType(type: Person | Animal) {
  if (type instanceof Person) {
    type;
  } else {
    type;
  }
}
let person1 = {
  name: "jw",
  high: true,
};
let person2 = {
  name: "zf",
  fat: true,
};

type Zhu = typeof person2;
type Jw = typeof person1;
function getPerson(person:Zhu | Jw){
    if('high' in person){
        person
    }else{
        person
    }
}
// 在ts中又新增了一些叫法   可以通过一个字段来判断两个人 可辨识的类型
interface I1 {
    name: "jw",
    high:string
}

interface I2 {
    name: "zf",
    fat:string
}
// is语法可以根据返回值推断 是不是这个类型

function isJw(person:I1 | I2):person is I1{ // 此方法返回的是true | false
    return person.name === 'jw'
}

// 判断一个类型是不是一个对象 是 、 不是

function getPerson2(person:I1 | I2){
    if(isJw(person)){
        person
    }else{
        person
    }
}



// ts 的兼容性是基于安全来考虑  谁能赋予给谁
"strictFunctionTypes": false, 决定着是否开启双向协变

// 基本类型  -> 联合类型  (对于基本类型而言 )
let a1!: string | number;
let a2!: string | number | boolean;

// 如果我把a2 给了a1    a2 可能是一个bookean , 那么boolean类型可以赋予给string | number
// a1 = a2;

// 出于安全考虑 我们定义的接口类型  ,只要满足这个接口就可以. 没有标识的方法不能被调用
interface IHasToString {
  toString(): string;
}
let str: IHasToString = "hello"; // hello里面具备了各种各样的方法 其中包含了toString

// 接口的兼容性 两个接口 多的是不能赋予给少的

interface IFruit1 {
  color: String;
  taste: String;
  size: number;
}
interface IFruit2 {
  color: String;
  taste: String;
}
let f1!: IFruit1;
let f2!: IFruit2;
// 我约定好 后端返回的数据有5个字段 但是我调用完毕后多了5个

f2 = f1; // 当我们把一个变量赋予给另一个变量的时候 会发生类型兼容性
// f2 = f1

// 函数的兼容性   主要考虑函数的参数和返回值
// 参数在定义的时候 可以声明多个 ,使用的时候 可以小于等于我们的参数个数
// 对于返回值而言 我们可以从安全性考虑
let a = (a: string, b: string): string => "abc";
let b = (a: string): string => "abc";

// a=b
// [].map((item)=>{} )

// 逆变和协变  (参数是逆变的 返回值是协变的) 在使用函数传参的时候 可以传递父亲 返回儿子  (传父返子)
class GrandParent {
  house: string = "房子";
}
class Parent extends GrandParent {
  money: string = "钱";
}
class Son extends Parent {
  play: string = "玩";
}
 
// 类型中的Parent是实例  (传父返子) 函数的参数是逆变的可以传递父亲 函数的返回值是协变的只能传递儿子
function getFn(cb: (val: Parent) => Parent) {

}
// 我传递的参数要保证你能处理 , 返回的结果要求符合你所有的属性


// 可以配置参数 允许双向协变 可以乱传
getFn((val: Parent) => new Parent());



// 传父返子
function getFn1(cb:(val: number | boolean) => string | boolean) {

}

getFn1((val:boolean | number | string):boolean => false)

// 枚举的结果结果永远无法兼容
enum A {
  
}
enum B{

}
let c2:A;
let d2:B;

//  类的兼容 只要你有的我有就可以
class Animal {
  private name:string
}
class Person {
  private name:string
}
let animal:Animal = new Animal
let person :Person = new Person
// animal = person; // 比较的是实例 , 如果两个人带有修饰器 除public之外的永远都不相等



// 装饰器是修饰类的  可以修饰类中的属性和方法 , 函数存在变量提升所以无法进行修饰

// 装饰器可以实现 同一个装饰器修饰不同的类  扩展

function addSay(target:{new (...args:any[]):any}){
  target.prototype.say = function(){
      console.log('say')
  }
}

function toUpperCase(flag:boolean){ // 为了方便可以传递参数
  return function(target:any,key:string){ // 装饰器函数
      let value = ''
      Object.defineProperty(target,key,{ // 给原型先定义属性
          get(){
              return flag? value.toUpperCase(): value.toLowerCase()
          },
          set(newValue:string){
              value = newValue
          }
      })
  }
}

function beforeDrink(str:string){
  return function(target:any,key:string,descripor:PropertyDescriptor){
      let oldValue = descripor.value;
      descripor.value = function(){
          console.log(str);
          oldValue.call(target);
      }
  }
}


// addSay(person)  @语法糖
@addSay
class Person {
  // say!:()=>void
  @toUpperCase(true)
  name:string = 'ZS'; // 实例上的还是原型上的 

  @beforeDrink('我渴了')
  drink(){
      console.log('喝水')
  }
}
// 可以扩展静态的属性和原型方法的参数 。。。
let person = new Person();
person.drink()