typescript(下)--个人学习笔记

134 阅读13分钟

泛型

在TS中,泛型是在函数、类或接口中使用一个占位符来代表一种类型,这个占位符可以在调用时用具体的类型来替换。泛型可以用来定义参数、返回值或类成员的类型。 泛型可以使用尖括号(<>)来表示

可以通过以下方式在TypeScript中使用泛型:

在函数中使用泛型:

function identity<T>(arg: T): T | undefind {
    return arg;
}

  let output = identity("hello");//自动判断类型,一旦判断成功,函数中的占位值都为该类型

let output = identity<string>("hello");//也可以手动给泛型赋值
console.log(output); // 输出:hello

let output2 = identity<number>(10);
console.log(output2); // 输出:10

在类中使用泛型:

class GenericClass<T> {
    private data: T;

    constructor(value: T) {
        this.data = value;
    }

    getValue(): T {
        return this.data;
    }
}

let genericObj = new GenericClass<number>(10);
console.log(genericObj.getValue()); // 输出:10

let genericObj2 = new GenericClass<string>("hello");
console.log(genericObj2.getValue()); // 输出:hello

在接口中使用泛型:

interface GenericInterface<T> {
    value: T;

    get(): T;
}

let obj: GenericInterface<number> = {
    value: 10,
    get() {
        return this.value;
    }
};

console.log(obj.get()); // 输出:10

let obj2: GenericInterface<string> = {
    value: "hello",
    get() {
        return this.value;
    }
};

console.log(obj2.get()); // 输出:hello

泛型的限制条件

在TypeScript中,可以使用泛型来对参数类型或返回类型进行约束,并且可以设置一些限制条件。

以下是一些常见的泛型限制条件:

类型约束

可以使用extends关键字来约束泛型类型的范围。例如,T extends number表示T必须为number类型或其子类型。

function identity<T extends number>(arg: T): T {
  return arg;
}


function identity<T extends {length:number}>(arg: T): T {
  return arg.length;
}
//限制泛型使其必须有length属性

多个类型参数之间的|

可以使用|符号来将多个类型约束合并在一起。例如, <number | string>表示T必须同时为number类型或string类型。

function combine<T | T>(arg1: T[], arg2: T[]): T[] {
  return arg1 。concat(arg2);
}


const arr=combine<string|number>(["zs"],[1,2,3])//这种结果可以是字符串,可以是数字,可以是混合的

多个类型参数之间的约束&

可以使用&符号来将多个类型约束合并在一起。例如,T extends number & string表示T必须同时为number类型和string类型。

function combine<T extends number & string>(arg1: T, arg2: T): T {
  return arg1 + arg2;//这种结果可以是只能是字符串,或者只能是数字
}
console.log(combine(1, 2)); // 输出:3
console.log(combine("Hello", "World")); // 输出:HelloWorld
console.log(combine(1, "Hello")); // 报错:Argument of type '1' is not assignable to parameter of type 'number & string'.

keyof约束

keyof关键字用于约束泛型类型必须为某个类型的键名。例如,T extends keyof SomeType表示必须为SomeType类型中的键名之一

type Person = {
  name: string;
  age: number;
}

function getProperty<T extends keyof Person>(obj: Person, key: T): Person[T] {
  return obj[key];
}

构造函数约束

可以使用new关键字来约束泛型类型必须为一个构造函数。

interface Constructable {
  new (...args: any[]): any;
}

function createInstance<T extends Constructable>(ctor: T, ...args: any[]): InstanceType<T> {
  return new ctor(...args);
}

以上是一些常见的泛型限制条件的示例,你可以根据具体的需求使用适合的限制条件来约束泛型类型。在TypeScript中,可以使用泛型来对参数类型或返回类型进行约束,并且可以设置一些限制条件。

内置对象

JavaScript 中有很多内置对象,它们可以直接在TypeScript 中当做定义好了的类型。

1、ECMAScript的内置对象 Number、Date、Boolean、String、RegExp、Error

let num : Number = new Number(1);
let date : Date = new Date();
let b : Boolean = new Boolean(true);
let s : String = new String;
let reg : RegExp = new RegExp(/\w/);
let error : Error = new Error('错误');
let xhr : XMLHttpRequest = new XMLHttpRequest();
    

