2023 GMTC TypeScript 分享
TypeScript 对现代前端工程带来的优势
TypeScript | JavaScript | |
---|---|---|
代码量 | 增加:需要编写类型标注 | 不需要额外的类型标注,JSDoc除外 |
开发效率 | 代码补全,开发体验增加 | 无编类型提示,无代码补全 |
工程质量 | 类型约束,工程质量增加 | 懂的都懂 |
TS 带来的开发体验
- 实时类型检查
- 实时代码补全
- 快速代码重构
- 嵌入类型的提示
Vue 中的 TypeScript 支持
Vue2 中 ts 的支持
Vue2.x 的类型都是基于 flow 来实现的 Vue 2 中全部属性都挂载在 this 之上,而 this 可以说是一个黑盒子,我们完全没办法预先知道 this 上会有什么数据,这也是为什么 Vue 2 对 TypeScript 的支持一直不太好的原因
Vue3 中 ts 的支持
Vue 3 全面拥抱 Composition API 之后,没有了 this 这个黑盒,对 TypeScript 的支持也比 Vue2 要好很多
如何拥有更好的 vue 开发体验?
Volar
Volar 从一个 vue vscode extension 到 embedded language tooling framework
Volar 实现了什么功能
代码高亮、vue 语法支持、代码补全、错误诊断、嵌入式语言(embedded language)...
Vue SFC
<template>
<div>hello vue</div>
</template>
<script setup ts="lang">
import { ref } from 'vue'
</script>
<style lang="less" scoped>
body {
font-size: 12px
}
</style>
Volar 的背后 TS 语言服务(Language Server)
- TS 约等于 JS + 静态类型系统
- TS 语言服务基于静态类型系统为编辑器提供了上面的各种功能
- 语言服务是指对编辑器(vscode)提供语言支持,提供开发体验
- 语言服务协议(Language Server Protocol)一种开源的语言服务器协定,各个编辑器只要支持LSP 就能接入语言服务
LSP 是微软定义的一套协议,本质上语言服务插件是一种特殊的 vs code 插件,目的是为了提升编程语言的开发体验和开发效率,比如提供了自动补全,错误检查,定义跳转,重命名等... 比如说:你想要 TS 在 Atom 编辑器里进行代码提示,就得单独针对 Atom 开发一套,想要在针对 Vim 实用 TS 类型补全,还要在开发一套。而且 Language Server 做各种提示的时候,需要解析大量文件,这种操作可能会消耗大量 CPU 资源,所以 LSP 出现了。LSP 标准化了 GPL 与代码编辑器之间的通信。 遵循LSP协议的插件有两个部分:
- 客户端(用JS/TS编写的VSCode插件)。 用来和 vs code 交互,是 vs code 与 LS 之间沟通的桥梁。客户端通过调用 vs code api,来监听 vs code 传过来的事件,或者向 vs code 发送消息
- 语言服务器(单独在进程中运行的语言分析工具):语言特性的核心,用来实现对文本,词法,语法分析,单独运行,不限于任何语言。
遵循 LSP 协议的好处就是 任何符合规范的 LSP 语言工具都可以与多个支持 LSP 的代码编辑器集成,也就不限于任何编辑器和语言, 比如 PHP https://github.com/felixfbecker/php-language-server。 扩展:
- CDP(Chrome DevTools Protocol) 实现 Chrome 调试功能
- _Vue Devtool _
- Debug Adapter Protocol 是基于 vs code 的一种调试通信协议,比如通过 vscode 调试 nodejs,前端web,Java 或者 Golang,都会遵循 DAP 协议。
- 基于 LSP 的 vue 插件
前端DSL
DSL 概念
一种专注于某个特定领域并具有有限表达性的计算机编程语言。 最成功的 DSL 代表: SQL、Regex
DSL 带来了什么
- 提升程序员开发效率:擅长在程序中某些特定部分发挥作用,让它们易于被理解,提高代码编写效率,减少bug
- 增进与领域专家的沟通:DSL 跳脱了程序员的范畴,DSL可以让非程序员看懂驱动业务的代码,然后就可以跟某些领域的专家进行沟通
前端的 DSL
- 前端领域的经典实践:GPL做控制,DSL做描述
- JavaScript做逻辑控制,HTML和CSS做文档结构和样式描述
- React、Vue:TypeScript,JSX,SFC
- ...
- 外部 DSL:不以 JS 为宿主的特殊语言,如:HTML, CSS, GraphQL
- 嵌入式 DSL: 以 JS 为宿主的:React Hooks, 广义上指的通用的 utils、组件库
- 混合式 DSL: Vue SFC, MDX...
基于 TS 语言服务实现 DSL 开发
MDX
- MDX = Markdown + Extension,混合式的 DSL
- 支持 JSX
- 支持 Vue, React 组件等
基于 VitePress 或者 VuePress 搭建的 SSG 都支持 MDX。 看个例子: 引用 组件
TypeScript 类型体操(类型编程)
什么是类型体操
利用 TS 类型运算 实现复杂功能的智力游戏
类型体操天花板
为什么需要类型编程?
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
问: 如果 add 函数需要 float + float 呢? 再新增一个函数吗?
另一种类型
范型 T
TS 中的类型运算符
- 条件: extends ? :
type isBool = T extends boolean ? true : false
- 推导:infer
type PickFirst<Tuple extends unknown[]> = Tuple extends [infer T, ...inferR] ? T : never
- 交叉:&
type Obj = {a: number} &{b: string}
- 联合:|
type Union = 1 | 2 | 3
- 映射:
type MapType<T> = { [key in keyof T]?: T[Key]}
图灵完备
TypeScript 的类型系统是图灵完备的,也就是能描述各种可计算逻辑。简单点来理解就是循环、条件等各种 JS 里面有的语法它都有,JS 能写的逻辑它都能写
TS 类型运算是不是纯函数式编程语言?
是但又不完全是 函数是编程语言两个显著的特点:
- first-class-function(头等函数),即函数是一等公民,函数可以当作入参,可以当作返回值
- 闭包(Contenx/Environment)
因为缺少这两大特性,可以说 TS类型运算是一门独立的纯函数式编程语言
TS extends 与 GPL 的条件分支逻辑
extends 运算符: A extends B ? C : D -> 代表了如果 A 是 B 的子集,就返回 C, 否则返回 D。 extends 运算的两个用途:
<值> extends <值> ? :
判断两个值是否相等,可以类比 javascript 的 <值> === <值> ? :- T extends 'foo'
- 'foo' extends 'foobar'
<值> extends <类型> ? :
判断一个值是否属于某个类型,可以类比成 typeof <值> === <类型> ? :- T extends string
- 'foo' extends string
变量
- type T = 'hello' // 全局变量,ts文件作用域
- type T = A extends infer B // 布局变量 => let B = A in xxx
type A = 'hello'; // 声明全局变量
type B = [A] extends infer T ? (
T // => 在这个表达式的作用域内,T 都为 [A]
) : never // 声明局部变量
函数调用
type Func<A extends number, B extends string = 'hello'> = [A, B]
// ↑ ↑ ↑ ↑ ↑ ↑ ↑
// 函数名 参数名 参数类型 参数名 参数类型 默认值 函数体
type Test = Func<10, 'world'> // => [10, 'world']
function Func(A: number, B: string = 'hello') { return [A, B] }
const Test = Func(10, 'world') // => [10, 'world']
type GetParameters<Func extends Function> =
Func extends (...args: infer Args) => unknown ? Args : never;
type SayHello = (h: 'hello') => h
type GetSayHello = GetParameters<SayHello> // [h: 'hello']
递归代替循环
函数式编程中经常使用递归代替逻辑循环,同样在TS 类型编程种 可以用递归实现类型的循环条件
type TKeyToCamelCase<Str extends string> =
Str extends `${infer Item}_${infer Rest}`
? `${Item}${TkeyToCamelCase<Capitalize<Rest>>}`
: Str
type A = TKeyToCamelCase<'aa_bb_cc_dd'> //type A = "aaBbCcDd"
数值运算
没有加减乘除怎么做数值运算? 可以通过数组类型的 lenght 属性,length 属性就是个数值 type len = [unknown]['length'] // type len = 1
实现两个数加法
type Add<1, 2> => type Add === 3
// 第一步 构造一个元组
// 为什么是元组而不是数组是因为 [unknown, unknown]['lenght'] => 2
// unknown[]['length'] => number
type BuildArray<
Len extends number,
EleType = unknown,
Arr extends unknown[] = []
> = Arr['length'] extends Len
? Arr
: BuildArray<Len, EleType, [...Arr, EleType]>
type R = BuildArray<3> // [unknown, unknown, unknown]
// 第二步实现 Add
type Add<N1 extends number, N2 extends number> =
[...BuildArray<N1>, ...BuildArray<N2>]['length']
type ResultIs5 = Add<2, 3>
// 实现减法
type Subtract<N1 extends number, N2 extends number> =
BuildArray<N1> extends [...n1: BuildArray<N2>, ...n2: infer R] ? R['length'] : never
type SubtractR = Subtract<5, 2>
// 乘法
type Mutiply<
N1 extends number,
N2 extends number,
ResultArr extends unknown[] = []
> = N2 extends 0 ? ResultArr['length']
: Mutiply<1, Subtract<N2, 1>, [...BuildArray<N1>, ...ResultArr]>;
// 除法
type Divide<
N1 extends number,
N2 extends number,
CountArr extends unknown[] = []
> = N1 extends 0 ? CountArr['length']
: Divide<Subtract<N1, Num2>, N2, [unknown, ...CountArr]>;
TS 类型编程闭包的抽象
function CreateFunc(a) {
return function func(b) {
return a + b
}
}
const func = CreateFunc('a')
const r = func('b') // r === 'ab'
type Func<
Env extends {A: string},
B extends string
> = `${Env['A']}${B}`
type CreateFuncEnv<A extends string> = {A: A}
// ('A' extends string) === true
// { A: 'A' }
type Env = CreateFuncEnv<'A'>
type R = Func<Env, 'B'> // R => 'AB'
个人 TS 学习指北
- 基础语法
- 内置工具类型
- 类型体操
- 元编程:reflect-metadata、Reflect
- 工程化: tsconfig
- 面相对象: 面相对象 -> 面相接口
- AST 编程 ts-morph 可以让操作 TS ast 很容易
TS 开发环境推荐
- es-no 类似 ts-node, 用来执行ts文件,底层基于Esbuild
- tsx 同上,底层基于 Esbuild
- ts-node-dev, ts-node + nodemon 的结合体
- tsd: npm library,帮助更好的进行声明式类型检查
- TypeScript Importer vs-code 插件,帮助类型补全
- vs-code 开启 TypeScript 配置
- Playground 官方在线的 TS 环境,可实时查看编译后的 JS