TypeScript 诞生已久,优缺点你们都知晓,它能够说是JavaScript静态类型校验和语法加强的利器,为了更好的代码可读性和可维护性,咱们一个个老工程都坦然接受了用TypeScript 重构的命运。然而在改造的过程当中,逐步意识到TypeScript这门语言的艺术魅力前端
人狠话很少,下面咱们先来聊一下 TypeScript 类型声明相关的技巧:segmentfault
先了解TypeScript的类型系统
TypeScript是 JavaScript 的超集,它提供了 JavaScript的全部功能,并在这些功能的基础上附加一层:TypeScript的类型系统数组
什么TypeScript的类型系统呢?举个简单的例子,JavaScript 提供了 String、Number、Boolean等基本数据类型,但它不会检查变量是否正确地匹配了这些类型,这也是 JavaScript 弱类型校验语言的天生缺陷,此处可能会有人DIS 弱类型语言的那些优势。但无能否认的是,不少大型项目里因为这种 弱类型的隐式转换 和 一些不严谨的判断条件 埋下了不胜枚举的 BUG,固然这不是咱们今天要讨论的主题。数据结构
不一样于JavaScript,TypeScript 能实时检测咱们书写代码里 变量的类型是否被正确匹配,有了这一机制咱们能在书写代码的时候 就提早发现 代码中可能出现的意外行为,从而减小出错机会。 类型系统由如下几个模块组成:函数
推导类型
首先,TypeScript 能够根据 JavaScript 声明的变量 自动生成类型(此方式只能针对基本数据类型),好比:工具
const helloWorld = 'Hello World' // 此时helloWorld的类型自动推导为string
定义类型
再者,若是声明一些复杂的数据结构,自动推导类型的功能就显得不许确了,此时须要咱们手动来定义 interface:ui
const helloWorld = { first: 'Hello', last: 'World' } // 此时helloWorld的类型自动推导为object,没法约束对象内部的数据类型 // 经过自定义类型来约束 interface IHelloWorld { first: string last: string } const helloWorld: IHelloWorld = { first: 'Hello', last: 'World' }
联合类型
能够经过组合简单类型来建立复杂类型。而使用联合类型,咱们能够声明一个类型能够是许多类型之一的组合,好比:url
type IWeather = 'sunny' | 'cloudy' | 'snowy'
泛型
泛型是一个比较晦涩概念,但它很是重要,不一样于联合类型,泛型的使用更加灵活,能够为类型提供变量。举个常见的例子:.net
type myArray = Array // 没有泛型约束的数组能够包含任何类型 // 经过泛型约束的数组只能包含指定的类型 type StringArray = Array<string> // 字符串数组 type NumberArray = Array<number> // 数字数组 type ObjectWithNameArray = Array<{ name: string }> // 自定义对象的数组
除了以上简单的使用,还能够经过声明变量来动态设置类型,好比:code
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 的类型, 新手很容易搞错。咱们先简单罗列一下二者的差别:
对比项 | type | interface |
---|---|---|
类型合并方式 | 只能经过&进行合并 | 同名自动合并,经过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和undefined | NonNullable<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 发布!若是这篇文章帮助到您,记得帮忙点个赞哦~