|
1 |
| -### 概述 |
| 1 | +### 初次见面 |
| 2 | +官方对其只用了一句话来描述 |
| 3 | +> TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. Any browser. Any host. Any OS. Open source. |
| 4 | +
|
| 5 | +大致意思为,TypeScript 是开源的,TypeScript 是 JavaScript 的类型的**超集**,它可以编译成纯 JavaScript。编译出来的 JavaScript 可以运行在任何浏览器上。TypeScript 编译工具可以运行在任何服务器和任何系统上 |
| 6 | + |
| 7 | +* 问题1: 什么是超集 |
| 8 | + |
| 9 | +**超集是集合论的术语** |
| 10 | +说到超集,不得不说另一个,子集,怎么理解这两个概念呢,举个例子 |
| 11 | + |
| 12 | +如果一个集合A里面的的所有元素集合B里面都存在,那么我们可以理解集合A是集合B的超集,反之集合B为集合A的子集 |
| 13 | + |
| 14 | +现在我们就能理解为 `Typescript` 里包含了 `Javascript` 的所有特性,这也意味着我们可以将`.js`后缀直接命名为`.ts`文件跑到`TypeScript`的编绎系统中 |
| 15 | + |
| 16 | +### Typescript 解决了什么问题 |
| 17 | + |
| 18 | +**一个事物的诞生一定会有其存在的价值** |
| 19 | + |
| 20 | +那么 `Typescript` 的价值是什么呢? |
| 21 | + |
| 22 | +回答这个问题之前,我们有必要先来了解一下 `Typescript` 的工作理念 |
| 23 | + |
| 24 | +本质上是在 `JavaScript` 上增加一套**静态类型系统**(编译时进行类型分析),强调静态类型系统是为了和运行时的类型检查机制做区分,`TypeScript` 的代码最终会被编译为 `JavaScript` |
| 25 | + |
| 26 | +我们再回到问题本身,缩小一下范围,`Typescript` 创造的价值大部分是在开发时体现的(编译时),而非运行时,如 |
| 27 | + |
| 28 | +- 强大的编辑器智能提示 (研发效率,开发体验) |
| 29 | +- 代码可读性增强 (团队协作,开发体验) |
| 30 | +- 编译时类型检查 (业务稳健,前端项目中Top10 的错误类型低级的类型错误占比达到70%) |
| 31 | + |
| 32 | +### 正文 |
| 33 | + |
| 34 | +本篇文章作为 `Vue3` 源码系列前置篇章之一,`Typescript` 的科普文,主要目的为了大家在面对 `Vue3` 源码时不会显得那么不知所措,下来将介绍一些 `Typescript` 的基本使用 |
2 | 35 |
|
3 | 36 | ### 变量申明
|
| 37 | + |
| 38 | +#### 基本类型 |
4 | 39 | ```js
|
| 40 | +let isDone: boolean = false |
5 | 41 | let num: number = 1
|
6 |
| -let str: string = 'hello' |
| 42 | +let str: string = 'vue3js.cn' |
| 43 | +let arr: number[] = [1, 2, 3] |
| 44 | +let arr2: Array<number> = [1, 2, 3] // 泛型数组 |
| 45 | +let obj: Object = {} |
| 46 | +let u: undefined = undefined; |
| 47 | +let n: null = null; |
7 | 48 | ```
|
8 | 49 |
|
9 |
| -### 函数参数类型与返回值类型 |
| 50 | +#### 类型补充 |
| 51 | + |
| 52 | +- 枚举 `Enum` |
| 53 | + |
| 54 | +使用枚举类型可以为一组数值赋予友好的名字 |
10 | 55 | ```js
|
11 |
| -function sum(a: number, b: number): number { |
12 |
| - return a + b |
| 56 | +enum LogLevel { |
| 57 | + info = 'info', |
| 58 | + warn = 'warn', |
| 59 | + error = 'error', |
13 | 60 | }
|
14 | 61 | ```
|
15 |
| -### 复合元素类型 |
| 62 | + |
| 63 | +- 元组 `Tuple` |
| 64 | + |
| 65 | +允许数组各元素的类型不必相同。 比如,你可以定义一对值分别为 string和number类型的元组 |
| 66 | + |
| 67 | +```js |
| 68 | +// Declare a tuple type |
| 69 | +let x: [string, number]; |
| 70 | +// Initialize it |
| 71 | +x = ['hello', 10]; // OK |
| 72 | +// Initialize it incorrectly |
| 73 | +x = [10, 'hello']; // Error |
| 74 | +``` |
| 75 | + |
| 76 | +- 任意值 `Any` |
| 77 | + |
| 78 | +表示任意类型,通常用于不确定内容的类型,比如来自用户输入或第三方代码库 |
| 79 | + |
16 | 80 | ```js
|
17 |
| -let arr: Array<number> = [1, 2, 3] |
18 |
| -let set: Set<number> = new Set([1, 2, 2]) |
19 |
| -let map: Map<string, number> = new Map([['key1', 1], ['key2', 2]]) |
| 81 | +let notSure: any = 4; |
| 82 | +notSure = "maybe a string instead"; |
| 83 | +notSure = false; // okay, definitely a boolean |
20 | 84 | ```
|
21 |
| -### 接口类型 |
| 85 | +- 空值 `Void` |
| 86 | + |
| 87 | +与 any 相反,通常用于函数,表示没有返回值 |
| 88 | + |
| 89 | +```js |
| 90 | +function warnUser(): void { |
| 91 | + console.log("This is my warning message"); |
| 92 | +} |
| 93 | +``` |
| 94 | + |
| 95 | +- 接口 `interface` |
| 96 | + |
| 97 | +类型契约,跟我们平常调服务端接口要先定义字段一个理 |
| 98 | + |
| 99 | +如下例子 point 跟 Point 类型必须一致,多一个少一个也是不被允许的 |
22 | 100 | ```js
|
23 | 101 | interface Point {
|
24 | 102 | x: number
|
25 | 103 | y: number
|
| 104 | + z?: number |
| 105 | + readonly l: number |
26 | 106 | }
|
27 |
| -const point: Point = { x: 10, y: 20 } |
| 107 | +const point: Point = { x: 10, y: 20, z: 30, l: 40 } |
| 108 | +const point2: Point = { x: '10', y: 20, z: 30, l: 40 } // Error |
| 109 | +const point3: Point = { x: 10, y: 20, z: 30 } // Error |
| 110 | +const point4: Point = { x: 10, y: 20, z: 30, l: 40, m: 50 } // Error |
| 111 | + |
28 | 112 | ```
|
29 |
| -### 类型别名 |
| 113 | + |
| 114 | +可选与只读 ? 表示可选参, readonly 表示只读 |
| 115 | + |
| 116 | +```js |
| 117 | +const point5: Point = { x: 10, y: 20, l: 40 } // 正常 |
| 118 | +point5.l = 50 // error |
| 119 | +``` |
| 120 | + |
| 121 | +### 函数参数类型与返回值类型 |
| 122 | + |
| 123 | +```js |
| 124 | +function sum(a: number, b: number): number { |
| 125 | + return a + b |
| 126 | +} |
| 127 | +``` |
| 128 | + |
| 129 | +配合 `interface` 使用 |
| 130 | + |
30 | 131 | ```js
|
31 |
| -type mathfunc = (a: number, b: number) => number |
32 |
| -const product: mathfunc = (a, b) => a * b |
| 132 | +interface Point { |
| 133 | + x: number |
| 134 | + y: number |
| 135 | +} |
| 136 | + |
| 137 | +function sum({ x, y}: Point): number { |
| 138 | + return x + y |
| 139 | +} |
| 140 | + |
| 141 | +sum({x:1, y:2}) // 3 |
33 | 142 | ```
|
34 | 143 |
|
35 | 144 | ### 泛型
|
36 |
| -泛型的意义在于函数的重用性,我们希望组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型 |
| 145 | +泛型的意义在于函数的重用性,设计原则希望组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型 |
37 | 146 |
|
38 |
| -**为什么不用`any`呢?** |
| 147 | +* 比如 |
| 148 | + |
| 149 | +根据业务最初的设计函数 `identity` 入参为`String` |
| 150 | +```js |
| 151 | +function identity(arg: String){ |
| 152 | + return arg |
| 153 | +} |
| 154 | +console.log(identity('100')) |
| 155 | +``` |
| 156 | + |
| 157 | +业务迭代过程参数需要支持 `Number` |
| 158 | +```js |
| 159 | +function identity(arg: String){ |
| 160 | + return arg |
| 161 | +} |
| 162 | +console.log(identity(100)) // Argument of type '100' is not assignable to parameter of type 'String'. |
| 163 | +``` |
| 164 | + |
| 165 | +#### **为什么不用`any`呢?** |
39 | 166 |
|
40 | 167 | 使用 `any` 会丢失掉一些信息,我们无法确定返回值是什么类型
|
41 | 168 | 泛型可以保证入参跟返回值是相同类型的,它是一种特殊的变量,只用于表示类型而不是值
|
42 | 169 |
|
43 |
| -语法 `<T>(arg:T):T` 其中`T`为自定义变量,能前后对应就行 |
| 170 | +语法 `<T>(arg:T):T` 其中`T`为自定义变量 |
44 | 171 |
|
45 | 172 | ```js
|
46 | 173 | const hello : string = "Hello vue!"
|
@@ -77,4 +204,97 @@ console.log(say(1)) // Argument of type '1' is not assignable to parameter of t
|
77 | 204 | console.log(say({value: 'hello vue!', length: 10})) // { value: 'hello vue!', length: 10 }
|
78 | 205 | ```
|
79 | 206 |
|
| 207 | +### 义叉类型 |
| 208 | + |
| 209 | +交叉类型(Intersection Types),将多个类型合并为一个类型 |
| 210 | + |
| 211 | +```js |
| 212 | +interface foo { |
| 213 | + x: number |
| 214 | +} |
| 215 | +interface bar { |
| 216 | + b: number |
| 217 | +} |
| 218 | +type intersection = foo & bar |
| 219 | +const result: intersection = { |
| 220 | + x: 10, |
| 221 | + b: 20 |
| 222 | +} |
| 223 | +const result1: intersection = { |
| 224 | + x: 10 |
| 225 | +} // error |
| 226 | +``` |
| 227 | + |
| 228 | +### 联合类型 |
| 229 | + |
| 230 | +交叉类型(Union Types),表示一个值可以是几种类型之一。 我们用竖线 | 分隔每个类型,所以 number | string | boolean表示一个值可以是 number, string,或 boolean |
| 231 | + |
| 232 | +```js |
| 233 | +type arg = string | number | boolean |
| 234 | +const foo = (arg: arg):any =>{ |
| 235 | + console.log(arg) |
| 236 | +} |
| 237 | +foo(1) |
| 238 | +foo('2') |
| 239 | +foo(true) |
| 240 | +``` |
| 241 | + |
| 242 | +### 函数重载 |
| 243 | + |
| 244 | +函数重载(Function Overloading), 允许创建数项名称相同但输入输出类型或个数不同的子程序,可以简单理解为一个函数可以执行多项任务的能力 |
| 245 | + |
| 246 | +例我们有一个`add`函数,它可以接收`string`类型的参数进行拼接,也可以接收`number`类型的参数进行相加 |
| 247 | + |
| 248 | +```js |
| 249 | +function add (arg1: string, arg2: string): string |
| 250 | +function add (arg1: number, arg2: number): number |
| 251 | + |
| 252 | +// 实现 |
| 253 | +function add <T,U>(arg1: T, arg2: U) { |
| 254 | + // 在实现上我们要注意严格判断两个参数的类型是否相等,而不能简单的写一个 arg1 + arg2 |
| 255 | + if (typeof arg1 === 'string' && typeof arg2 === 'string') { |
| 256 | + return arg1 + arg2 |
| 257 | + } else if (typeof arg1 === 'number' && typeof arg2 === 'number') { |
| 258 | + return arg1 + arg2 |
| 259 | + } |
| 260 | +} |
| 261 | + |
| 262 | +add(1, 2) // 3 |
| 263 | +add('1','2') //'12' |
| 264 | +``` |
| 265 | + |
| 266 | +### 总结 |
| 267 | + |
| 268 | +通过本篇文章,相信大家对`Typescript`不会再感到陌生了 |
| 269 | + |
| 270 | +下面我们来看看在`Vue`源码`Typescript`是如何书写的,这里我们以`defineComponent`函数为例,大家可以通过这个实例,再结合文章的内容,去理解,加深`Typescript`的认识 |
| 271 | + |
| 272 | +```js |
| 273 | +// overload 1: direct setup function |
| 274 | +export function defineComponent<Props, RawBindings = object>( |
| 275 | + setup: ( |
| 276 | + props: Readonly<Props>, |
| 277 | + ctx: SetupContext |
| 278 | + ) => RawBindings | RenderFunction |
| 279 | +): { |
| 280 | + new (): ComponentPublicInstance< |
| 281 | + Props, |
| 282 | + RawBindings, |
| 283 | + {}, |
| 284 | + {}, |
| 285 | + {}, |
| 286 | + // public props |
| 287 | + VNodeProps & Props |
| 288 | + > |
| 289 | +} & FunctionalComponent<Props> |
| 290 | + |
| 291 | +// defineComponent一共有四个重载,这里省略三个 |
| 292 | + |
| 293 | +// implementation, close to no-op |
| 294 | +export function defineComponent(options: unknown) { |
| 295 | + return isFunction(options) ? { setup: options } : options |
| 296 | +} |
| 297 | + |
| 298 | +``` |
| 299 | + |
80 | 300 |
|
0 commit comments