HTMLElement:表示 HTML 元素节点,是其他所有 HTML 元素的基类如 innerHTMLclassNamestyle 等。一般像section,header这类语义化的标签也是在这类

HTML(标签名)ElementHTMLInputElement表示 <input> 标签,。一般有功能性html标签的对象是Html(元素标签名)Element。如果记不住,直接as Element断言

//DOM
//HTML(元素名称)Element   HTMLElement
let body : HTMLElement = document.body;
 
let allDiv:NodeList = document.querySelectorAll('div');
let div = document.querySelector('div');
//还可以有以下写法:
let div1 :NodeListOf<HTMLDivElement | HTMLElement> = document.querySelectorAll('div');


//BOM
let local:Storage = localStorage;
let lo:Location = location;

let promise:Promise<number> = new Promise((r) =>r(1));
promise.then(res=>{
  res.toString
})

let cookie:string = document.cookie;

声明文件 declare

当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。

declare var 声明全局变量
declare function 声明全局方法 declare class 声明全局类 declare enum 声明全局枚举类型 declare namespace 声明(含有子属性的)全局对象 interface 和 type 声明全局类型

下载声明文件

让我们去下载他的声明文件, @types是规范,如果有声明文件必然是 @types开头

npm install @types/node -D

自己手写声明文件

如果有一些第三方包确实没有声明文件就自己去定义名称.d.ts 创建一个文件去声明,这里需要对接口足够的了解

案例手写声明文件

index.ts

import express from 'express'
 
 
const app = express()
 
const router = express.Router()
 
app.use('/api', router)
 
router.get('/list', (req, res) => {
    res.json({
        code: 200
    })
})
 
app.listen(9001,()=>{
    console.log(9001)
})
express.d.ts

declare module 'express' {
    interface Express {
        (): App
        Router(): Router
    }
    
     interface App {
        use(path: string, router: any): void
        listen(port: number, cb?: () => void): void
    }
    
    interface Router {
        get(path: string, cb: (req: any, res: any) => void): void
    }
   

    const express: Express
    export default express
}

合并

浅拷贝和深拷贝的区别

浅拷贝:浅拷贝会创建一个新的对象,但该对象的属性仍然是原始对象的引用。这意味着当修改原始对象的属性时,浅拷贝的对象也会受到影响。浅拷贝可以通过 Object.assign() 或者扩展运算符 ... 来实现。 创建了新栈地址,但是堆中指向的是一个位置

let obj1 = { name: 'Tom', age: 20 };
let obj2 = Object.assign({}, obj1);
console.log(obj2); // { name: 'Tom', age: 20 }

obj1.age = 30;
console.log(obj2); // { name: 'Tom', age: 30 }

深拷贝:深拷贝会创建一个新的对象,并且该对象的属性也是新创建的。这意味着当修改原始对象的属性时,深拷贝的对象不会受到影响。深拷贝可以通过递归复制对象和使用 JSON.parse(JSON.stringify()) 方法来实现。 创建了新栈地址,同时创建新堆,虽然内容是一样,但是不是一个堆了

let obj1 = { name: 'Tom', age: 20 };
let obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj2); // { name: 'Tom', age: 20 }

obj1.age = 30;
console.log(obj2); // { name: 'Tom', age: 20 }

需要注意的是,当对对象进行深拷贝时,如果对象中包含了函数、正则表达式等特殊类型,这些特殊类型在深拷贝过程中会丢失。

对象的合并

interface A{
  name:string,
}
interface B{
  age:number,
}
let a:A= {name:'123'}
let b:B= {age:123}

// 合并扩展运算符,下面两个效果一样的,{ "name": "123", "age": 123 }
//扩展运算符是浅拷贝,返回的是新对象
let c= {...a, ...b}
let d:A&B= {...a,...b}

//Object.assign 合并对象,返回的是新对象
//这个返回的是个交叉类型,和let d:A&B= {...a,...b}效果一样
let e= Object.assign({}, a, b)

//深拷贝,这个方法比较新。老版本不一定支持
let f=structuredClone(c)

类方法的合并

//插件形式混入
//创建三个类
class Logger {
  log(msg: string) {
    console.log(msg)
  }
}
class Html {
  render() {
    console.log('render')
  }
}

class App {
  run() {
    console.log('run')
  }
}

//声明下类型
type Custructor<T> = new (...args: any[]) => T

