TypeScript(六)条件类型,函数,装饰器

news2025/2/26 1:06:38

条件类型

TypeScript 中的条件类型是一种高级类型,它使我们根据不同的条件创建更复杂的类型。
TS中的条件类型就是在类型中添加条件分支,以支持更加灵活的泛型

条件类型允许我们根据某个类型的属性是否满足某个条件,来确定最终的类型。

type IsString<T> = T extends string ? "yes" : "no";

let result4: IsString<"hello"> = "yes";  // 类型为 "no"
let result3: IsString<42> = "no";   // 类型为 "yes"
console.log(result4, result3); // yes no
// let result2: IsString<"hello"> = "no";  // 类型为 "no"
// let result1: IsString<42> = "yes";   // 类型为 "yes"
// 通过给泛型参数传递不同的类型,得到了不同的结果。

在这里插入图片描述

  1. infer 关键字

infer 关键字用于引入一种类型变量,定义泛型里面推断出来的类型参数,而不是外部传入的类型参数。
通常,infer 关键字结合条件类型使用,用于提取和推断类型信息。

// infer R 表示 R 类型是 TypeScript 自己推断出来的,不用显式传入。
// 传入的 T 是函数, 就返回这个函数的结果类型。
type ExtractReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function sum(a: string, b: string): string {
  return a + b;
}
const result1: ExtractReturnType<typeof sum> = 'hello'; // 编译通过
console.log(result1) // hello
  • tuple转union 元组转为联合类型
type ElementOf<T> = T extends Array<infer P> ? P : never;
type Tuple = [string, number];
type TupleToUnion = ElementOf<Tuple>; 
// 等同于
// type TupleToUnion = string | number

let arr: TupleToUnion = '迪西'
let arr2: TupleToUnion = 9
  • 联合类型转成交叉类型 string | number =》string & number
type T1 = { name: string };
type T2 = { age: number };
type ToIntersection<T> = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never;
// 由于U需要同时满足T1的定义、T2的定义,因此U需要包含T1、T2所有的类型,因此T3就是T1 & T2
type T3 = ToIntersection<{ a: (x: T1) => void, b: (x: T2) => void }>; // type T3 = T1 & T2

let obj: T3 = {
  name: '迪西',
  age: 9,
}
  1. 条件类型中的泛型推断
    条件类型中的泛型推断是一个重要的概念,
    // 条件类型中的泛型推断
    type ExtractType<T> = T extends infer U ? U : never;
    // 使用泛型推断的条件类型
    type ExtractedType = ExtractType<string[]>;
    // 查看结果类型
    let extractedValue: ExtractedType = ["1", "2"];
    
    在这里插入图片描述
  2. 分布式条件类型
    条件类型在处理联合类型时表现出一种分布式的行为,
    type Diff<T, U> = T extends U ? never : T; // 返回T 不属于U的子类型的部分
    type Filter<T, U> = T extends U ? T : never; // 返回T 属于U的子类型的部分
    
    type OnlyStrings = Diff<"a" | "b" | 1 | 2 | false, number | boolean>; // "a" | "b" | false
    type OnlyNumbers = Filter<"a" | "b" | 1 | 2 | true, number | boolean>; // 1 | 2 | true
    
    let str1: OnlyStrings = 'a'
    let str2: OnlyNumbers = 1
    
  3. 内置条件类型
    在 TypeScript 中,有一些内置的条件类型,比如 NonNullable,它从类型中排除了 null 和 undefined。这里确保 value 的类型是非空的字符串。
    type NonNullableType<T> = T extends null | undefined ? never : T;
    const value1: NonNullableType<string | null> = '迪西'; // 编译通过
    // const value2: NonNullableType<string | null> = null; // 编译通过
    
    在这里插入图片描述
  4. 分配条件类型
    分布式条件类型是指在联合类型上进行条件类型判断时,该判断会分布到联合类型的每个成员上,从而生成新的联合类型。
    type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
    // MyReturnType ,应用于函数类型和非函数类型。由于条件类型的分布式能力,它会分别应用于函数类型和非函数类型的每个成员,最终生成新的联合类型
    function sum(a: number):number {
      return a;
    }
    // 编译通过 nonFuncResult 类型会被推断为string
    const nonFuncResult: MyReturnType<string> = "Hello";
    // 编译通过 funcResult 类型会被推断为number
    const funcResult: MyReturnType<typeof sum> = 3; 
    
  5. 条件类型在映射类型中的应用
    映射类型是一种用于从现有类型创建新类型的工具,可以根据对象的属性是否满足条件来创建一个新类型,从而对现有类型的属性进行转换、修改或过滤。
    type MakeNullable<T> = {
      [K in keyof T]: T[K] extends number ? T[K] | null : T[K];
    };
    
    // 使用映射类型中的条件类型
    type User = {
      id: number;
      name: string;
      age: number;
    };
    
    type NullableUser = MakeNullable<User>;
    // 将 T 中的 number 类型属性改为可空。通过应用于 User 类型,我们得到了一个可空的 NullableUser 类型。
    // 查看结果类型
    let nullableUser: NullableUser = {
      id: 1,
      name: "John",
      age: null,
    };
    
    在这里插入图片描述
  6. 条件类型中的嵌套应用
    条件类型可以嵌套使用,形成更复杂的类型判断。
    // 嵌套应用的条件类型
    type FlattenArray<T> = T extends Array<infer U> ? FlattenArray<U> : T;
    
    // 使用嵌套应用的条件类型
    type NestedArray = ['迪西', ['小波', ['拉拉', '嘻嘻'], '哈哈']];
    
    type FlatArray = FlattenArray<NestedArray>;
    
    // 查看结果类型
    let flatArray: FlatArray = '迪西';
    
    在这里插入图片描述

