泛型
在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 元素的基类如innerHTML、className、style等。一般像section,header这类语义化的标签也是在这类
HTML(标签名)Element:HTMLInputElement表示<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,它接收target和propertyKey作为参数。在装饰器函数内部,我们通过重新定义属性的getter和setter来添加额外的逻辑。当访问属性值时,会调用getter函数,而当设置属性值时,会调用setter函数。
在Example类中,使用@decorator将name属性装饰起来。当创建一个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)
方法用例
-
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 -
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 } -
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 -
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 } -
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 -
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 } -
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] -
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 -
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 -
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' ] -
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 -
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 -
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]