個人學習為主, 順便方便他人.?
首發地址: https://github.com/any86/ts-l..., 本文內容也會持續更新.
? 閱讀須知
由於個人能力有限, 所以本文只從"typescript 更新日誌"中篩選型別/語法相關的知識點, 3.1之前的版本都是一些基礎知識, 所以只摘取了部分內容. 如有錯誤還請各位多多指點幫助.
注意: 型別推斷的變化(放寬/收窄)和配置項以及 ECMA 的新增語法選錄.
v4.5
新增 Await 型別
獲取 Prmoise 的 resolve 的值的型別
// Promise<number>
const p = Promise.resolve(123);
// Awaited<Promise<number>> === number
type B = Awaited<typeof p>;
// 型別引數不是Promise型別,
// 那麼不處理, 直接返回
type S = Awaited<string>; // string
匯入名稱修飾符"type"
之前版本就支援"import type {xx} from 'xxx'"的語法, 現在進步支援對單個匯入項標記"type".
import { someFunc, type BaseType } from "./some-module.js";
檢查類的私有屬性是否存在
同步相容ecma語法
class Person {
#name: string;
constructor(name: string) {
this.#name = name;
}
equals(other: unknown) {
return other &&
typeof other === "object" &&
#name in other && // <- ?新語法
this.#name === other.#name;
}
}
匯入斷言
同步相容ecma語法, 對匯入檔案進行執行時判斷, ts不做任何判斷.
import obj from "./something.json" assert { type: "json" };
還有"import"函式的語法:
const obj = await import("./something.json", {
assert: { type: "json" },
});
v4.4
型別保護更智慧
常見的型別保護如下:
function nOrs() {
return Math.random() > 0.5 ? 123 : "abc";
}
let input = nOrs();
if (typeof input === "number") {
input++;
}
如果typeof input === 'number'
抽象到變數中,在 4.4 版本之前型別保護會失效,但是在 4.4 中 ts 可以正確的型別保護了.
function nOrs() {
return Math.random() > 0.5 ? 123 : "abc";
}
const input = nOrs();
const isNumber = typeof input === "number";
if (isNumber) {
// 失效,無法知道input是number型別
input++;
}
注意: 要求被保護的必須是"const 的變數"或者"realdonly 的屬性", 比如上面的 input 和下面的"n"屬性.
interface A {
readonly n: number | string;
}
const a: A = { n: Math.random() > 0.5 ? "123" : 321 };
const isNumber = typeof a.n === "number";
if (isNumber) {
// r是number
const r = a.n;
}
型別保護更深入
通過屬性的判斷可以縮小聯合型別的範圍.
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; sideLength: number };
function area(shape: Shape): number {
const isCircle = shape.kind === "circle";
if (isCircle) {
// We know we have a circle here!
return Math.PI * shape.radius ** 2;
} else {
// We know we're left with a square here!
return shape.sideLength ** 2;
}
}
⚡ 增加支援 symbol 型別做為物件型別的鍵
之前只支援"string | number ", 造成對 Object 型別的鍵的描述不全面, 現在解決了.
interface Test1 {
[k: string | number | symbol]: any;
}
type Test2 = {
[k in string | number | symbol]: any;
};
類中的 static 塊
同步支援 es 新語法
class Foo {
static #count = 0;
get count() {
return Foo.#count;
}
static {
try {
const lastInstances = loadLastInstances();
Foo.#count += lastInstances.length;
} catch {}
}
}
v4.3
override 關鍵字
"override"是給類提供的語法, 用來標記子類中的屬性/方法是否覆蓋父類的同名屬性/方法.
class A {}
class B extends A {
// 提示不能用override, 因為基類中沒有"a"欄位.
override a() {}
}
註釋中的@link
點選跳轉到指定程式碼.
/**
* To be called 70 to 80 days after {@link plantCarrot}.
*/
function harvestCarrot(carrot: Carrot) {}
/**
* Call early in spring for best results. Added in v2.1.0.
* @param seed Make sure it's a carrot seed!
*/
function plantCarrot(seed: Seed) {
// TODO: some gardening
}
v4.2
元祖支援可選符號
let c: [string, string?] = ["hello"];
c = ["hello", "world"];
元祖型別定義支援任意位置使用"..."
但是要求尾部不能有可選元素(?)和"..."出現
let foo: [...string[], number];
foo = [123];
foo = ["hello", 123];
foo = ["hello!", "hello!", "hello!", 123];
let bar: [boolean, ...string[], boolean];
bar = [true, false];
bar = [true, "some text", false];
bar = [true, "some", "separated", "text", false];
錯誤示例
let StealersWheel: [...Clown[], "me", ...Joker[]];
// A rest element cannot follow another rest element.
let StringsAndMaybeBoolean: [...string[], boolean?];
// An optional element cannot follow a rest element.
v4.1
模板字串型別
語法和 es6 中的"``"用法一樣, 只不過這裡用來包裹型別:
type World = "world";
// hello world
type Greeting = `hello ${World}`;
type Color = "red" | "blue";
type Quantity = "one" | "two";
// "one fish" | "two fish" | "red fish" | "blue fish"
type SeussFish = `${Quantity | Color} fish`;
新增字串型別
ts 系統新增 Uppercase / Lowercase / Capitalize / Uncapitalize 型別
// ABC
type S1 = Uppercase<"abc">;
// abc
type S2 = Lowercase<"ABC">;
// Abc
type S3 = Capitalize<"abc">;
// aBC
type S4 = Uncapitalize<"ABC">;
key in 結構中使用 as
獲取 key 後用來獲取值, 但是不用 key,只用值, 然後重新標註 key.
type PPP<T> = {
[K in keyof T as "ww"]: T[K];
};
// type A = {ww:1|'2'}
type A = PPP<{ a: 1; b: "2" }>;
v4.0
元祖的 rest 項可精準推導
以前獲取元組除了第一個元素的型別:
function tail<T extends any[]>(arr: readonly [any, ...T]) {
const [_ignored, ...rest] = arr;
return rest;
}
const myTuple = [1, 2, 3, 4] as const;
const myArray = ["hello", "world"];
const r1 = tail(myTuple);
// const r1: [2, 3, 4]
const r2 = tail([...myTuple, ...myArray] as const);
// const r2: [2, 3, 4, ...string[]]
rest 元素可以出現在元組中的任何地方, 而不僅僅是在最後!
這裡要求"..."後面的元素必須是元組, 只有最後一個元素可以是"..."+"陣列"
type Strings = [string, string];
type Numbers = [number, number];
type StrStrNumNumBool = [...Strings, ...Numbers, boolean];
type StrStrNumNumBool = [...[string], string, ...string[]];
元組元素支援帶標籤
主要是為了相容函式引數的定義, 因為函式的引數都是有引數名的.
type Range = [start: number, end: number];
class 中建構函式的賦值可以直接推匯出屬性的型別
定義屬性的時候, 可以省略型別標註.
class A {
// 此時a直接被推匯出是number型別
// 型別可以省略, a不能省略
a;
constructor() {
this.a = 1;
}
}
try/catch 中的 catch 的引數變成 unknown 型別
使用之前需要進行判斷或斷言.
try {
// ...
} catch (e) {
e; // unknown
}
/*_ @deprecated _/
標註棄用.
v3.9
// @ts-expect-error
用來遮蔽錯誤, 不同於"// @ts-ignore"的使用動機, "// @ts-expect-error"主要的使用場景是你故意產生錯的, 比如測試用例.
function doStuff(abc: string, xyz: string) {
assert(typeof abc === "string");
assert(typeof xyz === "string");
}
// 你想測試傳入數字引數, 但是ts會自動推斷錯誤, 這不是你想要的, 所以加"// @ts-expect-error"
expect(() => {
// @ts-expect-error
doStuff(123, 456);
}).toThrow();
v3.8
import type / export type
為僅型別匯入和匯出新增了新語法。
import type { SomeThing } from "./some-module.js";
export type { SomeThing };
類的私有屬性"#"
同步 ecma 的私有屬性語法, 不同於 private 修飾符, #後的私有屬性即便在編譯成 js 後依然不能在類的外部訪問.
class Person {
#name: string;
constructor(name: string) {
this.#name = name;
}
greet() {
console.log(`Hello, my name is ${this.#name}!`);
}
}
export * as ns
匯出語法, 同步於 ecma2020 的語法
export * as utilities from "./utilities.js";
頂級作用域的 await 關鍵字
同步於 ecma 的語法, 要求必須是模組內使用, 所以起碼要有一個"空匯出(export {})"
const response = await fetch("...");
const greeting = await response.text();
console.log(greeting);
// Make sure we're a module
export {};
還有一個限制就是配置項: "target"為 es2017 以上且"module"為"esnext"
v3.4
readonlyArray
只讀陣列
function foo(arr: ReadonlyArray<string>) {
arr.slice(); // okay
arr.push("hello!"); // error!
}
readonly T[]
和 readonlyArray 同效果.
function foo(arr: readonly string[]) {
arr.slice(); // okay
arr.push("hello!"); // error!
}
readonly 元祖
function foo(pair: readonly [string, string]) {
console.log(pair[0]); // okay
pair[1] = "hello!"; // error
}
const 斷言
使用 const 斷言後, 推斷出的型別都是"不可修改"的.
// Type '"hello"',不能修改x的值了
let x = "hello" as const;
// Type 'readonly [10, 20]'
let y = [10, 20] as const;
// Type '{ readonly text: "hello" }'
let z = { text: "hello" } as const;
還可以寫使用尖角號斷言:
// Type '"hello"'
let x = <const>"hello";
// Type 'readonly [10, 20]'
let y = <const>[10, 20];
// Type '{ readonly text: "hello" }'
let z = <const>{ text: "hello" };
注意const 斷言只能立即應用於簡單的文字表示式。
// Error! A 'const' assertion can only be applied to a
// to a string, number, boolean, array, or object literal.
let a = (Math.random() < 0.5 ? 0 : 1) as const;
let b = (60 * 60 * 1000) as const;
// Works!
let c = Math.random() < 0.5 ? (0 as const) : (1 as const);
let d = 3_600_000 as const;
v3.2
bigint 型別
bigint 是 ECMAScript 即將推出的提案的一部分.
let foo: bigint = BigInt(100); // the BigInt function
let bar: bigint = 100n; // a BigInt literal
v3.1
typesVersions
一個專案支援多個宣告檔案, 比如下面版本大於 3.1 就去專案下的"ts3.1"資料夾下查詢宣告檔案.
// tsconfig.js
{
"typesVersions": {
">=3.1": { "*": ["ts3.1/*"] }
">=3.2": { "*": ["ts3.2/*"] }
}
}
v2.9
import 型別
新的--declarationMap
隨著--declaration 一起啟用--declarationMap,編譯器在生成.d.ts 的同時還會生成.d.ts.map。 語言服務現在也能夠理解這些 map 檔案,將宣告檔案對映到原始碼。
換句話說,在啟用了--declarationMap 後生成的.d.ts 檔案裡點選 go-to-definition,將會導航到原始檔裡的位置(.ts),而不是導航到.d.ts 檔案裡。
v2.8
-/+
對 readonly/?修飾符進行增刪控制.
type MutableRequired<T> = { -readonly [P in keyof T]-?: T[P] }; // 移除readonly和?
type ReadonlyPartial<T> = { +readonly [P in keyof T]+?: T[P] }; // 新增readonly和?
v2.7
let x!: number[];
初始化變數直接斷言已賦值.
// 沒有這!, 會提示"在賦值前不能進行操作"
let x!: number[];
// 這個函式的賦值ts是檢測不到的, 所以上一行用了"!"
initialize();
x.push(4);
function initialize() {
x = [0, 1, 2, 3];
}
v2.6
中文 tsc
tsc --locale zh-cn
後面的命令列提示都會以中文形式顯示.
@ts-ignore
@ts-ignore 註釋隱藏下一行錯誤
if (false) {
// @ts-ignore:無法被執行的程式碼的錯誤
console.log("hello");
}
v2.5
try/catch 中 catch 可以省略引數
let input = "...";
try {
JSON.parse(input);
} catch {
// ^ 注意我們的 `catch` 語句並沒有宣告一個變數
console.log("傳入的 JSON 不合法\n\n" + input);
}
v2.4
動態的 import, 很多打包工具都已經支援 import 非同步載入模組.
注意: 需要把 tsconfig 中的目標模組設定為"esnext".
async function getZipFile(name: string, files: File[]): Promise<File> {
const zipUtil = await import("./utils/create-zip-file");
const zipContents = await zipUtil.getContentAsBlob(files);
return new File(zipContents, name);
}
v2.3
@ts-nocheck
通過"// @ts-nocheck"註釋來宣告檔案不檢測型別.
// @ts-nocheck
let a;
// 本應該提示a未賦值.
a.push(123);
v2.0
快捷外部模組宣告
當你使用一個新模組時,如果不想要花費時間書寫一個宣告時,現在你可以使用快捷宣告以便以快速開始。
// xxx.d.ts
declare module "hot-new-module";
所有匯入都是any型別
// x,y 都是any
import x, { y } from "hot-new-module";
x(y);
模組名稱中的萬用字元
這個 vue 初始化的專案中就有, 用來宣告.vue 檔案是個元件.
declare module "*.vue" {
import { DefineComponent } from "vue";
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>;
export default component;
}
還有:
declare module "*!text" {
const content: string;
export default content;
}
// Some do it the other way around.
declare module "json!*" {
const value: any;
export default value;
}
微信群
感謝大家的閱讀, 如有疑問可以加我微信, 我拉你進入微信群(由於騰訊對微信群的100人限制, 超過100人後必須由群成員拉入)
github
我個人的開源都是基於ts的, 歡迎大家訪問https://github.com/any86