聊聊typescript类型声明那些最佳实践(代码片段)

Jafeney Jafeney     2022-11-29     464

关键词:

TypeScript 诞生已久,优缺点大家都知晓,它可以说是JavaScript静态类型校验和语法增强的利器,为了更好的代码可读性和可维护性,我们一个个老工程都坦然接受了用TypeScript 重构的命运。然而在改造的过程中,逐步意识到TypeScript这门语言的艺术魅力

人狠话不多,下面我们先来聊一下 TypeScript 类型声明相关的技巧:

先了解TypeScript的类型系统

TypeScript是 JavaScript 的超集,它提供了 JavaScript的所有功能,并在这些功能的基础上附加一层:TypeScript的类型系统

什么TypeScript的类型系统呢?举个简单的例子,JavaScript 提供了 String、Number、Boolean等基本数据类型,但它不会检查变量是否正确地匹配了这些类型,这也是 JavaScript 弱类型校验语言的天生缺陷,此处可能会有人DIS 弱类型语言的那些优点。但无可否认的是,很多大型项目里由于这种 弱类型的隐式转换 和 一些不严谨的判断条件 埋下了不胜枚举的 BUG,当然这不是我们今天要讨论的主题。

不同于JavaScript,TypeScript 能实时检测我们书写代码里 变量的类型是否被正确匹配,有了这一机制我们能在书写代码的时候 就提前发现 代码中可能出现的意外行为,从而减少出错机会。 类型系统由以下几个模块组成:

推导类型

首先,TypeScript 可以根据 JavaScript 声明的变量 自动生成类型(此方式只能针对基本数据类型),比如:

const helloWorld = 'Hello World'  // 此时helloWorld的类型自动推导为string

定义类型

再者,如果声明一些复杂的数据结构,自动推导类型的功能就显得不准确了,此时需要我们手动来定义 interface:

const helloWorld =  first: 'Hello', last: 'World'  // 此时helloWorld的类型自动推导为object,无法约束对象内部的数据类型

// 通过自定义类型来约束
interface IHelloWorld 
  first: string
  last: string

const helloWorld: IHelloWorld =  first: 'Hello', last: 'World' 

联合类型

可以通过组合简单类型来创建复杂类型。而使用联合类型,我们可以声明一个类型可以是许多类型之一的组合,比如:

type IWeather = 'sunny' | 'cloudy' | 'snowy'

泛型

泛型是一个比较晦涩概念,但它非常重要,不同于联合类型,泛型的使用更加灵活,可以为类型提供变量。举个常见的例子:

type myArray = Array // 没有泛型约束的数组可以包含任何类型

// 通过泛型约束的数组只能包含指定的类型
type StringArray = Array<string> // 字符串数组
type NumberArray = Array<number> // 数字数组
type ObjectWithNameArray = Array< name: string > // 自定义对象的数组

除了以上简单的使用,还可以通过声明变量来动态设置类型,比如:

interface Backpack<T> 
  add: (obj: T) => void
  get: () => T

declare const backpack: Backpack<string>
console.log(backpack.get()) // 打印出 “string”

结构类型系统

TypeScript的核心原则之一是类型检查的重点在于值的结构,有时称为"duck typing" 或 "structured typing"。即如果两个对象具有相同的数据结构,则将它们视为相同的类型,比如:

interface Point 
  x: number
  y: number


interface Rect 
  x: number
  y: number
  width: number
  height: number


function logPoint(p: Point) 
  console.log(p)

const point: Point =  x: 1, y: 2 
const rect: Rect =  x:3, y: 3, width: 30, height: 50 

logPoint(point) // 类型检查通过
logPoint(rect) // 类型检查也通过,因为Rect具有Point相同的结构,从感官上说就是React继承了Point的结构

此外,如果对象或类具有所有必需的属性,则TypeScript会认为它们成功匹配,而与实现细节无关

分清type和interface的区别

interface 和 type 都可以用来声明 TypeScript 的类型, 新手很容易搞错。我们先简单罗列一下两者的差异:

对比项typeinterface
类型合并方式只能通过&进行合并同名自动合并,通过extends扩展
支持的数据结构所有类型只能表达 object/class/function 类型

注意:由于 interface 支持同名类型自动合并,我们开发一些组件或工具库时,对于出入参的类型应该尽可能地使用 interface 声明,方便开发者在调用时做自定义扩展

从使用场景上说,type 的用途更加强大,不局限于表达 object/class/function ,还能声明基本类型别名、联合类型、元组等类型:

// 声明基本数据类型别名
type NewString = string

// 声明联合类型
interface Bird 
  fly(): void
  layEggs(): boolean

interface Fish 
  swim(): void
  layEggs(): boolean

type SmallPet = Bird | Fish

// 声明元组
type SmallPetList = [Bird, Fish]

3个重要的原则

TypeScript 类型声明非常灵活,这也意味着一千个莎士比亚就能写出一千个哈姆雷特。在团队协作中,为了更好的可维护性, 我们应该尽可能地践行以下3条原则:

泛型优于联合类型

举个官方的示例代码做比较:

interface Bird 
  fly(): void
  layEggs(): boolean

interface Fish 
  swim(): void
  layEggs(): boolean

// 获得小宠物,这里认为不能够下蛋的宠物是小宠物。现实中的逻辑有点牵强,只是举个例子。
function getSmallPet(...animals: Array<Fish | Bird>): Fish | Bird 
  for (const animal of animals) 
    if (!animal.layEggs())
      return animal
  
  return animals[0]


let pet = getSmallPet()
pet.layEggs() // okay 因为layEggs是Fish | Bird 共有的方法
pet.swim() // errors 因为swim是Fish的方法,而这里可能不存在

这种命名方式有3个问题:

  • 第一,类型定义使 getSmallPet变得局限。从代码逻辑看,它的作用是返回一个不下蛋的动物,返回的类型指向的是Fish或Bird。但我如果只想在一群鸟中挑出一个不下蛋的鸟呢?通过调用这个方法,我只能得到一个 可能是Fish、或者是Bird的神奇生物。
  • 第二,代码重复、难以扩展。比如,我想再增加一个乌龟,我必须找到所有类似 Fish | Bird 的地方,然后把它修改为 Fish | Bird | Turtle
  • 第三,类型签名无法提供逻辑相关性。我们再审视一下类型签名,完全无法看出这里为什么是 Fish | Bird 而不是其他动物,它们两个到底和逻辑有什么关系才能够被放在这里

介于以上问题,我们可以使用泛型重构一下上面的代码,来解决这些问题:

// 将共有的layEggs抽象到Eggable接口
interface Eggable 
  layEggs(): boolean


interface Bird extends Eggable 
  fly(): void


interface Fish extends Eggable 
  swim(): void


function getSmallPet<T extends Eggable>(...animals: Array<T>): T 
  for (const animal of animals) 
    if (!animal.layEggs()) return animal
  
  return animals[0]


let pet = getSmallPet<Fish>()
pet.layEggs()
pet.swim()

巧用typeof推导优于自定义类型

这个技巧可以在没有副作用的代码中使用,最常见的是前端定义的常量数据结构。举个简单的case,我们在使用Redux的时候,往往需要给Redux每个模块的State设置初始值。这个地方就可以用typeof推导出该模块的数据结构类型:

// 声明模块的初始state
const userInitState = 
  name: '',
  workid: '',
  avator: '',
  department: '',


// 根据初始state推导出当前模块的数据结构
export type IUserStateMode = typeof userInitState // 导出的数据类型可以在其他地方使用

这个技巧可以让我们非常坦然地 “偷懒”,同时也能减少一些Redux里的类型声明,比较实用

巧用内置工具函数优于重复声明

Typescript提供的内置工具函数有如下几个:

内置函数用途例子
Partial<T>类型T的所有子集(每个属性都可选)Partial<IUserStateMode>
Readony<T>返回和T一样的类型,但所有属性都是只读Readony<IUserStateMode>
Required<T>返回和T一样的类型,每个属性都是必须的Required<IUserStateMode>
Pick<T, K extends keyof T>从类型T中挑选的部分属性K`Pick<IUserStateMode, 'name'
Exclude<T, U extends keyof T>从类型T中移除部分属性U`Exclude<IUserStateMode, 'name'
NonNullable<T>从属性T中移除null和undefinedNonNullable<IUserStateMode>
ReturnType<T>返回函数类型T的返回值类型ReturnType<IUserStateMode>
Record<K, T>生产一个属性为K,类型为T的类型集合Record<keyof IUserStateMode, string>
Omit<T, K>忽略T中的K属性Omit<IUserStateMode, 'name'>

上面几个工具函数尤其是 Partial、Pick、Exclude, Omit, Record 非常实用,平时在编写过程中可以做一些刻意练习

参考资料

本文由博客一文多发平台 OpenWrite 发布!

聊聊typescript类型声明那些最佳实践(代码片段)

TypeScript诞生已久,优缺点大家都知晓,它可以说是JavaScript静态类型校验和语法增强的利器,为了更好的代码可读性和可维护性,我们一个个老工程都坦然接受了用TypeScript重构的命运。然而在改造的过程中,... 查看详情

聊聊typescript类型声明那些最佳实践(代码片段)

TypeScript诞生已久,优缺点大家都知晓,它可以说是JavaScript静态类型校验和语法增强的利器,为了更好的代码可读性和可维护性,我们一个个老工程都坦然接受了用TypeScript重构的命运。然而在改造的过程中,... 查看详情

TypeScript 类型定义最佳实践

】TypeScript类型定义最佳实践【英文标题】:TypeScripttypedefinitionbestpractices【发布时间】:2018-04-2308:33:08【问题描述】:我已经阅读了几十页,试图找出在TypeScript中设置类型定义的最佳方法。我曾经在我的项目中的某处有一个typing... 查看详情

