├── README.md ├── test.ts └── tsconfig.json /README.md: -------------------------------------------------------------------------------- 1 | # ts 更新日志速读, 持续更新... 2 | 3 | 个人学习为主, 顺便方便他人.😉 4 | 5 | ## 🔥 阅读须知 6 | 7 | 由于个人能力有限, 所以本文只从"typescript 更新日志"中筛选**类型/语法**相关的知识点, 3.1 之前的版本都是一些基础知识, 所以只摘取了部分内容. 如有错误还请各位多多指点帮助. 8 | 9 | **注意**: 类型推断的变化(放宽/收窄)和配置项以及 ECMA 的新增语法选录. 10 | 11 | ## v4.5 12 | 13 | ### 新增 Await 类型 14 | 15 | 获取 Prmoise 的 resolve 的值的类型 16 | 17 | ```typescript 18 | // Promise 19 | const p = Promise.resolve(123); 20 | // Awaited> === number 21 | type B = Awaited; 22 | 23 | // 类型参数不是Promise类型, 24 | // 那么不处理, 直接返回 25 | type S = Awaited; // string 26 | ``` 27 | 28 | ### 导入名称修饰符"type" 29 | 30 | 之前版本就支持"import type {xx} from 'xxx'"的语法, 现在进步支持对单个导入项标记"type". 31 | 32 | ```typescript 33 | import { someFunc, type BaseType } from "./some-module.js"; 34 | ``` 35 | 36 | ### 检查类的私有属性是否存在 37 | 38 | 同步兼容 ecma 语法 39 | 40 | ```typescript 41 | class Person { 42 | #name: string; 43 | constructor(name: string) { 44 | this.#name = name; 45 | } 46 | equals(other: unknown) { 47 | return other && 48 | typeof other === "object" && 49 | #name in other && // <- 🔥新语法 50 | this.#name === other.#name; 51 | } 52 | } 53 | ``` 54 | 55 | ### 导入断言 56 | 57 | 同步兼容 ecma 语法, 对导入文件进行运行时判断, ts 不做任何判断. 58 | 59 | ```typescript 60 | import obj from "./something.json" assert { type: "json" }; 61 | ``` 62 | 63 | 还有"import"函数的语法: 64 | 65 | ```typescript 66 | const obj = await import("./something.json", { 67 | assert: { type: "json" }, 68 | }); 69 | ``` 70 | 71 | ### 未使用的变量不删除 72 | tsconfig增加了一个配置项"preserveValueImports", 用来保留未使用的变量, 意义在于vue的setup模式下, js定义的变量, 只在模板中使用, ts识别不到"已使用", 有了这个选项就可以保留变量. 73 | ```html 74 | 75 | 78 | 79 | ``` 80 | 81 | ## v4.4 82 | 83 | ### 类型保护更智能 84 | 85 | 常见的类型保护如下: 86 | 87 | ```typescript 88 | function nOrs() { 89 | return Math.random() > 0.5 ? 123 : "abc"; 90 | } 91 | let input = nOrs(); 92 | if (typeof input === "number") { 93 | input++; 94 | } 95 | ``` 96 | 97 | 如果`typeof input === 'number'`抽象到变量中,在 4.4 版本之前类型保护会失效,但是在 4.4 中 ts 可以正确的类型保护了. 98 | 99 | ```typescript 100 | function nOrs() { 101 | return Math.random() > 0.5 ? 123 : "abc"; 102 | } 103 | const input = nOrs(); 104 | const isNumber = typeof input === "number"; 105 | if (isNumber) { 106 | // 失效,无法知道input是number类型 107 | input++; 108 | } 109 | ``` 110 | 111 | **注意**: 要求被保护的必须是"const 的变量"或者"realdonly 的属性", 比如上面的 input 和下面的"n"属性. 112 | 113 | ```typescript 114 | interface A { 115 | readonly n: number | string; 116 | } 117 | 118 | const a: A = { n: Math.random() > 0.5 ? "123" : 321 }; 119 | const isNumber = typeof a.n === "number"; 120 | if (isNumber) { 121 | // r是number 122 | const r = a.n; 123 | } 124 | ``` 125 | 126 | ### 类型保护更深入 127 | 128 | 通过属性的判断可以缩小联合类型的范围. 129 | 130 | ```typescript 131 | type Shape = 132 | | { kind: "circle"; radius: number } 133 | | { kind: "square"; sideLength: number }; 134 | 135 | function area(shape: Shape): number { 136 | const isCircle = shape.kind === "circle"; 137 | if (isCircle) { 138 | // We know we have a circle here! 139 | return Math.PI * shape.radius ** 2; 140 | } else { 141 | // We know we're left with a square here! 142 | return shape.sideLength ** 2; 143 | } 144 | } 145 | ``` 146 | 147 | ### ⚡ 增加支持 symbol 类型做为对象类型的键 148 | 149 | 之前只支持"string | number ", 造成对 Object 类型的键的描述不全面, 现在解决了. 150 | 151 | ```typescript 152 | interface Test1 { 153 | [k: string | number | symbol]: any; 154 | } 155 | 156 | type Test2 = { 157 | [k in string | number | symbol]: any; 158 | }; 159 | ``` 160 | 161 | ## 类中的 static 块 162 | 163 | 同步支持 es 新语法 164 | 165 | ```typescript 166 | class Foo { 167 | static #count = 0; 168 | 169 | get count() { 170 | return Foo.#count; 171 | } 172 | 173 | static { 174 | try { 175 | const lastInstances = loadLastInstances(); 176 | Foo.#count += lastInstances.length; 177 | } catch {} 178 | } 179 | } 180 | ``` 181 | 182 | ## v4.3 183 | 184 | ### override 关键字 185 | 186 | "override"是给类提供的语法, 用来标记子类中的属性/方法是否覆盖父类的同名属性/方法. 187 | 188 | ```typescript 189 | class A {} 190 | 191 | class B extends A { 192 | // 提示不能用override, 因为基类中没有"a"字段. 193 | override a() {} 194 | } 195 | ``` 196 | 197 | ### 注释中的{@link xxx} 198 | 199 | 点击跳转到指定代码, 属于行内标签. 200 | 201 | ```typescript 202 | /** 203 | * To be called 70 to 80 days after {@link plantCarrot}. 204 | */ 205 | function harvestCarrot(carrot: Carrot) {} 206 | /** 207 | * Call early in spring for best results. Added in v2.1.0. 208 | * @param seed Make sure it's a carrot seed! 209 | */ 210 | function plantCarrot(seed: Seed) { 211 | // TODO: some gardening 212 | } 213 | ``` 214 | 215 | ## v4.2 216 | 217 | ### 元祖支持可选符号 218 | 219 | ```typescript 220 | let c: [string, string?] = ["hello"]; 221 | c = ["hello", "world"]; 222 | ``` 223 | 224 | ### 元祖类型定义支持任意位置使用"..." 225 | 226 | 但是要求尾部不能有可选元素(?)和"..."出现 227 | 228 | ```typescript 229 | let foo: [...string[], number]; 230 | 231 | foo = [123]; 232 | foo = ["hello", 123]; 233 | foo = ["hello!", "hello!", "hello!", 123]; 234 | 235 | let bar: [boolean, ...string[], boolean]; 236 | 237 | bar = [true, false]; 238 | bar = [true, "some text", false]; 239 | bar = [true, "some", "separated", "text", false]; 240 | ``` 241 | 242 | **错误示例** 243 | 244 | ```typescript 245 | let StealersWheel: [...Clown[], "me", ...Joker[]]; 246 | // A rest element cannot follow another rest element. 247 | 248 | let StringsAndMaybeBoolean: [...string[], boolean?]; 249 | // An optional element cannot follow a rest element. 250 | ``` 251 | 252 | ## v4.1 253 | 254 | ### 模板字符串类型 255 | 256 | 语法和 es6 中的"``"用法一样, 只不过这里用来包裹类型: 257 | 258 | ```typescript 259 | type World = "world"; 260 | // hello world 261 | type Greeting = `hello ${World}`; 262 | 263 | type Color = "red" | "blue"; 264 | type Quantity = "one" | "two"; 265 | // "one fish" | "two fish" | "red fish" | "blue fish" 266 | type SeussFish = `${Quantity | Color} fish`; 267 | ``` 268 | 269 | ### 新增字符串类型 270 | 271 | ts 系统新增 Uppercase / Lowercase / Capitalize / Uncapitalize 类型 272 | 273 | ```typescript 274 | // ABC 275 | type S1 = Uppercase<"abc">; 276 | // abc 277 | type S2 = Lowercase<"ABC">; 278 | // Abc 279 | type S3 = Capitalize<"abc">; 280 | // aBC 281 | type S4 = Uncapitalize<"ABC">; 282 | ``` 283 | 284 | ### key in 结构中使用 as 285 | 286 | 获取 key 后用来获取值, 但是不用 key,只用值, 然后重新标注 key. 287 | 288 | ```typescript 289 | type PPP = { 290 | [K in keyof T as "ww"]: T[K]; 291 | }; 292 | 293 | // type A = {ww:1|'2'} 294 | type A = PPP<{ a: 1; b: "2" }>; 295 | ``` 296 | 297 | ### Promise 中 resolve 的参数不再为可选 298 | 299 | 4.1 之前版本 resolve 可以不传参数, 现在不允许. 300 | 301 | ```typescript 302 | new Promise((resolve) => { 303 | // 报错,resolve参数不能为空 304 | resolve(); 305 | }); 306 | ``` 307 | 308 | **除非**对 Promise 传入类型参数"void": 309 | 310 | ```typescript 311 | // 正确 312 | new Promise((resolve) => { 313 | resolve(); 314 | }); 315 | ``` 316 | 317 | ## v4.0 318 | 319 | ### 元祖的 rest 项可精准推导 320 | 321 | 以前获取元组除了第一个元素的类型: 322 | 323 | ```typescript 324 | function tail(arr: readonly [any, ...T]) { 325 | const [_ignored, ...rest] = arr; 326 | return rest; 327 | } 328 | 329 | const myTuple = [1, 2, 3, 4] as const; 330 | const myArray = ["hello", "world"]; 331 | 332 | const r1 = tail(myTuple); 333 | // const r1: [2, 3, 4] 334 | const r2 = tail([...myTuple, ...myArray] as const); 335 | // const r2: [2, 3, 4, ...string[]] 336 | ``` 337 | 338 | ### rest 元素可以出现在元组中的任何地方, 而不仅仅是在最后! 339 | 340 | 这里要求"..."后面的元素必须是元组, 只有最后一个元素可以是"..."+"数组" 341 | 342 | ```typescript 343 | type Strings = [string, string]; 344 | type Numbers = [number, number]; 345 | type StrStrNumNumBool = [...Strings, ...Numbers, boolean]; 346 | type StrStrNumNumBool = [...[string], string, ...string[]]; 347 | ``` 348 | 349 | ### 元组元素支持带标签 350 | 351 | 主要是为了兼容函数参数的定义, 因为函数的参数都是有参数名的. 352 | 353 | ```typescript 354 | type Range = [start: number, end: number]; 355 | ``` 356 | 357 | ### class 中构造函数的赋值可以直接推导出属性的类型 358 | 359 | 定义属性的时候, 可以省略类型标注. 360 | 361 | ```typescript 362 | class A { 363 | // 此时a直接被推导出是number类型 364 | // 类型可以省略, a不能省略 365 | a; 366 | constructor() { 367 | this.a = 1; 368 | } 369 | } 370 | ``` 371 | 372 | ### try/catch 中的 catch 的参数变成 unknown 类型 373 | 374 | 使用之前需要进行判断或断言. 375 | 376 | ```typescript 377 | try { 378 | // ... 379 | } catch (e) { 380 | e; // unknown 381 | } 382 | ``` 383 | 384 | ### /\*_ @deprecated _/ 385 | 386 | 标注弃用. 387 | ![](https://devblogs.microsoft.com/typescript/wp-content/uploads/sites/11/2020/06/deprecated_4-0.png) 388 | 389 | ## v3.9 390 | 391 | ### // @ts-expect-error 392 | 393 | 用来屏蔽错误, 不同于"// @ts-ignore"的使用动机, "// @ts-expect-error"主要的使用场景是你故意产生错的, 比如测试用例. 394 | 395 | ```typescript 396 | function doStuff(abc: string, xyz: string) { 397 | assert(typeof abc === "string"); 398 | assert(typeof xyz === "string"); 399 | } 400 | 401 | // 你想测试传入数字参数, 但是ts会自动推断错误, 这不是你想要的, 所以加"// @ts-expect-error" 402 | expect(() => { 403 | // @ts-expect-error 404 | doStuff(123, 456); 405 | }).toThrow(); 406 | ``` 407 | 408 | ## v3.8 409 | 410 | ### import type / export type 411 | 412 | 为仅类型导入和导出添加了新语法。 413 | 414 | ```typescript 415 | import type { SomeThing } from "./some-module.js"; 416 | export type { SomeThing }; 417 | ``` 418 | 419 | ### 类的私有属性"#" 420 | 421 | 同步 ecma 的私有属性语法, 不同于 private 修饰符, #后的私有属性即便在编译成 js 后依然不能在类的外部访问. 422 | 423 | ```typescript 424 | class Person { 425 | #name: string; 426 | constructor(name: string) { 427 | this.#name = name; 428 | } 429 | greet() { 430 | console.log(`Hello, my name is ${this.#name}!`); 431 | } 432 | } 433 | ``` 434 | 435 | ### export \* as ns 436 | 437 | 导出语法, 同步于 ecma2020 的语法 438 | 439 | ```typescript 440 | export * as utilities from "./utilities.js"; 441 | ``` 442 | 443 | ### 顶级作用域的 await 关键字 444 | 445 | 同步于 ecma 的语法, 要求必须是模块内使用, 所以起码要有一个"空导出(export {})" 446 | 447 | ```typescript 448 | const response = await fetch("..."); 449 | const greeting = await response.text(); 450 | console.log(greeting); 451 | // Make sure we're a module 452 | export {}; 453 | ``` 454 | 455 | 还有一个限制就是配置项: "target"为 es2017 以上且"module"为"esnext" 456 | 457 | ## v3.4 458 | 459 | ### readonlyArray 460 | 461 | 只读数组 462 | 463 | ```typescript 464 | function foo(arr: ReadonlyArray) { 465 | arr.slice(); // okay 466 | arr.push("hello!"); // error! 467 | } 468 | ``` 469 | 470 | ### readonly T[] 471 | 472 | 和 readonlyArray 同效果. 473 | 474 | ```typescript 475 | function foo(arr: readonly string[]) { 476 | arr.slice(); // okay 477 | arr.push("hello!"); // error! 478 | } 479 | ``` 480 | 481 | ### readonly 元祖 482 | 483 | ```typescript 484 | function foo(pair: readonly [string, string]) { 485 | console.log(pair[0]); // okay 486 | pair[1] = "hello!"; // error 487 | } 488 | ``` 489 | 490 | ### const 断言 491 | 492 | 使用 const 断言后, 推断出的类型都是"不可修改"的. 493 | 494 | ```typescript 495 | // Type '"hello"',不能修改x的值了 496 | let x = "hello" as const; 497 | // Type 'readonly [10, 20]' 498 | let y = [10, 20] as const; 499 | // Type '{ readonly text: "hello" }' 500 | let z = { text: "hello" } as const; 501 | ``` 502 | 503 | 还可以写使用尖角号断言: 504 | 505 | ```typescript 506 | // Type '"hello"' 507 | let x = "hello"; 508 | // Type 'readonly [10, 20]' 509 | let y = [10, 20]; 510 | // Type '{ readonly text: "hello" }' 511 | let z = { text: "hello" }; 512 | ``` 513 | 514 | **注意**const 断言只能立即应用于简单的文字表达式。 515 | 516 | ```typescript 517 | // Error! A 'const' assertion can only be applied to a 518 | // to a string, number, boolean, array, or object literal. 519 | let a = (Math.random() < 0.5 ? 0 : 1) as const; 520 | let b = (60 * 60 * 1000) as const; 521 | // Works! 522 | let c = Math.random() < 0.5 ? (0 as const) : (1 as const); 523 | let d = 3_600_000 as const; 524 | ``` 525 | 526 | ## v3.2 527 | 528 | ### bigint 类型 529 | 530 | bigint 是 ECMAScript 即将推出的提案的一部分. 531 | 532 | ```typescript 533 | let foo: bigint = BigInt(100); // the BigInt function 534 | let bar: bigint = 100n; // a BigInt literal 535 | ``` 536 | 537 | ## v3.1 538 | 539 | ### typesVersions 540 | 541 | 一个项目支持多个声明文件, 比如下面版本大于 3.1 就去项目下的"ts3.1"文件夹下查找声明文件. 542 | 543 | ```javascript 544 | // tsconfig.js 545 | { 546 | "typesVersions": { 547 | ">=3.1": { "*": ["ts3.1/*"] } 548 | ">=3.2": { "*": ["ts3.2/*"] } 549 | } 550 | } 551 | ``` 552 | 553 | ## v2.9 554 | 555 | ### import.meta 556 | 557 | 同步 ecma 语法, 存储模块信息的对象. 具体参看[MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/import.meta). 558 | 559 | ### 支持导入 JSON 文件 560 | 561 | 通过在 tsconfig 中开启 resolveJsonModule:true; 562 | 563 | ### 新的--declarationMap 564 | 565 | 随着--declaration 一起启用--declarationMap,编译器在生成.d.ts 的同时还会生成.d.ts.map。 语言服务现在也能够理解这些 map 文件,将声明文件映射到源码。 566 | 567 | 换句话说,在启用了--declarationMap 后生成的.d.ts 文件里点击 go-to-definition,将会导航到源文件里的位置(.ts),而不是导航到.d.ts 文件里。 568 | 569 | ## v2.8 570 | 571 | ### -/+ 572 | 573 | 对 readonly/?修饰符进行增删控制. 574 | 575 | ```typescript 576 | type MutableRequired = { -readonly [P in keyof T]-?: T[P] }; // 移除readonly和? 577 | type ReadonlyPartial = { +readonly [P in keyof T]+?: T[P] }; // 添加readonly和? 578 | ``` 579 | 580 | ## v2.7 581 | 582 | ### 用常量命名的类型的属性 583 | 584 | 常量可以用来表示类型的属性. 585 | 586 | ```typescript 587 | export const Key = "123abc"; 588 | export interface Map { 589 | [Key]: string; 590 | } 591 | ``` 592 | 593 | ### unique symbol 类型 594 | 595 | symbol 类型的子类型, 只能在有 const/readonly 的情况下才可以使用, 表示唯一不可变. 596 | 597 | ```typescript 598 | // 正确 599 | const bar: unique symbol = Symbol(); 600 | // 正确 601 | interface M { 602 | readonly a: unique symbol; 603 | } 604 | 605 | // 错误, 不能用let 606 | let bar: unique symbol = Symbol(); 607 | ``` 608 | 609 | ### let x!: number[]; 610 | 611 | 初始化变量直接断言已赋值. 612 | 613 | ```typescript 614 | // 没有这!, 会提示"在赋值前不能进行操作" 615 | let x!: number[]; 616 | // 这个函数的赋值ts是检测不到的, 所以上一行用了"!" 617 | initialize(); 618 | x.push(4); 619 | 620 | function initialize() { 621 | x = [0, 1, 2, 3]; 622 | } 623 | ``` 624 | 625 | ## v2.6 626 | 627 | ### 中文 tsc 628 | 629 | ```shell 630 | tsc --locale zh-cn 631 | ``` 632 | 633 | 后面的命令行提示都会以中文形式显示. 634 | 635 | ### @ts-ignore 636 | 637 | @ts-ignore 注释隐藏下一行错误 638 | 639 | ```typescript 640 | if (false) { 641 | // @ts-ignore:无法被执行的代码的错误 642 | console.log("hello"); 643 | } 644 | ``` 645 | 646 | ## v2.5 647 | 648 | ### try/catch 中 catch 可以省略参数 649 | 650 | ```typescript 651 | let input = "..."; 652 | try { 653 | JSON.parse(input); 654 | } catch { 655 | // ^ 注意我们的 `catch` 语句并没有声明一个变量 656 | console.log("传入的 JSON 不合法\n\n" + input); 657 | } 658 | ``` 659 | 660 | ## v2.4 661 | 662 | ### 动态的 import, 很多打包工具都已经支持 import 异步加载模块. 663 | 664 | 注意: 需要把 tsconfig 中的目标模块设置为"esnext". 665 | 666 | ```typescript 667 | async function getZipFile(name: string, files: File[]): Promise { 668 | const zipUtil = await import("./utils/create-zip-file"); 669 | const zipContents = await zipUtil.getContentAsBlob(files); 670 | return new File(zipContents, name); 671 | } 672 | ``` 673 | 674 | ## v2.3 675 | 676 | ### @ts-nocheck 677 | 678 | 通过"// @ts-nocheck"注释来声明文件不检测类型. 679 | 680 | ```typescript 681 | // @ts-nocheck 682 | let a; 683 | // 本应该提示a未赋值. 684 | a.push(123); 685 | ``` 686 | 687 | ## v2.0 688 | 689 | ### 快捷外部模块声明 690 | 691 | 当你使用一个新模块时,如果不想要花费时间书写一个声明时,现在你可以使用快捷声明以便以快速开始。 692 | 693 | ```typescript 694 | // xxx.d.ts 695 | declare module "hot-new-module"; 696 | ``` 697 | 698 | 所有导入都是**any**类型 699 | 700 | ```typescript 701 | // x,y 都是any 702 | import x, { y } from "hot-new-module"; 703 | x(y); 704 | ``` 705 | 706 | ### 模块名称中的通配符 707 | 708 | 这个 vue 初始化的项目中就有, 用来声明.vue 文件是个组件. 709 | 710 | ```typescript 711 | declare module "*.vue" { 712 | import { DefineComponent } from "vue"; 713 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types 714 | const component: DefineComponent<{}, {}, any>; 715 | export default component; 716 | } 717 | ``` 718 | 719 | 还有: 720 | 721 | ```typescript 722 | declare module "*!text" { 723 | const content: string; 724 | export default content; 725 | } 726 | // Some do it the other way around. 727 | declare module "json!*" { 728 | const value: any; 729 | export default value; 730 | } 731 | ``` 732 | 733 | ## 其他 734 | 🤩 有些我也不知道是那个版本的语法, 但是在别人的代码中看到了, 所以收录下. 735 | 736 | ### 构造函数的参数可以用来声明类的属性 737 | 算是一种简写, 在vue3的computed部分的代码中看到. 738 | ```typescript 739 | class Www { 740 | constructor(private readonly a1: number) { 741 | this.a1 = 123; 742 | } 743 | } 744 | // 等价于 745 | class Www { 746 | private readonly a1: number; 747 | constructor() { 748 | this.a1 = 123; 749 | } 750 | } 751 | ``` 752 | -------------------------------------------------------------------------------- /test.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/any86/ts-log-cn/62289af28993cd9b42d3634c44e3d30ce6629fe6/test.ts -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // 不报告执行不到的代码错误。 4 | "allowUnreachableCode": true, 5 | "strictNullChecks": true, 6 | // 严格模式, 强烈建议开启 7 | "strict": true, 8 | // 支持别名导入: 9 | // import * as React from "react" 10 | "esModuleInterop": true, 11 | // 目标js的版本 12 | "target": "ESNext", 13 | // 目标代码的模块结构版本 14 | "module": "ESNext", 15 | // 在表达式和声明上有隐含的 any类型时报错。 16 | "noImplicitAny": true, 17 | // "__extends"等函数 18 | // 将以"import {__extends} from 'tslib'的形式导入 19 | "importHelpers": true, 20 | // 删除注释 21 | "removeComments": true, 22 | // 保留 const和 enum声明 23 | "preserveConstEnums": false, 24 | // 生成sourceMap 25 | "sourceMap": true, 26 | // 目标文件所在路径 27 | "outDir": "./lib", 28 | // 编译过程中需要引入的库文件的列表 29 | "lib": ["dom", "esnext"], 30 | // 额外支持解构/forof等功能 31 | "downlevelIteration": true, 32 | // 是否生成声明文件 33 | "declaration": false, 34 | // 声明文件路径 35 | // "declarationDir": "./lib", 36 | // 此处设置为node,才能解析import xx from 'xx' 37 | "moduleResolution": "node", 38 | "baseUrl": ".", 39 | "rootDir": ".", 40 | 41 | }, 42 | // 入口文件 43 | "include": ["./test.ts"] 44 | } --------------------------------------------------------------------------------