深入浅出TypeScript | 青训营

1. 为什么要学TypeScript

TypeScript 和 JavaScript 的差异

  • 类型系统:TypeScript 引入了静态类型系统,允许在开发过程中指定变量、函数参数和返回值的类型。这样可以提前检测类型错误,并增强代码的可靠性和可维护性。
  • 类和接口:TypeScript 支持面向对象编程范式,可以使用类和接口来创建自定义数据类型和对象。
  • 编译过程:JavaScript 是一种解释型语言,而 TypeScript 需要在编译阶段将 TypeScript 代码转换成 JavaScript 代码,这意味着 TypeScript 在运行之前需要进行额外的编译步骤。
  • ES6+ 功能支持:TypeScript 支持 ECMAScript 6 (ES6) 及以上版本的语法特性,并且可以通过编译选项进行配置。
  • 非空断言和可选类型:TypeScript 引入了非空断言(!)和可选类型(?)来处理变量或属性的可选性和非空性。
  • 严格模式:TypeScript 允许开启严格模式,强制执行更严格的类型检查和语法规范,从而减少潜在的错误。
  • 泛型:TypeScript 支持泛型,可以创建更通用和灵活的函数和类,增加代码的重用性。

总结:TypeScript 是 JavaScript 的扩展是超集,TypeScript 也能编译成 JavaScript 类型,TypeScript 在编译环节支持静态代码检测,通过类型检查等手段提前发现潜在的错误;支持后端语言中常见的功能,如类、接口、模块化等,这是 TypeScript 的核心能力。

TypeScript 的特性

  • 静态类型检查:TypeScript 引入了静态类型系统,允许在编码阶段发现类型错误。这可以帮助开发者在开发过程中避免常见的类型相关错误,提高代码质量和可靠性。
  • 更好的代码维护性:有了类型系统和严格的类型检查,代码的意图更加明确,阅读和维护代码变得更容易。IDE 提供更强大的代码提示和自动补全,让开发者能够更快速地编写正确的代码。
  • 类和接口支持:TypeScript 支持面向对象编程范式,引入类和接口的概念,可以更好地组织代码结构,提高代码的可读性和可扩展性。
  • ES6+ 功能支持:TypeScript 支持 ECMAScript 6 (ES6) 及以上版本的语法特性,使得开发者可以使用最新的 JavaScript 特性,提高开发效率。
  • 代码重用和泛型:TypeScript 的泛型支持允许开发者编写更通用和灵活的函数和类,增加了代码的重用性。
  • 强大的工具生态系统:TypeScript 可以与现有的 JavaScript 工具和库无缝集成,同时还支持为第三方库编写类型声明文件,使得使用第三方库时拥有更好的类型检查和代码提示。
  • 易于学习和过渡:对于熟悉 JavaScript 的开发者来说,学习 TypeScript 是相对容易的,因为 TypeScript 是 JavaScript 的超集,可以逐步添加类型和其他特性。

总结:TypeScript 提供了更多的语言特性和类型安全性,为 JavaScript 开发带来了更好的开发体验和更可靠的代码质量。

2. TypeScript基础

基础类型

  • 布尔类型(Boolean): 表示逻辑值,可以是 truefalse
  • 数字类型(Number): 表示数值,可以是整数或浮点数。
  • 字符串类型(String): 表示文本字符串,可以用单引号或双引号括起来。
  • 枚举类型(Enum): 表示一组命名的常数值集合。
  • 任意类型(Any): 表示可以是任意类型的值,类似于普通 JavaScript 中的变量。
  • (Unknow):表示一个值,但是它的类型不确定。与 Any 类型不同,Unknown 类型是类型安全的,不能直接对其进行任何操作,除非先进行类型检查或类型断言。
  • 空类型(Void): 表示没有返回值的函数的返回类型。
  • Never 类型:表示那些永远不会出现的值的类型。在函数返回类型中,Never 用于表示抛出异常或无法正常结束的函数。在类型系统中,Never 可以用于处理一些不可能的情况,例如在联合类型中涵盖了所有可能的情况,使得编译器能够推断出某些代码永远不会执行到。
  • 数组类型(Array): 表示一个由同类型元素组成的列表。可以使用泛型表示数组的元素类型,例如 number[] 表示由数字组成的数组。
  • 元组类型(Tuple): 类似于数组,但允许指定每个元素的类型,并且元素的数量是固定的。
  • Null 和 Undefined 类型: 分别表示 nullundefined 值。
  • 联合类型(Union): 允许一个变量具有多种类型中的一种。
  • 交叉类型(Intersection): 允许一个变量拥有多种类型的属性。
  • 对象类型(Object): 表示一个普通的 JavaScript 对象,指定对象属性的名称和类型。
  • 函数类型(Function): 表示函数的类型,包括参数类型和返回值类型。

函数类型

  • 定义:TypeScript定义函数类型要定义输入参数类型和输出类型。
  • 语法格式:(参数1类型, 参数2类型, ...) => 返回值类型
  • 输入参数:参数支持可选参数和默认参数,与 JavaScript 相同。
  • 输出参数:函数的输出参数类型通常是可以自动推断的,函数没有返回值,即没有使用 return 语句,或者返回了一个没有具体值的 return;,则 TypeScript 默认会将该函数的返回值类型设置为 void
  • 函数重载:函数重载是指可以使用相同的函数名但具有不同参数类型的多个函数声明来实现函数重载。通过函数重载,可以为同一个函数提供多种参数类型的支持。在 TypeScript 中,可以多次定义一个函数类型,每次定义对应不同的参数类型和返回值类型,这些函数类型声明会被视为重载。当调用函数时,TypeScript 会根据传递的参数类型来匹配对应的重载声明,并选择相应的函数实现。它会根据重载声明的顺序,从上到下进行匹配,选择第一个匹配的重载声明。

interface(接口)

  • 定义:接口(interface)是一种用来定义对象的结构和类型的方式。它可以描述对象的属性、方法以及类型之间的关系。接口定义可用一些参数来去标识不同属性的值,例如,可以对属性进行可选属性或只读属性的标识。
  • 特点:
    • 可选属性:?
    • 只读属性:readonly
    • 可以描述函数类型
    • 可以描述自定义属性

接口用来定义对象的形状和结构,如果两个对象的形状完全一致,它们就可以被视为相同的类型,从而通过编译器的验证。

  • 定义:在TypeScript中,类是一种用来创建对象的蓝图或模板,可以描述对象的属性和方法。
  • 特点(与JavaScript的区别):
    • 增加了这些字段标识,如:publicprivateprotected修饰符。
      • 默认的方法和属性,默认都是public属性。
      • private是私有属性,在private定义后,在继承类和实例中都不能调用private的私有属性。
      • protected是受保护的,protected定义的属性仅支持在继承类中被调用,在其生成的一些实例中是不能被调用。
    • 抽象类:
      • 如果一个类是抽象类,它不能被直接实例化,只能被其他类继承。
      • 作为基类,抽象类中的抽象方法必须在子类中被实现,否则子类也必须声明为抽象类。
    • interface约束类:使用interface来约束类,并使用implements关键字来实现接口的定义,有助于开发者判断一个类是否满足了接口的定义。

3. TypeScript进阶

高级类型

  • 联合类型(|):联合类型(Union Types)是一种允许变量、参数或属性具有多种可能类型的类型系统概念。通过联合类型,可以在一个变量中表示多个不同的类型选项。
  • 交叉类型(&):交叉类型(Intersection Types)是一种类型系统概念,允许将多个类型合并为一个类型,以获取它们所有的特性。通过交叉类型,可以将不同类型的属性和方法组合在一起,形成一个新的类型。
    • 对于基础类型(如numberstringboolean等),交叉类型会取它们的交集,这意味着如果两个类型都是相同的基础类型,最终的交叉类型就是这个基础类型。
    • 对于非基础类型(如对象、接口、类等),交叉类型会进行相关元素的组合。如果两个类型具有相同名称的属性,它们会被合并为一个属性,属性的类型会取交叉类型。如果两个类型具有不同名称的属性,那么这些属性会都保留在交叉类型中。
  • 类型断言:类型断言(Type Assertion)是一种允许显式地指定变量的类型的方法。
    • 类别:
      • 非空断言(Non-null Assertion / Non-null Assertion Operator / Postfix !):非空断言用于告诉 TypeScript 编译器,确信一个表达式不会为 nullundefined。它在变量或属性名后面添加 !,表示断言该表达式是非空的。
      • 确定赋值断言(Definite Assignment Assertion / Prefix !):确定赋值断言用于告诉 TypeScript 编译器,确信一个变量在使用之前已经被赋值。它在变量名前面添加 !,表示断言该变量已经确定被赋值。
    • 断言用法:
      • 通过arg标识符的方式,来进行属性前置的一个断言定义。
      function getLength(arg: number | string): number {
          return arg.length
      }
      
      • 通过as的方式,断言分为非空断言和确定赋值断言(比较推荐)。
      function getLength(arg: number | string): number {
          const str = arg as string
          if(str.length) {
              return str.length
          } else {
              const number = arg as number
              return number.toString().length
          }
      }
      
  • 类型别名:
    • 定义:类型别名(Type Aliases)是一种用来给现有类型起别名的方式。通过类型别名,您可以将复杂的类型描述用一个简单的名字来表示。
    • type VS interface:
      • 相同点:两者都可以定义对象或函数,都允许继承。
      • 差异点:
        • interface是TypeScript用来定义对象,type是用来定义别名方便使用。
        • type可以定义基本类型,interface不行。
        • interface可以合并重复声明,type不行。
  • 总结:
    • 实际开发中,一般在使用联合类型和交叉类型时,使用type。
    • 当涉及到类和类的类型定义时,则使用interface。
    • 在定义一个对象或函数时,按通用习惯来进行定义即可。

