You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
enumGenderEnum{'male'='男生','female'='女生'}interfaceIPerson{
name:string
gender:keyoftypeofGenderEnum}
let bob:IPerson={name:"bob",gender:'male'}<span>{Gender[bob.gender]}</span>
Typescript中应该严格禁止使用ts-ignore,ts-ignore是一个比any更加影响Typescript代码质量的因素。对于any,在我的项目中曾一度想把any也禁掉,但是有一些场景中是需要使用any的,因此没有粗鲁的禁止any的使用。但是绝大部分场景下,你可能都不需要使用any.需要使用any的场景,可以case by case的分析。
Typescript代码整洁之道
最近半年陆续交接了几位同事的代码,发现虽然用了严格的eslint来规范代码的书写方式,同时项目也全量使用了Typescript,但是在review代码的过程中,还是有很多不整洁不规范的地方。良好的代码具有很好的可读性,后续维护起来也会令人愉悦,也能降低重构的概率。本文会结合Typescript,谈谈如何clean代码:
一、基础规范
(1)常量
常量必须命名, 在做逻辑判断的时候,也不允许直接对比没有命名的常量。
上述的例子中,根本不知道1 3 7 对应的是什么意思,这种写法就基本上没有可读性。
从上述正确的写法可以看出来,常量有了命名,在switch或者if等逻辑判断的时候,我们可以从变量名得知常量的具体含义,增加了可读性。
(2)枚举
除了常量枚举外,在Typescript的编译阶段,枚举会生成一个maping对象,如果不是字符串枚举,甚至会生成一个双向的mapping。因此在我们的业务代码中,有了枚举,就不需要一个与枚举值相关的数组。
这里错误的原因是冗余,我们要得到一个FruitList,并不需要new一个,而是可以直接根据FruitEnum的枚举来生成一个数组,原理就是我们之前所说的Typescript的枚举,除了常量枚举外,在编译的时候是会生成一个map对象的。
上述就是正确的写法,这种写法不仅仅是不冗余,此外,如果修改了枚举的类型,我们只要直接修改枚举,这样衍生的数组也会改变。
除此之外,字符串枚举值和字符串是有本质区别的,在定义类型的时候请千万注意,要不然会让你写的代码很冗余。
上述的错误的原因就是IPerson的类型定义中,gender不应该是string,而应该是一个枚举的key,因此,在将string转枚举值的时候,必须增加一个as keyof typeof GenderEnum的断言。
上述 就是正确的写法,字符串枚举和字符串类型是有 明显区别的,当某个变量需要使用到枚举时,不能将他定义成string。
(3)ts-ignore & any
Typescript中应该严格禁止使用ts-ignore,ts-ignore是一个比any更加影响Typescript代码质量的因素。对于any,在我的项目中曾一度想把any也禁掉,但是有一些场景中是需要使用any的,因此没有粗鲁的禁止any的使用。但是绝大部分场景下,你可能都不需要使用any.需要使用any的场景,可以case by case的分析。
上述就是最经典的使用ts-ignore的场景,如上的方式使用了ts-ignore.那么Typescript会认为Plugin的类型是any。正确的方法通过declare module的方法自定义需要使用到的类型.
在module内部可以定义声明,同名的声明遵循一定 的合并原则,如果要扩展三方模块,declare module是很方便的。
同样的大部分场景下,你也不需要使用any,部分场景下如果无法立刻确定某个值的类型,我们可以 用unknown来代替使用any。
any会完全失去类型判断,本身其实是比较危险的,且使用any就相当于放弃了类型检测,也就基本上放弃了typescript。举例来说:
上述的例子中我们调用了一个不存在的方法 ,因为使用了any,因此跳过了静态类型检测,因此是不安全的。运行时会出错,如果无法立刻确定某个值的类型,我们可以 用unknown来代替使用any。
unkonwn是任何类型的子类型,因此跟any一样,任意类型都可以赋值给unkonwn。与any不同的是,unkonwn的变量必须明确自己的类型,类型收缩或者类型断言后,unkonwn的变量才可以正常使用其上定义的方法和变量。
简单来说,unkonwn需要在使用前,强制判断其类型。
(4)namespace
Typescript的代码中,特别是偏业务的开发中,你基本上是用不到namespace的。此外module在nodejs中天然支持,此外在es6(next)中 es module也成为了一个语言级的规范,因此Typescript官方也是推荐使用module。
namespace简单来说就是一个全局对象,当然我们也可以把namespace放在module中,但是namespace放在module中也是有问题的。
上述直接使用module,就是正确的方法,在模块系统中本身就可以避免变量命名重复,因此namespace是没有意义的。
(5)限制函数参数的个数
在定义函数的时候,应该减少函数参数的个数,推荐不能超过3个。
不推荐函数的参数超过3个,当超过3个的时候,应该使用对象来聚合。
同样的引申到React项目中,useState也是同理
(6)module模块尽量保证无副作用
请不要使用模块的副作用。要保证模块的使用应该是先import再使用。
上述在index.ts中import的模块,其调用是在test.ts文件内部的,这种方法就是import了一个有副作用的模块。
正确的方法应该是保证模块非export变量的纯净,且调用方在使用模块的时候要先import,后调用。
(7)禁止使用!.非空断言
非空断言本身是不安全的,主观的判断存在误差,从防御性编程的角度,是不推荐使用非空断言的。
因为使用了非空断言,因此编译的时候不会报错,但是运行的时候会报错.
比较推荐使用的是optional chaining。以?.的形式。
(8)使用typescript的内置函数
typescript的很多内置函数都可以复用一些定义。这里不会一一介绍,常见的有Partial、Pick、Omit、Record、extends、infer等等,如果需要在已有的类型上,衍生出新的类型,那么使用内置函数是简单和方便的。
此外还可以使用 联合类型、交叉类型和类型合并。
值得注意的是字面量的赋值。
最后讲一讲类型合并,这是一种极其不推荐的方法。在业务代码中,不推荐使用类型合并,这样会增加代码的阅读复杂度。
类型合并存在很多地方。class、interface、namespace等之间都可以进行类型合并,以interface为例:
上述同名的interface Box是会发生类型合并的。不仅interface和 interface可以类型合并,class和interface,class和namesppace等等都可能存在同名类型合并,在业务代码中个人不推荐使用类型合并。
(9)封装条件语句以及ts的类型守卫
在正确的写法中我们封装了条件判断的逻辑成一个独立函数。这种写法比较可读,我们从函数名就能知道做了一个什么判断。
此外封装条件语句也可以跟ts的自定义类型守卫挂钩。来看一个最简单的封装条件语句的自定义类型守卫。
在项目中合理地使用自定义守卫,可以帮助我们减少很多不必要的类型断言,同时改善代码的可读性。
(10)不要使用非变量
不管是变量名还是函数名,请千万不要使用非命名,在业务中我就遇到过这个问题,后端定义了一个非命名形式的变量isNotRefresh:
isNotRefresh表示不刷新,这样定义的变量会导致跟这个变量相关的很多逻辑都是相反的。正确的形式应该是定义变量是isRefresh表示是否刷新。
二、函数式
个人非常推荐函数式编程,主观的认为链式调用优于回调,函数式的方式又优于链式调用。近年来,函数式编程日益流行,Ramdajs、RxJS、cycleJS、lodashJS等多种开源库都使用了函数式的特性。本文主要介绍一下如何使用ramdajs来简化代码。
(1)声明式和命令式
个人认为函数声明式的调用比命令式更加简洁,举例来说:
从上述例子我们可以看出来,明显函数调用声明式的方法更加简洁。此外对于没有副作用的函数,比如上述的map函数,完全可以不考虑函数内部是如何实现的,专注于编写业务代码。优化代码时,目光只需要集中在这些稳定坚固的函数内部即可。
(2)Ramdajs
推荐使用ramdajs,ramdajs是一款优秀的函数式编程库,与其他函数式编程库相比较,ramdajs是自动柯里化的,且ramdajs提供的函数从不改变用户已有数据。
来自最近业务代码中的一个简单的例子:
上述是业务代码中的一个例子,利用pipe可以使得流程的操作较为清晰,此外也不用定义中间变量。
再来看一个例子:
我们需要从这个数组中找出tags包含fun的对象。如果用命令式:
我们用函数式的写法可以简化:
很明显减少了代码量且更加容易理解含义。
最后再来看一个例子:
我们想把oldArr这个多维数组,最内层的那个name,由小写转成大写,用函数式可以直接这样写。
The text was updated successfully, but these errors were encountered: