首先,声明几点:Typescript 必须学,属于前端和中间件开发的基本技能,这个没得谈
以下所称不需要 Typescript 的场景,均为特定场景,并非贬低 Typescript
成本收益考量是基础,并不是说一项技术不好,而是从其中获取的收益,没有付出的成本高
有这几个基础点,再来讨论这个问题 ——
使用Typescript一定会更好么?
静态类型 vs 动态类型
静态类型一定比动态类型好,这句话没问题
因为问题出现在编译期,一定比出现在运行时要更方便调试
但是这个问题在前端不完全成立,因为前端不覆盖全部逻辑,简而言之:数据源不在你这,写入和读取的类型限制更不在你这,前端没有orm
any,null,undefined 存在,会加大初始化难度,类型完备很难实现
这个场景和后端不同,在后端,你可以明确地说,静态类型>>动态类型,静态类型一时爽,代码重构火葬场
因为数据库结构,你可以用orm定义,既然定义权在你这,那么读取到的数据,绝大部分情况下,都不会出现意外,除了接口部分,需要用到校验(注意校验,之后还会讨论),从数据源到接口的整个类型链条,中间不会断裂,并且可选类型和any出现的情况很少,很多语言直接就没有
因此,这个静态类型和动态类型的比较还远没有结束:
静态类型>>动态类型≈错误的静态类型>>>>毫无约束的运行时
很多Typescript使用者,很想实现第一种情况,然而,如果因此划向了第三种,甚至第四种情况,那就真的得不偿失了
Typescript没有报错,但是却在运行阶段出现了奇奇怪怪的错误,此时你该如何进行调试呢?
是,解决方案有:禁止写入any
一系列方案处理初始化
抽象开发,将逻辑静态化(提倡面向对象,函数式)
这些,是你需要针对Typescript付出的成本,同时,Typescript带来的可读性降低,更是成本之一,这个现象在React中更加严重,比如泛型和标签,同时,配置成本也是成本之一,只是不那么重要罢了
得到的优势是什么?编译期类型错误提示
接口,类型提示方便团队协作
编译器掌握更多信息,可以优化性能
通过构建目标设置,可以实现跨平台(当然js通过响应工具也能实现,但是ts更加游刃有余)
四个优势,第三点聊胜于无,性能的小幅度提升影响不会很大,第四点和第二点,只对大型项目起作用(跨平台必然大),因此,在中小型项目中,所有问题都集中在了第一点 —— 编译期类型错误提示
但是,有一个很奇怪的问题是:
解决类型错误调试只有这一条解法么?
并不是!
首先,大部分的类型错误,实际上是发生在初始化阶段:
Cannot read property 'xxx' on undefined
'undefined' is not an object:undefined
null is not an object
'undefined' is not a function
Object doesn't support property
Cannot read property 'length'
cannot set property of undefined
或者说这种情况下,问题与其说是类型错误,不如说是初始化处理方式错误
正常的情况下,大家直接想到的,处理初始化问题的方式,就是 ——
设置默认值
const DataContext = createContext({
name:"",
password:"",
setName:"",
setPassword:""
})
const data = useContext(DataContext)
// data.name data.password
这是 React 的做法,很多使用 React + typescript 的开发者,很自然地写出 createContext(),createContext(null) 的代码,在 Typescript 中是没有问题的,因为使用时必然是已经初始化了的(useContext 必然 在 createContext 下出现)
但是在 JavaScript 中,有这个初始值在,data. 后,就会亲切地出现 name 了
另外有意思的事,Typescript 并没有解决初始化问题,但是却将初始化问题更加方便地暴露在你面前
当然,Typescript 的反射更能帮助解决初始化,前提是 class+装饰器+反射 实现的依赖注入:
constructor(someData:SomeData){
// someData.a , someData.b}
因此,反复审视,你使用的技术栈有提供这方面的支持么?
剩下的部分,是可变类型:
string -> number
SomeClass -> AnotherClass
这种看似 Typescript 是唯一解法,然而有意思的是,大家忽略了另外一个相当强势,并且根本上解决问题的方式 ——
校验
{
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,
}
经过校验的数据,一定会是这个类型
注意,是一定
不论是编译期,还是运行时,都改变不了这个数据的格式!
一旦代码得以运行,相关数据必然是如此格式,这才是真正的类型安全!
所以,之前后端在处理前端接口问题时,采用的校验方式,其实也是主要方式之一
你当然可以 proto - grpc 来解决服务间交互,直接 http + 校验也并无不可
当然,如果出现人为的数据类型变更,Typescript 的优势就无可替代了
自动判定的可选类型,交叉类型,可以让你在开发时更加游刃有余
但是注意,这其实也是个成本问题,为何如此?因为其导致的后果,仅仅是数据类型变更时的小心翼翼,甚至添加动态校验,以及交叉,可选类型时,需要手动使用 class 的继承特性来处理
另外,如果由于数据变更导致的错误,Typescript能够有更直观的错误,而校验做不到更加细致,只能大致告诉你,问题出在哪个组件,哪个函数,会加大开发负担
综上分析,细致来下,在中小型项目中,Typescript 的优势是:处理数据类型变更
重构提示
带来的负担是:可读性降低
架构设计负担加重
开发负担增加
这就像是个天平,如果左边的优势压过了右边的负担,那么Typescript就会成为推荐,反之则反
所以,一旦能够降低这里的负担出现的情况,那就可以直接选择Typescript不带犹豫有依赖注入,框架帮你解决数据和类型的获取问题,框架帮你解决初始化问题
类型完备,绝大部分api没有any,没有unknown,没有可选,没有交叉,判定过程框架帮你完成
采用 class 进行构建,充分运用 Typescript 的反射,类型判断,装饰器的功能,增加可读性,最好不要有JSX,html的归html,ts的归ts
有了这些,当优势压过负担,那么Typescript就可以顺理成章成为必选项了另外,TypeScript 也不会取代 JS,前者只是 JS 的一个超集。我个人认为,JS 与 TS(带类型的超集)并行发展才是最合理的未来方向,而且这一点在可预见的未来不会改变 —— 尤雨溪