泛型

在 TypeScript 中,泛型(Generics)是一种用来创建可重用、通用性更强的组件的机制。泛型允许在定义函数、类或接口时,将类型作为参数传递,从而能在多个地方使用相同的代码,但处理不同类型的数据。

  • 使用场景:

    • 定义一个print函数,该函数的功能是能把传入的参数打印出来,再返回这个参数,传入的参数类型是string,函数返回类型为string。
    function print(arg: string): string {
        console.log(arg)
        return arg
    }
    
    • 支持打印number类型。
    function print(arg: string | number): string | number{
        console.log(arg)
        return arg
    }
    
    • 支持打印数组类型、任意类型。
    function print(arg: any): any {
        console.log(arg)
        return arg
    }
    
    • 有一个类型解决输入输出可关联的问题。
    function print<T>(arg: T): T {
        console.log(arg)
        return arg
    }
    
  • 基本定义:

    泛型的核心概念之一是引入临时占位的变量(通常用 T 表示),它在函数、类或接口定义中作为参数来表示类型,但在实际使用时可以根据传递的类型参数进行推导,从而实现完整的类型定义。

    • 泛型的语法:<>里面写类型参数,一般用T(标识泛型定义)表示。
    • 指定类型的方法:
      • 定义要使用的类型,即在使用时标识泛型传入的类型指向。
      • 通过TypeScript类型推断,自动推导出相关的类型。
    • 泛型的作用是临时占位,通过传来的类型参数进行推导,来进行完整类型的相关定义。
  • 基础操作符:

    • typeof:获取具体的类型取值。
    interface Person {
        name: string;
        age: number;
    }
    const sem: Person = { name: "semlinker", age: 30};
    
    type Sem = typeof sem;
    
    • keyof:获取所有键。
    interface Person {
        name: string;
        age: number;
    }
    
    type k1 = typeof Person;
    type k2 = typeof Person[];
    
    • in:遍历枚举类型。
    type Keys = "a" | "b" | "c"
    
    type Obj = {
        [p in Keys]: any
    }
    
    • T[K]:在一个类型中对他的k值进行索引访问。
    interface IPerson {
        name: string;
        age: number;
    }
    
    let type1: IPerson['name']
    let type2: IPerson['age']
    
    • extends:泛型约束。
    interface Lengthwise {
        length: number;
    }
    
    function loggingIdentity<T extends Lengthwise>(arg: T): T {
        console.log(arg.length);
        return arg;
    }
    
    loggingIdentity({value: 3})
    loggingIdentity({length: 5})
    
  • 常用的类型工具:

    • Partial<T>:将类型属性变为可选。
    type Partial<T> = {
        [P in keyof T]?: T[P];
    };
    

    对于传入类型 T 中的每个属性 P(使用 keyof T 获取所有属性的联合类型),它创建了一个新属性,该新属性的名字和原属性一样,但值变成了原属性类型 T[P] 的可选版本(? 表示可选)。

    • Required<T>:将类型属性变为必选。
    type Required<T> = {
        [P in keyof T]-?: T[P];
    };
    

    对于传入类型 T 中的每个属性 P(使用 keyof T 获取所有属性的联合类型),它创建了一个新属性,该新属性的名字和原属性一样,但值的类型和原属性类型 T[P] 保持不变,但是在新类型中被标记为必需(-? 表示必需)。

    • Readonly<T>:将类型属性变为只读。
    type Readonly<T> = {
        readonly [P in keyof T]: T[P];
    };
    

    对于传入类型 T 中的每个属性 P(使用 keyof T 获取所有属性的联合类型),它创建了一个新属性,该新属性的名字和原属性一样,但值的类型和原属性类型 T[P] 保持不变,但是在新类型中被标记为只读(readonly 关键字)。

4. 实战和工程向

声明文件

  • declare(类型环境):三方库需要类型声明文件
  • .d.ts:声明文件定义
  • @types:三方库TypeScript类型包
  • tsconfig.json:定义TypeScript的配置

本篇笔记为个人总结,学习交流使用,欢迎各位大佬评价指正,非常感谢!

全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务