typescript前端工程最佳实践

 作者:王春雨前言随着前端工程化的快速发展,TypeScript变得越来越受欢迎,它已经成为前端开发人员必备技能。TypeScript最初是由微软开发并开源的一种编程语言,自2012年10月发布首个公开版本以来,它已得到了人们的广泛... 查看详情

在 typescript 函数中返回 2 种不同类型的最佳实践?

】在typescript函数中返回2种不同类型的最佳实践?【英文标题】:bestpracticeforreturning2differenttypesintypescriptfunction?【发布时间】:2017-08-2821:24:30【问题描述】:我有一个执行发布并返回ModelAResponse的Observable的api函数(我拥有的接口... 查看详情

聊聊typescript中的类型保护(代码片段)

聊聊TypeScript中的类型保护在TypeScript中使用联合类型时,往往会碰到这种尴尬的情况:interfaceBird //独有方法fly(); //共有方法layEggs();interfaceFish //独有方法swim(); //共有方法layEggs();functiongetSmallPet():Fish|Bird//...letpet=getSmallPet() 查看详情

textangular/typescript/rxjs-最佳实践(代码片段)

查看详情

聊聊kafka:kafka消息重复的场景以及最佳实践(代码片段)

一、前言上一篇我们讲了聊聊Kafka:Kafka消息丢失的场景以及最佳实践,这一篇我们来说一说Kafka消息重复的场景以及最佳实践。我们下面会从以下两个方面来说一下Kafka消息重复的场景以及最佳实践。生产者重复消息消费... 查看详情

typescript开发环境的最佳实践(代码片段)

Typescript开发环境的最佳实践0??gitinit(略)1????初始化:$yarnadd-Dts-nodetypescript2??生成tsconfig.json:$yarntsc-init3??配置TSLint:$yarnaddtslint-D4??生成tslint.json:$yarntslint--init5??创建src/index.ts:$mkdirsrc&&e 查看详情

聊聊typescript中的类型保护(代码片段)

聊聊TypeScript中的类型保护在TypeScript中使用联合类型时,往往会碰到这种尴尬的情况:interfaceBird //独有方法fly(); //共有方法layEggs();interfaceFish //独有方法swim(); //共有方法layEggs();functiongetSmallPet():Fish|Bird//...letpet=getSmallPet();pet.layEggs... 查看详情

转载在angular2/typescript中声明全局变量的最佳方式是什么?

问题详细描述我想在Typescript语言中的Angular2中声明一些全局可见的变量。最佳的实践方法是? 推荐的实现方法这是最简单的解决方案,无需使用Service或Observer:将全局变量放在文件中然后导出它们。////=====Fileglobals.ts//‘usest... 查看详情

NPM 模块 + TypeScript 最佳实践

】NPM模块+TypeScript最佳实践【英文标题】:NPMModules+TypeScriptBestPractices【发布时间】:2016-10-0500:10:00【问题描述】:我想用TypeScript编写一个npm模块。有人可以向我推荐一个如何开始的最佳实践指南吗?我的问题是:Node.js不支持开... 查看详情

聊聊kafka:kafka消息丢失的场景以及最佳实践(代码片段)

一、前言大家好,我是老周,有快二十多天没有更新文章了,很多小伙伴一直在催更。先说明下最近的情况,最近项目上线很忙,没有时间写,并且组里有个同事使用Kafka不当,导致线上消息丢失,... 查看详情

egg中controller最佳实践(代码片段)

...Java/C#一样,更加直观自然的,做面向切面编程。而随着TypeScript的成熟,类型系统也让我们增强了信心,面对复杂的业务逻辑,也更有底气。egg-controller是集合了一些在Controller层开发中常见问题解决方案的插件。Controller路由定义... 查看详情

聊聊kafka:kafka消息重复的场景以及最佳实践(代码片段)

一、前言上一篇我们讲了聊聊Kafka:Kafka消息丢失的场景以及最佳实践,这一篇我们来说一说Kafka消息重复的场景以及最佳实践。我们下面会从以下两个方面来说一下Kafka消息重复的场景以及最佳实践。生产者重复消息消费... 查看详情

在 Web API/OWIN 中使用大量声明的最佳实践

...声明的授权设置,并且我正在尝试找出管理更细粒度授权类型的最佳方法。我不喜欢只使用角色的想法,因为我正在处理的应用程序中需要大量细粒度的授权。我在想角色+权 查看详情

typescript node.js express 路由分隔文件的最佳实践

】typescriptnode.jsexpress路由分隔文件的最佳实践【英文标题】:typescriptnode.jsexpressroutesseparatedfilesbestpractices【发布时间】:2016-09-0703:32:58【问题描述】:在Node项目中使用Express和Typescript什么是express.Router的“最佳实践”。示例目录... 查看详情

typescript教程#3:ts的类型声明(代码片段)

说明尚硅谷TypeScript教程(李立超老师TS新课)学习笔记。类型声明类型声明是TS非常重要的一个特点通过类型声明可以指定TS中变量(参数、形参)的类型指定类型后,当为变量赋值时,TS编译器会自动检查值是否符... 查看详情