函数

  1. 函数是JavaScript应用程序的基础。 它帮助你实现抽象层,模拟类,信息隐藏和模块。
  2. 和JavaScript一样,TypeScript函数可以创建有名字的函数和匿名函数。可以随意选择适合应用程序的方式,不论是定义一系列API函数还是只使用一次的函数。
// Named function
function add(x, y) {
    return x + y;
}
// 匿名函数 函数表达式是一种将函数赋值给变量的方式
let myAdd = function(x, y) { return x + y; };
  1. 函数类型
    函数类型包含2部分:参数类型和返回值类型。 当写出完整函数类型的时候,这两部分都是需要的
  • 参数类型
    为每个参数指定一个名字和类型。 这个名字只是为了增加可读性。
    只要参数类型是匹配的,那么就认为它是有效的函数类型,而不在乎参数名是否正确。
    let myAdd: (baseValue: number, increment: number) => number =
        function(x: number, y: number): number { return x + y; };
    
  • 返回值类型
    返回值,我们在函数和返回值类型之前使用( =>)符号,使之清晰明了。
    返回值类型是函数类型的必要部分,如果函数没有返回任何值,你也必须指定返回值类型为 void而不能留空。
    let myAdd = function(x: number, y: number): number { return x + y; };
    
  1. 可选参数和默认参数
    使用?符号来定义可选参数。可选参数可以不传递
    在调用这些函数时,我们可以选择传递或不传递可选参数。
    // 1).可选参数 ?代表参数可选,age可传可不传
    function func1(name: string, age?: number): void {
      console.log(name); // 张三
    }
    func1('张三');
    
    // 2).可选参数,参数有默认值
    function func2(url: string, method: string = 'POST'): void {
      console.log(url, method); // /list POST
    }
    func2('/list');
    
  2. 剩余参数
    可以使用…符号来定义剩余参数。剩余参数可以接受任意数量的参数,并将它们作为数组传递给函数
    function func1(...arge: number[]): number {
      return arge.reduce((val, item) => val + item, 0);
    }
    console.log(func1(4, 5, 6, 7)); // 22
    
  3. 函数重载
    函数重载是一种在 TypeScript 中定义多个具有相同名称但参数类型和数量不同的函数的技术。这可以编写更清晰和类型安全的代码。