//注入原理
//将主函数放入混入函数中
//返回一个继承主函数的新类
//在新类中创建要注入函数的实例,并将要注入的函数以实例方法的形式被新类的方法调用
//从而达到注入的效果中



function pluginMinxins<T extends Custructor<App>>(Base: T) {
  //返回一个继承主函数的新类
  return class extends Base {
    //在新类中创建要注入函数的实例
    private Logger = new Logger()
    private Html = new Html()
    constructor(...args: any[]) {
      //调用主函数的构造函数,获得主类中的方法
      super(...args)
      //初始化
      this.Logger = new Logger()
      this.Html = new Html()
    }
    run() {
      //要注入的函数以实例方法的形式被新类的方法调用
      this.Logger.log('run')
      this.Html.render()
    }
    html() {
      this.Logger.log('123')
    }
    log() {
      this.Logger.log('123')
    }
  }
}

const minxins = pluginMinxins(App)
const newapp = new minxins()
newapp.run()
newapp.html()
newapp.log()

修饰器也叫装饰器

在tsconfig.json开启装饰器,

{ 
    "compilerOptions": {
        "experimentalDecorators": true, /* 启用装饰器 */  
        "emitDecoratorMetadata": true /* 发射装饰器元数据 
        "target": "ES5" 
        } 
}

类装饰器

要给装饰器添加ClassDecorator类,这样就能用注解@base捕捉到下面的类,及target,便可以直接向目标类原型链中添加属性和方法,而不用在目标类中修改

const base :ClassDecorator = (target) => {
//像目标类添加的属性
  target.prototype.name = "zs";
};


class b {
  
}
@base
const b = new B()as any;
//base(B)

//@a其实就相当于在在下面base(B);
//在一些不能使用修饰符的场景可以这么写
console.log(b.name);
//输出zs

装饰器工厂

(下面代码没执行成功,不知道啥原因,但是理论是这样的)

// 如果需要参数,就要柯里化函数
const base  = (name:string) => {
  const fn:ClassDecorator=(target) => {
    target.prototype.name1 = name;
  }
  return fn;
};


class B {
  
}
@base("123")
const b = new B()as any;
console.log(b.name1);


方法装饰器

TypeScript中的方法装饰器用于在运行时动态地修改类方法的行为。方法装饰器可以在调用方法前后进行一些操作,例如日志记录、参数验证等。

方法装饰器的语法如下:

const methodDecorator:MethodDecorator=(target, Key, descriptor) {
    // 在这里修改方法的行为
}

参数解释:

  • target:被装饰的类的原型对象,也就是类的实例的原型链上的对象。
  • Key:被装饰的方法的名称。
  • descriptor:被装饰方法的属性描述符。及是修饰器方法返回的,原来的方法的返回会被它替代

下面是一个示例,演示如何使用方法装饰器来打印方法的调用日志:

    const log:MethodDecorator = (target, Key, descriptor: PropertyDescriptor)=> {
    const originalMethod = descriptor.value;
  
    descriptor.value = function(...args: any[]) {
        console.log(`Calling method: ${Key}`);
        const result = originalMethod.apply(this, args);
        console.log(`Method returned: ${result}`);
        return result;
    };
  
    return descriptor;
}

class MyClass {
    @log
    greet(name: string) {
        return `Hello, ${name}!`;
    }
}

const instance = new MyClass();
instance.greet('John');

在上面的示例中,@log装饰器被应用到greet方法上。当greet方法被调用时,装饰器会在方法调用前后分别打印日志信息。输出结果如下:

Calling method: greet
Method returned: Hello, John!

参数装饰器

key:被修饰的方法名 index:被修饰的方法的参数在的索引

const logParameter:ParameterDecorator=(target: any, key: string, index: number) => {
    const originalMethod = target[key];
    target[key] = function(...args: any[]) {
        const parameterValue = args[index];
        console.log(`Parameter value: ${parameterValue}`);
        return originalMethod.apply(this, args);
    }
}

class MyClass {
    // 使用装饰器修饰参数
    greet(@logParameter message: string) {
        console.log(message);
    }
}

const myInstance = new MyClass();
myInstance.greet("Hello World"); // 输出:Parameter value: Hello World   Hello World

在这个例子中,logParameter装饰器被应用于greet方法的参数上。装饰器函数会被调用,并传入三个参数:类的原型(target)、方法名(key)、参数的索引值(index)。

当我们调用myInstance.greet("Hello World")时,装饰器会首先打印参数的值,然后再执行原始的方法并输出"Hello World"。

属性装饰器

const decorator:ProperstyDecorator(target: Object, propertyKey: string) =>{
  // 在属性的getter中添加额外逻辑
  const getter = function () {
    console.log(`Getting value of ${propertyKey}`);
    return this[propertyKey];
  };

  // 在属性的setter中添加额外逻辑
  const setter = function (val: any) {
    console.log(`Setting value of ${propertyKey} to ${val}`);
    this[propertyKey] = val;
  };

  // 重新定义属性的getter和setter
  Object.defineProperty(target, propertyKey, {
    get: getter,
    set: setter,
    enumerable: true,
    configurable: true,
  });
}

class Example {
  @decorator
  name: string;

  constructor(name: string) {
    this.name = name;
  }
}

const example = new Example('John');
example.name = 'Mike'; // 设置属性值时会调用setter
console.log(example.name); // 获取属性值时会调用getter

定义了一个箭头函数装饰器decorator,它接收targetpropertyKey作为参数。在装饰器函数内部,我们通过重新定义属性的getter和setter来添加额外的逻辑。当访问属性值时,会调用getter函数,而当设置属性值时,会调用setter函数。

Example类中,使用@decoratorname属性装饰起来。当创建一个Example实例并设置name属性时,会触发装饰器中的setter函数。当我们获取name属性时,会触发装饰器中的getter函数。

proxy和Reflect

简单用例

let person ={name:"zs", age:20}

console.log(person.name)
console.log(Reflect.get(person, "name",person) )//和上面的结果一样
// Reflect,类似代理,不过他是直接使用,第三个参数保证了上下文的一致性,不太明白啥意思,懂得用就行
console.log(Reflect.set(person, "name","ls",person) )//set会返回一个布尔值



// proxy只能接受引用类型,对象,数组,函数,set,map之类的
let personProxy=new Proxy(person,{
    // get方法拦截取值的操作
    get(target,key,receive){
        if(target.age<18){
            return Reflect.get(target,key,receive)
        }else{
            return `${target.name}成年了`
        }
    },



    // 拦截赋值操作,需要返回一个布尔值,所以可以和reflect结合使用
    // receive保证上下文的正确,传给自身的
    set(target,key,value,receive){

        return Reflect.set(person, "name","wu",person) 

    },
    // 拦截函数的调用
    apply(){
        console.log("该函数被调用了")
    },
    // 拦截in操作符
    has(){
        console.log("in操作被调用了");
        return true;
    },
    // 拦截for in
    // ownKeys(){
       
    // },
    // // 拦截new操作符的
    // construct(){

    // },
    // // 拦截删除操作符
    // deleteProperty(){

    // }
})
  var a=1;
  "a" in window;
  
  console.log(personProxy.name)

方法用例

  1. apply(target, thisArg, argArray) 用法举例:

    const target = (...args) => console.log(...args);
    const handler = {
      apply: function(target, thisArg, argArray) {
        console.log(`Calling ${target.name} with arguments: ${argArray.join(", ")}`);
        return target.apply(thisArg, argArray);
      }
    };
    const proxy = new Proxy(target, handler);
    proxy("Hello", "World"); //输出:Calling target with arguments: Hello, World | Hello World
    
  2. construct(target, argArray, newTarget) 用法举例:

    class Person {
      constructor(name, age) {
        this.name = name;
        this.age = age;
      }
    }
    const handler = {
      construct: function(target, argArray, newTarget) {
        console.log(`Creating a new instance of ${target.name} with arguments: ${argArray.join(", ")}`);
        return new target(...argArray);
      }
    };
    const proxy = new Proxy(Person, handler);
    const person = new proxy("John", 30); //输出:Creating a new instance of Person with arguments: John, 30
    console.log(person); //输出:Person { name: 'John', age: 30 }
    
  3. defineProperty(target, propertyKey, attributes) 用法举例:

    const obj = {};
    const handler = {
      defineProperty: function(target, propertyKey, attributes) {
        console.log(`Defining property ${propertyKey}`);
        return Reflect.defineProperty(target, propertyKey, attributes);
      }
    };
    const proxy = new Proxy(obj, handler);
    proxy.name = "John"; //输出:Defining property name
    console.log(proxy.name); //输出:John
    
  4. deleteProperty(target, propertyKey) 用法举例:

    const obj = { name: "John", age: 30 };
    const handler = {
      deleteProperty: function(target, propertyKey) {
        console.log(`Deleting property ${propertyKey}`);
        return Reflect.deleteProperty(target, propertyKey);
      }
    };
    const proxy = new Proxy(obj, handler);
    delete proxy.name; //输出:Deleting property name
    console.log(proxy); //输出:{ age: 30 }
    
  5. get(target, propertyKey, receiver) 用法举例:

    const obj = { name: "John", age: 30 };
    const handler = {
      get: function(target, propertyKey, receiver) {
        console.log(`Getting property ${propertyKey}`);
        return Reflect.get(target, propertyKey, receiver);
      }
    };
    const proxy = new Proxy(obj, handler);
    console.log(proxy.name); //输出:Getting property name | John
    
  6. getOwnPropertyDescriptor(target, propertyKey) 用法举例:

    const obj = { name: "John", age: 30 };
    const handler = {
      getOwnPropertyDescriptor: function(target, propertyKey) {
        console.log(`Getting descriptor for property ${propertyKey}`);
        return Reflect.getOwnPropertyDescriptor(target, propertyKey);
      }
    };
    const proxy = new Proxy(obj, handler);
    console.log(Object.getOwnPropertyDescriptor(proxy, "name")); //输出:Getting descriptor for property name | { value: 'John', writable: true, enumerable: true, configurable: true }
    
  7. getPrototypeOf(target) 用法举例:

    class Person {
      constructor(name, age) {
        this.name = name;
        this.age = age;
      }
    }
    const handler = {
      getPrototypeOf: function(target) {
        console.log(`Getting prototype of ${target.name}`);
        return Reflect.getPrototypeOf(target);
      }
    };
    const proxy = new Proxy(Person, handler);
    console.log(Object.getPrototypeOf(proxy)); //输出:Getting prototype of Person | [Function: Object]
    
  8. has(target, propertyKey) 用法举例:

    const obj = { name: "John", age: 30 };
    const handler = {
      has: function(target, propertyKey) {
        console.log(`Checking if property ${propertyKey} exists`);
        return Reflect.has(target, propertyKey);
      }
    };
    const proxy = new Proxy(obj, handler);
    console.log("name" in proxy); //输出:Checking if property name exists | true
    
  9. isExtensible(target) 用法举例:

    const obj = { name: "John", age: 30 };
    const handler = {
      isExtensible: function(target) {
        console.log(`Checking if object is extensible`);
        return Reflect.isExtensible(target);
      }
    };
    const proxy = new Proxy(obj, handler);
    console.log(Object.isExtensible(proxy)); //输出:Checking if object is extensible | true
    
  10. ownKeys(target) 用法举例:

    const obj = { name: "John", age: 30 };
    const handler = {
      ownKeys: function(target) {
        console.log(`Getting own property keys`);
        return Reflect.ownKeys(target);
      }
    };
    const proxy = new Proxy(obj, handler);
    console.log(Object.keys(proxy)); //输出:Getting own property keys | [ 'name', 'age' ]
    
  11. preventExtensions(target) 用法举例:

    const obj = { name: "John", age: 30 };
    const handler = {
      preventExtensions: function(target) {
        console.log(`Preventing object from being extended`);
        return Reflect.preventExtensions(target);
      }
    };
    const proxy = new Proxy(obj, handler);
    Object.preventExtensions(proxy); //输出:Preventing object from being extended
    
  12. set(target, propertyKey, value, receiver) 用法举例:

    const obj = { name: "John", age: 30 };
    const handler = {
      set: function(target, propertyKey, value, receiver) {
        console.log(`Setting property ${propertyKey} to ${value}`);
        return Reflect.set(target, propertyKey, value, receiver);
      }
    };
    const proxy = new Proxy(obj, handler);
    proxy.name = "Jane"; //输出:Setting property name to Jane
    console.log(proxy.name); //输出:Jane
    
  13. setPrototypeOf(target, prototype) 用法举例:

    class Person {
      constructor(name, age) {
        this.name = name;
        this.age = age;
      }
    }
    const handler = {
      setPrototypeOf: function(target, prototype) {
        console.log(`Setting prototype of ${target.name}`);
        return Reflect.setPrototypeOf(target, prototype);
      }
    };
    const proxy = new Proxy(Person, handler);
    Object.setPrototypeOf(proxy, {});
    console.log(Object.getPrototypeOf(proxy)); //输出:Setting prototype of Person | {}
    

    协变和逆变

interface A{
    name:string
    age:number
}

interface B{
    name:string
    age:number
    sex:string
}

let a:A={
    name:"zs",
    age:18
}

let b:B={
    name:"ls",
    age:20,
    sex:"男"
}
// 协变,只要子类型的属性能覆盖主类型里的所有属性就可以直接赋值
a=b


//逆变,在值中是协变,在函数中是逆变
let fna=(parmas:A) =>{

}

let fnb=(parmas:B) =>{
    
}

// fna=fnb 是出错的
fnb=fna
// 这样就没问题,因为最终还是fna在执行,其实道理和协变是一样的

泛型工具

TS泛型工具是指在TypeScript中可以使用的一些泛型工具类型。泛型工具类型是一些内置的类型,可以用来操作和处理泛型类型。以下是一些常用的TS泛型工具类型:

Partial<T>:将类型T的所有属性设置为可选属性。

Required<T>:将类型T的所有属性设置为必选属性。

Readonly<T>:将类型T的所有属性设置为只读属性。

Pick<T, K>:从类型T中选择指定属性K到一个新的类型。拿取一个使用

Omit<T, K>:从类型T中删除指定属性K到一个新的类型。删一个

Exclude<T, U>:从类型T中排除类型U的所有属性。删很多

Extract<T, U>:从类型T中提取类型U的所有属性。拿取很多使用

NonNullable<T>:从类型T中排除null和undefined的类型。

ReturnType<T>:获取函数类型T的返回值类型。

Parameters<T>:获取函数类型T的参数类型。

常见泛型工具的用例及原理

Partial<T>

将类型T的所有属性设置为可选属性。

interface User{
    name:string
    age:number
    sex:string
    address:string
}

type PartiaUser=Partial<User>

// 作用相当于
// type PartiaUser = {
//     name?: string | undefined;
//     age?: number | undefined;
//     sex?: string | undefined;
//     address?: string | undefined;
// }



// 原理就是遍历类型对象
interface User1{
    name:string
    age:number
    sex:string
    address:string
}

// T是类型数组,P是数组的key
type CoustomPartial<T>={
    [P in keyof T]?:T[P]
}

type PartiaUser1=Partial<User1>

Required<T>

将类型T的所有属性设置为必选属性。

interface User{
    name?:string
    age?:number
    sex?:string
    address?:string
}

type RequiredUser=Required<User>

// 作用相当于
// type RequiredUser = {
//     name: string
//     age: number
//     sex: string
//     address: string
// }



// 原理就是遍历类型对象
interface User1{
    name?:string
    age?:number
    sex?:string
    address?:string
}

// T是类型数组,P是数组的key
type CoustomRequired<T>={
//将问号除去
    [P in keyof T]-?:T[P]
}

type RequiredUser1=Required<User1>

Pick<T, K>

从类型T中选择指定属性K到一个新的类型。

interface User{
    name:string
    age:number
    sex:string
    address:string
}

type PickUser=Pick<User,"name">

// 作用相当于
// type PickUser = {
//     name: string;
// }




interface User1{
    name:string
    age:number
    sex:string
    address:string
}

// T是类型数组,P是数组的key
type CoustomPick<T,K extends keyof T>={
    [P in K]:T[P]
}

type PartiaUser1=CoustomPick<User1,"name">

ReturnType<T>

获取函数类型T的返回值类型。 实现原理

type CustomReturnType<F extends Function>=F extends (...args:any[])=>infer Res?Res:never 
const fn = ()=>false
type PartiaUser1=CustomReturnType<typeof fn>

这个类型接受一个泛型参数 F,它必须是 Function 类型或其子类型。然后,它使用条件类型来检查 F 是否可以赋值给一个具有任意参数和返回类型 Res 的函数类型。如果可以,则 Res 被推断为函数的返回类型;如果不能,则返回 never 类型。通过 typeof fn获取了 fn 函数的类型(即 () => boolean)。然后,我们将这个类型传递给 CustomReturnType,以获取函数的返回类型。在这种情况下,CustomReturnType<typeof fn>会推断出 boolean 类型。

Exclude<T, K>

从类型T中排除类型U的所有属性

type T = 'a' | 'b' | 'c' | 'd';  
type U = 'b' | 'c';  
  
type Excluded = Exclude<T, U>;  
// Excluded 类型将会是 'a' | 'd'

在上面的例子中,T 是一个联合类型,包含了四个字符串字面量类型 'a', 'b', 'c', 'd'。U 也是一个联合类型,包含了 'b' 和 'c'。Exclude<T, U> 的作用就是从 T 中排除掉所有可以赋值给 U 的类型,即 'b' 和 'c',因此 Excluded 类型的结果将是 'a' | 'd'。

never在联合类型中会被排除的

type Fruits = 'apple' | 'banana' | 'cherry' | 'date';  
type Berries = 'cherry' | 'blueberry' | 'raspberry';  
  
// T 是源类型,U 是要排除的类型  
type ExcludeFruits<T extends string, U extends string> = T extends U ? never : T;  
  
// 使用 ExcludeFruits 来模拟 Exclude 的行为  
type NonBerryFruits = ExcludeFruits<Fruits, Berries>;  
  
  
  
  
// 分解 ExcludeFruits 的实现,来更清晰地展示其工作原理  
// 首先,我们定义一个条件类型,检查 T 是否可以赋值给 U  
type IsExcluded<T extends string, U extends string> = T extends U ? true : false;  
  
// 然后,我们使用映射类型来过滤出不是 U 的类型  
type FilteredFruits = {  
    [F in Fruits]: IsExcluded<F, Berries> extends true ? never : F;  
};  
  
// 最后,我们使用类型查询来提取出 FilteredFruits 中的所有非 never 的类型  
type NonBerryFruitsFinal = FilteredFruits[Fruits];  
  
// NonBerryFruitsFinal 类型将会是 'apple' | 'banana' | 'date'

Record的用法。

首先,Record 工具类型允许我们为对象的键和值分别指定类型。它的定义形式为 Record<K, T>,其中 K 是键的类型,而 T 是值的类型。

下面是一个简单的例子,我们定义了一个 PersonRecord 类型,其中键是字符串类型,而值是 Person 类型:

	interface Person {  

	  name: string;  

	  age: number;  

	}  

	  

	type PersonRecord = Record<string, Person>;  

	  

	const people: PersonRecord = {  

	  alice: { name: 'Alice', age: 25 },  

	  bob: { name: 'Bob', age: 30 },  

	};

在这个例子中,PersonRecord 是一个对象类型,它的键是字符串(如 "alice" 和 "bob"),而每个键对应的值都是 Person 类型的一个实例。这样,当我们创建 people 对象时,TypeScript 编译器会检查每个属性的键是否是字符串类型,以及每个属性的值是否符合 Person 类型的定义。

Record 工具类型的另一个强大之处是,我们可以使用联合类型来定义键的类型,这意味着对象的键可以是多个不同类型中的任何一个。例如:

	type Key = 'id' | 'name' | 'age';  

	type PersonPartialRecord = Record<Key, string | number>;  

	  

	const partialPerson: PersonPartialRecord = {  

	  id: 1,  

	  name: 'Charlie',  

	  // age 可以省略,因为 Record 的属性不是必需的  

	};

Key 是一个联合类型,它包含了可能的键:'id'、'name' 和 'age'。PersonPartialRecord 是基于 Key 和 string | number 定义的 Record 类型。当我们创建 partialPerson 对象时,我们只需要提供 Key 中定义的一个或多个键,并且每个键的值可以是字符串或数字。注意,Record 类型的属性不是必需的,所以在这个例子中,我们可以选择性地省略 age 属性。

此外,Record 类型还可以与泛型结合使用,定义一个泛型函数,它接受一个 Record 类型的对象作为参数,并对该对象进行处理:

	function printRecord<T>(record: Record<string, T>): void {  

	  for (const key in record) {  

	    console.log(`${key}: ${record[key]}`);  

	  }  

	}  

	  

	const numbers: Record<string, number> = { one: 1, two: 2 };  

	printRecord(numbers); // 输出: one: 1, two: 2  

	  

	const strings: Record<string, string> = { hello: 'world', foo: 'bar' };  

	printRecord(strings); // 输出: hello: world, foo: bar

printRecord 函数是一个泛型函数,它接受一个 Record<string, T> 类型的参数 record。这意味着 record 的键是字符串类型,而值可以是任何类型 T,这个类型在调用 printRecord 函数时由传入的参数决定。我们分别传入了一个数字类型的 Record 和一个字符串类型的 Record 来演示泛型函数的用法。

any的妙用

type CoustomRecord<K extends string|number|symbol,T>={
    [P in K]:T
}
//一般像这string|number|symbol三个类型,经常在key中用到,写起来还挺长,可以巧用any实现
type Objkey =key of any
//`key of` 是一种类型查询操作,它用于获取对象类型的所有键的类型。
type CoustomRecord<K extends Objkey,T>={
    [P in K]:T
}

infer

使用

infer 关键字在 TypeScript 的条件类型(Conditional Types)中用于推导泛型参数的具体类型。当你在 extends 关键字后面使用 infer 时,TypeScript 会尝试匹配给定的类型,并推导出 infer 后面声明的变量的具体类型。

在你提供的代码中,你定义了一个 GetPromisetype 类型,它使用条件类型来检查传入的类型 T 是否是 Promise 类型。如果是,它会推导出 Promise 中包裹的类型 U,并将其作为结果返回;如果不是,它会直接返回 T

对于嵌套的 Promise 类型,你使用了递归的方式来逐层解开 Promise 的包装,直到找到最终的类型。这是通过递归调用 GetPromisetyp1 类型来实现的,每次递归调用都会检查当前的类型是否是 Promise 类型,并如果是的话,继续解开下一层。

下面是你提供的代码的详细解释:

interface User { 
    name: string,  
    age: number  
}  

// 定义一个简单的Promise<User>类型  
type Promisetype = Promise<User>;  

// 定义一个GetPromisetype类型,它使用条件类型来检查T是否是Promise类型  
// 如果是,则使用infer推导出Promise中的类型U,并返回U;否则直接返回T  
type GetPromisetype<T> = T extends Promise<infer U> ? U : T;  
	  
// 使用GetPromisetype类型来获取Promisetype中的实际类型,即User  
type A = GetPromisetype<Promisetype>; // A的类型是User  

// 定义一个嵌套的Promise<Promise<User>>类型  
type Promisetype1 = Promise<Promise<User>>;  

// 定义一个递归的GetPromisetyp1类型,用于逐层解开嵌套的Promise  
type GetPromisetyp1<T> = T extends Promise<infer U> ? GetPromisetyp1<U> : T;  
	  

// 使用GetPromisetyp1类型来获取Promisetype1中的最终实际类型,即User  
type A1 = GetPromisetyp1<Promisetype1>; // A1的类型是User

在这个例子中,A 的类型是 User,因为 Promisetype 是一个 Promise<User> 类型,所以 GetPromisetype<Promisetype> 推导出 User 类型。

对于 A1,Promisetype1 是一个嵌套的 Promise<Promise<User>> 类型。GetPromisetyp1<Promisetype1> 通过递归调用自身,首先解开第一层 Promise 得到 Promise<User>,然后再解开第二层 Promise 得到 User 类型。因此,A1 的类型也是 User

infer的协变和逆变

let obj={
    name:"zs",
    age:18
}

type Bar<T>=T extends {name:infer N,age:infer A}?[N,A]:T
type C =Bar<typeof obj>



type Bar1<T>=T extends {name:infer U,age:infer U}?U:T
type C1 =Bar1<typeof obj>
// 协变返回一个联合类型type C1 = string | number




type Bar2 <T>=T extends {
    a:(x:infer U)=>void,
    b:(x:infer U)=>void
}?U:never
type T =Bar2<{a:(x:number)=>void, b:(x:string)=>void}>
//如果两个参数不一样就会返回never
type T1 =Bar2<{a:(x:number)=>void, b:(x:number)=>void}>
//两个参数一样就会返回统一类型
// 逆变返回一个交叉类型

infer的递归处理

type Arr=[1,2,3,4]
type ReverArr<T extends any[]>=T extends [infer one,...infer rest]?[...ReverArr<rest>,one]:T
type Arrb=ReverArr<Arr>
//type Arrb = [4, 3, 2, 1]