为什么要用TypeScript?
正常来讲,只会ESM写法也是足够开发Node.js用的,掌握了TypeScript可以更好的应对大规模应用开发,多花一点时间,对可维护性更好。
- 良好的类型支持是现代框架必须的功能
- 可维护性大大提升,为迭代打下坚实基础
- 适用于大规模开发
黑粉
Ruby on Rails作者DHH在2023年宣布在Turbo 8中放弃使用TypeScript,笔者以为Rails本身追求的是开发效率,十五分钟写一个Blog,结果加了TypeScript,五分钟还没写完类型定义。敏捷社区喜欢测试驱动开发(TDD),本来只需要改改代码就行,现在还要管类型定义和类型 测试,很显然,这样做的效率和质量是矛盾的,在收益不够高的时候,放弃也是正常。类型和测试都是一个上下文的上文,也就是一个上下文的预设,如果预设类型错误了,全盘皆输,无类型的Javascript则在探索不确定问题上效率更高。
1、Rails是崇尚效率的框架,截图中5分钟那个有误,早在2005年dhh成名之作,就是15分钟写一个Blog。所以加了ts,会阻碍效率,对于这样一个追求极致效率的团队,弃用是正常的。
2、TS适合的是大型项目,团队水平要么很高,要么很低,其实效果都是非常好的。而Rails其实不是很大型的项目,我没太见过,大家都是想快速交付。
3、Ruby社区有敏捷基因,喜欢TDD或XP,代码变动极快的情况下,要把测试改完,要关注类型测试,就会多出非常多的工作。
4、Rails程序员也都是以Ruby为主,js为辅。当js能完成,非要用ts搞,会带来很多负担,对他们而言roi不高。前端之所以喜欢ts,前端会js为主,以ts为辅就很简单。
某些场景确实不需要TS,纯ES已经比js好很多了,这种纯用ES/JS的比例是比较少的。
奉若神教
主要人群:大厂前端、开源贡献者。
TS适合的是大型项目,团队水平要么很高,要么很低。
高p会想,下面的一线同学少犯点低级错误,参考现在这故障分,搞得人心惶惶,如果只是麻烦一下一线开发,对稳定性有好处,问题不大。一线同学,大家都觉得ts挺先进,别管会不会,用上再说,不行还有any大法。通常大厂都是会比较愿意用ts的。
- 规范,比黑魔法要重要,这其实也是选React的原因。
- 低级错误少,人水平如果高,其实也无所谓的,就怕参差不齐。
- 不差那点时间,dddd。
- 不出故障啥都好,稳定性压到一切。
- 传承会好的多,喜欢研究带着飞。
中立
不吹不黑。
我的观点:对于TS,你一定要学,但在不在项目使用依照团队和项目类型而定。
举例看一下ts成本
以tomcat为例。
import debug from 'debug';
import Koa from 'koa';
import { Plugable } from './plugin';
import { mergeDeep } from './utils';
const log = debug('@tomrpc/core/fn');
const ProxyDefaultConfig = {
proxy: {
inject: 'before', //init | load | before | after
before: [],
},
};
export interface IProxyConfig {
name?: string | 'tomapp';
proxy?: {
inject: 'init' | 'load' | 'before' | 'after';
before: [];
};
}
export class Proxy extends Plugable {
public inject;
constructor(cfg?: IProxyConfig) {
super(mergeDeep(ProxyDefaultConfig, cfg));
}
proxy() {
return async (ctx: Koa.BaseContext, next) => {
log('proxy default');
await next();
log('proxy default end');
};
}
}
最初的版本,我基本上只加了必要的类型。比如配置项这种。
以hono为例。
import { HonoBase } from './hono-base'
import type { HonoOptions } from './hono-base'
import { RegExpRouter } from './router/reg-exp-router'
import { SmartRouter } from './router/smart-router'
import { TrieRouter } from './router/trie-router'
import type { Env, Schema } from './types'
export class Hono<
E extends Env = Env,
S extends Schema = {},
BasePath extends string = '/'
> extends HonoBase<E, S, BasePath> {
constructor(options: HonoOptions<E> = {}) {
super(options)
this.router =
options.router ??
new SmartRouter({
routers: [new RegExpRouter(), new TrieRouter()],
})
}
}
export type MiddlewareHandler<
E extends Env = any,
P extends string = string,
I extends Input = {}
> = (c: Context<E, P, I>, next: Next) => Promise<Response | void>
另外,router也是个好例子
/* eslint-disable @typescript-eslint/ban-ts-comment */
import type { Router, Result } from '../../router'
import { UnsupportedPathError, MESSAGE_MATCHER_IS_ALREADY_BUILT } from '../../router'
export class SmartRouter<T> implements Router<T> {
name: string = 'SmartRouter'
routers: Router<T>[] = []
routes?: [string, string, T][] = []
constructor(init: Pick<SmartRouter<T>, 'routers'>) {
Object.assign(this, init)
}
add(method: string, path: string, handler: T) {
if (!this.routes) {
throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT)
}
this.routes.push([method, path, handler])
}
match(method: string, path: string): Result<T> {
if (!this.routes) {
throw new Error('Fatal error')
}
const { routers, routes } = this
const len = routers.length
let i = 0
let res
for (; i < len; i++) {
const router = routers[i]
try {
routes.forEach((args) => {
router.add(...args)
})
res = router.match(method, path)
} catch (e) {
if (e instanceof UnsupportedPathError) {
continue
}
throw e
}
this.match = router.match.bind(router)
this.routers = [router]
this.routes = undefined
break
}
if (i === len) {
// not found
throw new Error('Fatal error')
}
// e.g. "SmartRouter + RegExpRouter"
this.name = `SmartRouter + ${this.activeRouter.name}`
return res as Result<T>
}
get activeRouter() {
if (this.routes || this.routers.length !== 1) {
throw new Error('No active router has been determined yet.')
}
return this.routers[0]
}
}
简单,规矩。
trpc