let obj: any = {};
function func1(val: string): void;
function func1(val: number): void;
function func1(val: boolean): void;
function func1(val: any): void {
  if (typeof val === 'string') {
    obj.name = val;
  } else if (typeof val === 'number') {
    obj.age = val;
  } else if (typeof val === 'boolean' ) {
    obj.isSuccess = val;
  }
}
func1('迪西');
func1(10);
func1(true);
console.log(obj); // {name: '迪西', age: 10, isSuccess: true}

装饰器

要启用实验性的装饰器特性,你必须在命令行或tsconfig.json里启用experimentalDecorators编译器选项:

tsc --target ES5 --experimentalDecorators

tsconfig.json:

{
    "compilerOptions": {
        "target": "ES5",
        "experimentalDecorators": true
    }
}

解决bug

编译ts文件报错:Unable to resolve signature of method decorator when called as an expression.
官网的配置还是不能解决报错
可以通过监ts文件的热更新。
$ tsc 文件名 --target ES5 -w --experimentalDecorators

装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上。 装饰器使用 @expression这种形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。

装饰器的作用
1)只能在类中使用
2)减少冗余代码量
3)提高代码扩展性

  1. 装饰器的语法
    装饰器本质上就是一个函数,在特定的位置调用装饰器函数即可对数据进行扩展。
    // target 表示要扩展的数据,可以是类、方法、属性、参数等。
    function myDecorator(target: any) {
      // 对 target 进行处理
    }
    
  2. 类装饰器
    类装饰器在类声明之前被声明(紧靠着类声明)。 类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。 类装饰器不能用在声明文件中( .d.ts),也不能用在任何外部上下文中
    类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。
    如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。

    注意 如果你要返回一个新的构造函数,你必须注意处理好原来的原型链。 在运行时的装饰器调用逻辑中 不会为你做这些。

    function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) {
      return class extends constructor {
          newProperty = "new property";
          hello = "override";
      }
    }
    
    @classDecorator
    class Greeter {
      property = "property";
      hello: string;
      constructor(m: string) {
        console.log(m) // world
          this.hello = m;
      }
    }
    
    console.log(new Greeter("world")); // class_1 {property: 'property', hello: 'override', newProperty: 'new property'}
    
  3. 装饰器工厂
    若需要要定制一个修饰器如何应用到一个声明上,得写一个装饰器工厂函数。 装饰器工厂就是一个简单的函数,它返回一个表达式,以供装饰器在运行时调用。
    function color(value: string) { // 这是一个装饰器工厂
        return function (target) { //  这是装饰器
            // do something with "target" and "value"...
        }
    }
    
    function addNameEatFactory(name: string):Function {
      return function addNameEat(constructor: Function):void {
        constructor.prototype.name = name;
        constructor.prototype.eat = (m) => {
          console.log(m) // 2
        };
      };
    };
    @addNameEatFactory('Jerry')
    class Person {
      name: string;
      eat: Function | undefined;
      constructor(n) {
        console.log(n) // 1
        this.name = n;
      }
    }
    let p: Person = new Person(1);
    console.log(p.name); // 1
    p.eat(2);
    
  4. 属性、方法装饰器
    方法装饰器声明在一个方法的声明之前(紧靠着方法声明)。 它会被应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义。 方法装饰器不能用在声明文件( .d.ts),重载或者任何外部上下文(比如declare的类)中。
    方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:

    1)对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
    2)成员的名字。
    3)成员的属性描述符。
    注意  如果代码输出目标版本小于ES5,属性描述符将会是undefined。
    如果方法装饰器返回一个值,它会被用作方法的属性描述符。
    注意  如果代码输出目标版本小于ES5返回值会被忽略。

    function upperCase(target: any, propertyKey: string) {
      // console.log(target, propertyKey); // { getName: [Function (anonymous)], sun: [Function (anonymous)] 	} name
      let value = target[propertyKey]
      const getter = () => value;
      const setter = (newVal: string) => { value = newVal.toUpperCase(); };
      if (delete target[propertyKey]) {
        Object.defineProperty(target, propertyKey, {
          get: getter,
          set: setter,
          enumerable: true,
          configurable: true
        });
      }
    }
    // 如果装饰的是静态属性,target就是构造函数
    function staticPrototypeDecorator(target: any, propertyKey: string) {
      // console.log(target, propertyKey); // [Function: Person] { age: 18 } age
    }
    function noEnumerable(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
      descriptor.enumerable = false; // 不可枚举
    }
    function toNumber(target: any, propertyKey, descriptor: PropertyDescriptor) {
      let oldMethod = descriptor.value;
      descriptor.value = function (...args: any[]) {
        console.log(args) // ['1', '2', '3', '4']
        args = args.map(item => parseFloat(item));
        return oldMethod.apply(this, args)
      }
    }
    class Person {
      @upperCase
      name: string = 'jerry';   // 实例属性
      @staticPrototypeDecorator
      static age: number = 18;  // 静态属性
      @noEnumerable
      getName() { console.log(this.name); }; // 实例方法
      @toNumber
      sum(...args: any[]) { // 实例方法
        console.log(args) // [1, 2, 3, 4]
        return args.reduce((prev: number, next: number) => prev + next, 0);
      }
    }
    let p = new Person();
    console.log(p.name); // JERRY
    console.log(p.sum('1', '2', '3', '4')); // 10
    
  5. 参数装饰器
    参数装饰器声明在一个参数声明之前(紧靠着参数声明)。 参数装饰器应用于类构造函数或方法声明。 参数装饰器不能用在声明文件(.d.ts),重载或其它外部上下文里。

    参数装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
    1)对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
    2)成员的名字。
    3)参数在函数参数列表中的索引。
    注意  参数装饰器只能用来监视一个方法的参数是否被传入。

    // target:静态属性指构造函数,实例属性、实例方法值构造函数的原型
      // methodName:方法的名称
      // paramIndex:参数的索引
      function addAge(target: any, methodName: string, paramIndex: number) {
        // console.log(target, methodName, paramIndex); // { login: [Function (anonymous)] } login 1
        target.age1 = 18;
      }
      class Person {
        age1: number;
        login(username: string, @addAge password: string) {
          console.log(this.age1, username, password); // 18 admin admin123
        }
      }
      let p = new Person();
      p.login('admin', 'admin123'); // 18 'admin' 'admin123'
    
  6. 装饰器执行顺序
    • 1)class装饰器最后执行,后写的类装饰器先执行
    • 2)方法和参数中的装饰器,参数装饰器先执行,再执行方法装饰器
    • 3)方法和属性装饰器,谁在前面先执行谁
    • 4)先内后外 先上后下执行
    function ClassDecorator1() {
      return function (target) {
        console.log('ClassDecorator1');
      }
    }
    function ClassDecorator2() {
      return function (target) {
        console.log('ClassDecorator2');
      }
    }
    function PropertyDecorator(name: string) {
      return function (target, propertyName) {
        console.log('PropertyDecorator', propertyName, name);
      }
    }
    function MethodDecorator() {
      return function (target, propertyName) {
        console.log('MethodDecorator', propertyName);
      }
    }
    function ParameterDecorator() {
      return function (target, propertyName, index) {
        console.log('ParameterDecorator', propertyName, index);
      }
    }
    @ClassDecorator1()
    @ClassDecorator2()
    class Person {
      @PropertyDecorator('name')
      name: string = '';
      @PropertyDecorator('age')
      age: number = 9;
      @MethodDecorator()
      hello(@ParameterDecorator() hello: string, @ParameterDecorator() word: string) {}
    }
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1528643.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【海贼王的数据航海】排序——冒泡|快速|归并排序|总结

目录 1 -> 交换排序 1.1 -> 冒泡排序 1.1.1 -> 代码实现 1.2 -> 快速排序 1.2.1 -> hoare版本 1.2.2 -> 挖坑法 1.2.3 -> 前后指针法 1.2.4 -> 快速排序(递归版) 1.2.5 -> 快速排序(非递归版) 2 -> 归并排序 2.1 -> 归并排序 2.…

泛微 OA - 根据流程 requestid 获取流程中的附件

泛微 OA - 根据流程 requestid 获取流程中的附件 在泛微 OA 流程中&#xff0c;附件是通过加密上传的&#xff0c;如果在第三方系统想要调用 OA 系统获取 OA 附件&#xff0c;暂时没有很好的方法实现。但是可以在本地进行调用&#xff0c;得到附件 url 地址、附件 id、附件上传…

蓝桥杯练习02随机数生成器

随机数生成器 介绍 实际工作中随机数的使用特别多&#xff0c;比如随机抽奖、随机翻牌。通过随机数还能实现很多有趣的效果&#xff0c;比如随机改变元素的位置或颜色。 本题需要在已提供的基础项目中使用JS知识封装一个函数&#xff0c;该函数可以根据需要&#xff0c;生成指…

01. 【Android教程】系统背景及结构概述

1. Android 的历史 Android 一词的本意指“人形机器人”&#xff0c;安迪•鲁宾 (Andy Rubin) 在 2003 年以此名创办了 Android 公司并开始召集团队研发 Android 系统。后于 2005 年被 Google 收购&#xff0c;安迪•鲁宾随后任 Google 工程部副总裁&#xff0c;继续负责 Andr…

数据可信流通:从运维信任到技术信任

1.数据可信流通概念 "数据可信流通"通常指的是确保数据在不同系统、应用程序或者组织之间的传输和交换过程中的可信性、完整性和安全性。在数据流通的过程中&#xff0c;确保数据的真实性、完整性和保密性是非常重要的&#xff0c;尤其是涉及到敏感信息或者重要数据…

大数据开发--01.初步认识了解

一.环境准备 1.使用虚拟机构建至少三台linux服务器 2.使用公有云来部署服务器 二.大数据相关概念 大数据是指处理和分析大规模数据集的一系列技术、工具和方法。这些数据集通常涉及海量的数据&#xff0c;包括结构化数据&#xff08;如关系型数据库中的表格&#xff09;以及…

Verdaccio部署及基础使用

1. Verdaccio 简介 Verdaccio&#xff0c;是一个轻量级的 npm 私有仓库的开源解决方案。npm是一个基于http的协议&#xff0c;用来存放软件包并且维护版本和依赖&#xff0c;利用 http 提供的 url路径 来对软件包进行增删改查。所以 Verdaccio 这款软件的核心就是实现 npm协议…

机器人路径规划:基于双向A*算法(bidirectional a star)的机器人路径规划(提供Python代码)

一、双向A*算法简介 传统A*算法是一种静态路网中求解最短路径最有效的方法&#xff0c; 它结合了BFS 算法和迪杰斯特拉算法(Dijkstra)的优点。 和迪杰斯特拉算法(Dijkstra)一样&#xff0c; A*算法能够用于 搜索最短路径&#xff1b; 和BFS 算法一样&#xff0c; A*算法可以用…

从头手搓一台ros2复合机器人(带机械臂)

一.前言 大家好呀&#xff0c;从本小节开始我们就步入了仿真篇&#xff0c;主要对机器人仿真进行介绍与操作&#xff0c;当然仿真有优点也有缺陷&#xff0c;基于对此学习&#xff0c;我们可以对上几小节创建的小车模型模拟硬件的特性&#xff0c; 比如&#xff1a; 有多重…

打开磁盘清理工具的9种方法,总有一种适合你

前言 你可以在Windows 10和11上使用许多第三方磁盘清理工具来进行清理。但是,别忘了Windows包含自己的磁盘清理工具,你可以使用该工具释放硬盘存储空间。一些第三方替代方案可能有更广泛的清理选项和功能,但磁盘清理仍然是消除多余文件的完美工具。 每个用户都应该不时地进…

vue 部署 abap BSP

How to Create a Vue.Js App with VS Code and Deploy... - SAP Community 详情见上面这个教程连接 Open VS Code and open a terminal window and run npm install -g vue/cli restart vscode. Open a terminal window again and go to your workspace folder to create the…

使用echart绘制拓扑图,树类型,自定义tooltip和label样式,可收缩

效果如图&#xff1a; 鼠标移上显示 vue3 - ts文件 “echarts”: “^5.4.3”, import { EChartsOption } from echarts import * as echarts from echarts/core import { TooltipComponent } from echarts/components import { TreeChart } from echarts/charts import { C…

VR历史建筑漫游介绍|虚拟现实体验店|VR设备购买

VR历史建筑漫游是一种利用虚拟现实技术&#xff0c;让用户可以身临其境地参观和探索历史建筑的体验。通过VR头显和相关设备&#xff0c;用户可以在虚拟环境中自由移动和互动&#xff0c;感受历史建筑的真实氛围和文化内涵。 在VR历史建筑漫游中&#xff0c;您可以选择不同的历史…

拿捏指针(三)

✨✨欢迎&#x1f44d;&#x1f44d;点赞☕️☕️收藏✍✍评论 个人主页&#xff1a;秋邱博客 所属栏目&#xff1a;C语言 &#xff08;感谢您的光临&#xff0c;您的光临蓬荜生辉&#xff09; 前言 在这之前我们学习了《拿捏指针&#xff08;一&#xff09;》&#xff0c;《拿…

Centos7部署使用TELEMAC-MASCARET

Background TELEMAC-MASCARET是一款研究水动力学和水文学领域的高性能数值仿真开源软件。MASCARET&#xff08;1980&#xff09;和 TELEMAC&#xff08;1987&#xff09;最初是由法电集团所属的法国国立水利与环境实验室开发&#xff0c;随后整合为TELEMAC-MASCARET并由法英德三…

后端系统开发之——创建注册接口

原文地址&#xff1a;后端系统开发之——创建注册接口 - Pleasure的博客 下面是正文内容&#xff1a; 前言 这是一篇SpringBoot项目的实践篇。 主要用于介绍如何从零开始搭建某一种类型的系统。 个人认为&#xff0c;只要后端逻辑完善了&#xff0c;纵使前端页面千变万化都可…

【DL经典回顾】激活函数大汇总(二十四)(Absolute附代码和详细公式)

激活函数大汇总&#xff08;二十四&#xff09;&#xff08;Absolute附代码和详细公式&#xff09; 更多激活函数见激活函数大汇总列表 一、引言 欢迎来到我们深入探索神经网络核心组成部分——激活函数的系列博客。在人工智能的世界里&#xff0c;激活函数扮演着不可或缺的…

个人开发者上架App流程

摘要 个人开发者完全可以将自己开发的App上传至应用商店进行上架。本文将介绍上架流程的通用步骤&#xff0c;包括确定App功能和定位、准备相关资料、开发App、提交审核、发布App和宣传推广等内容。 引言 个人开发者在如今的移动应用市场中也有机会将自己的作品推向更广泛的…

【书生·浦语大模型实战营】学习笔记3

文章目录 1. 大模型开发范式2. LangChain简介3. 构建向量数据库4. 搭建知识库助手5. Web Demo部署6. 动手实战环节环境配置知识库搭建InternLM接入LangChain构建检索问答链部署Web Demo 参考资料 1. 大模型开发范式 LLM局限性&#xff1a; 知识时效性&#xff1a;LLM无法获取最…

【Vue3】组件通信的的各种方式和tsx风格

组件通信 Vue2组件通信方式全局事件总线bus&#xff0c;可以实现组件通信 Vue3组件通信方式propsprovide与inject依赖注入 全局APiVue3其他改变组件通信之自定义事件组件通信之事件总线组件通信之v-model多个v-model传值 TSX风格使用风格一使用tsx风格&#xff0c;使用optionsA…