TypeScript 版本特性及技巧

一憎妙、版本特性

4.1

  • 模板字符串中可以使用字符串類型
type World = "world";
type Greeting = `hello ${World}`;

// example1
type VerticalAlignment = "top" | "middle" | "bottom";
type HorizontalAlignment = "left" | "center" | "right";
declare function setAlignment(value: `${VerticalAlignment}-${HorizontalAlignment}`): void;

// example2
let person = makeWatchedObject({
  firstName: "Homer",
  age: 42, // give-or-take
  location: "Springfield",
});
person.on("firstNameChanged", () => {
  console.log(`firstName was changed!`);
});

type PropEventSource<T> = {
    on(eventName: `${string & keyof T}Changed`, callback: () => void): void;
};
declare function makeWatchedObject<T>(obj: T): T & PropEventSource<T>;
  • Key Remapping in Mapped Types
// 新語法 k in keyof T as NewKeyType
type MappedTypeWithNewKeys<T> = {
    [K in keyof T as NewKeyType]: T[K]
}
// example1
type Getters<T> = {
    [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};
interface Person {
    name: string;
    age: number;
    location: string;
}

type LazyPerson = Getters<Person>;
// equal to
type LazyPerson = {
  getName: () => string;
  getAge: () => number;
  getLocation: () => string;
}

// example2
type RemoveKindField<T> = {
    [K in keyof T as Exclude<K, "kind">]: T[K]
};
interface Circle {
    kind: "circle";
    radius: number;
}
type KindlessCircle = RemoveKindField<Circle>;
  • Recursive Conditional Types
type ElementType<T> = T extends ReadonlyArray<infer U> ? ElementType<U> : T;

function deepFlatten<T extends readonly unknown[]>(x: T): ElementType<T>[] {
  throw "not implemented";
}

// All of these return the type 'number[]':
deepFlatten([1, 2, 3]);
deepFlatten([[1], [2, 3]]);
deepFlatten([[1], [[2]], [[[3]]]]);
  • --noUncheckedIndexedAccess 默認關閉煮岁,不隨 --strictNullChecks 打開
interface Options {
  path: string;
  permissions: number;
  [propName: string]: string | number;
}

const opt:Options
opt.xxx  string|number|undefined

// 影響 for 循環(huán)能岩,不影響for-of,forEach
function screamLines(strs: string[]) {
  // This will have issues
  for (let i = 0; i < strs.length; i++) {
    console.log(strs[i].toUpperCase());
Object is possibly 'undefined'.
  }
}
  • paths without baseUrl
  • checkJs Implies allowJs
  • Breaking Changes
// resolve’s Parameters Are No Longer Optional in Promises
new Promise((resolve) => {
  resolve() // error
});

// fix 
new Promise<void>((resolve) => {
  resolve() // work
});

4.0

  • 可變元祖類型
    1、元祖類型可以使用擴展運算符了伺通,之前只能對數(shù)組類型用
    2答渔、擴展元算符可以用在任意位置了,之前只能用在最后一個參數(shù)
type IT = [string,boolean]
function tail<T extends any[]>(arr: readonly [any, ...IT]) {
  const [_ignored, ...rest] = arr;
  return rest;
}

type Strings = [string, string];
type Numbers = [number, number];
type StrStrNumNumBool = [...Strings, ...Numbers, boolean];

// 不限參數(shù)長度
type Strings = [string, string];
type Numbers = number[];
type Unbounded = [...Strings, ...Numbers, boolean];
//   ==> type Unbounded = [string, string, ...(number | boolean)[]]
  • 標記的元祖類型
    下面這種寫法
function foo(...args: [string, number]): void {
  // ...
}

是這種提示:



而這種寫法的提示語意上更為明確

type IRange = [start: string, end: number];
function foo(...args: IRange): void {
  // ...
}
foo()
  • 構造函數(shù)的類屬性推斷
    現(xiàn)在類屬性在構造函數(shù)中被初始化后會被推斷出類型治专,之前一直是 any
  • 短路分配運算符
    新加了&&=,||=遭顶,和??=
if(!a){
   a=b
}
===>
a||=b
  • catch 的 error 參數(shù)類型由 any 轉為了 unknown
  • 啟動時的部分編譯功能
    4.0 支持在 VSCode 啟動時優(yōu)先編譯當前工作區(qū)打開的文件张峰,而不是等所有的項目都編譯一遍才開始提供類型等信息

3.9

  • 修復 Promise.all 類型推斷錯誤問題
interface Lion {
  roar(): void;
}
interface Seal {
  singKissFromARose(): void;
}

async function visitZoo(
  lionExhibit: Promise<Lion>,
  sealExhibit: Promise<Seal | undefined>
) {
  let [lion, seal] = await Promise.all([lionExhibit, sealExhibit]);
  lion.roar(); // uh oh
  // lion is possibly 'undefined'.
}
  • 提供 ts-expect-error 注釋
    與 ts-ignore 的區(qū)別在于,如果下一行沒有 ts 錯誤棒旗,ts-ignore 啥也不干喘批,但 ts-expect-error 本身會報錯
  • 條件表達式中的未調(diào)用函數(shù)檢查
    條件表達式中只寫函數(shù)名而沒加括號會報錯
  • 解析可選鏈接和非null斷言中的差異
// before, this transform is a bug
foo?.bar!.baz;  =>  (foo?.bar).baz;  
// now
foo?.bar!.baz;  =>  foo?.bar.baz;  
  • 交叉類型和可選屬性的更嚴格檢查
interface A {
  a: number; // notice this is 'number'
}
interface B {
  b: string;
}
interface C {
  a?: boolean; // notice this is 'boolean'
  b: string;
}
declare let x: A & B;
declare let y: C;
// before is ok , now is error
y = x;
  • getter/setter 不再可枚舉
  • 類型參數(shù)any不再擴展any
function foo<T extends any>(arg: T) {
  arg.spfjgerijghoied; // before is ok , now is  error!
}

3.x

  • 支持 reference 配置
  • ReadonlyArray 類型
    數(shù)組內(nèi)元素不能增加、修改和刪除
function foo(arr: ReadonlyArray<string>) {
  arr.slice(); // okay
  arr.push("hello!"); // error!
}

也可寫成 readonly string[]铣揉,同時也支持 tuples 類型:readonly [string,number]

  • const 斷言
    as const 可以將一些類型變?yōu)橹蛔x:
// Type '"hello"'
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;
  • 3.5 引入 Omit 類型
type Person = {
  name: string;
  age: number;
  location: string;
};

type QuantumPerson = Omit<Person, "location">;

// equivalent to
type QuantumPerson = {
  name: string;
  age: number;
};
  • 3.7 引入可選鏈
// Before
if (foo && foo.bar && foo.bar.baz) {
  // ...
}
// After
if (foo?.bar?.baz) {
  // ...
}
  • 3.7 引入空位合并
let x = foo ?? bar();
// equal to
let x = foo !== null && foo !== undefined ? foo : bar();

注意和 || 運算不同饶深,|| 包含了更多 falsy 類的值,如 false,'',0 等

  • 3.8 引入類型導入和導出
import type { SomeThing } from "./some-module.js";
export type { SomeThing };
  • 3.8 真-私有字段
    之前都是 private逛拱,但這只在編譯階段有約束敌厘,編譯成JS后仍可訪問。3.8 的私有字段語法為: #valuableName橘券,編譯成JS后無法訪問额湘,實現(xiàn)上用到了 WeakMap
class C {
  #foo = 10;
  cHelper() {
    return this.#foo;
  }
}

二、小技巧

  • 枚舉 key-value 互取
enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green; // 2
let colorName: string = Color[Color.Green];  // 'Green'
  • Record
    如果定義一個對象類型旁舰,直接 Record<string,unknown>锋华,而不是用 interface,interface寫起來麻煩
  • Partial
    將一個已定義好的接口必選屬性改為可選屬性箭窜,類似的還有 Readonly毯焕,Required,Omit<T,K>磺樱,Pick<T,K>
  • 回調(diào)函數(shù)類型
type Cb = ()=>void
  • 當為一個默認值定義類型時纳猫,可以用 type,而不是 interface竹捉。能少寫代碼
// bad
interface IState{
      loading: boolean;
      data:any[];
}
const defalutState:IState={
    loading:true,
    data:[]
}
// good
const defalutState = {
    loading:true,
    data:[]
}
type IState = typeof defalutState
  • 導入其它非 js 類的文件芜辕,需要自己聲明模塊
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;
}

import fileContent from "./xyz.txt!text";
import data from "json!http://example.com/data.json";
  • 類型沖突時,可以用 reference 指定使用哪個類型文件块差,比如侵续,@types/node 和 @types/webpack-env 這兩個文件都聲明了 NodeJS,但有時 ts 找不到 webpack-env 里對 NodeJS 的 Module 接口擴展憨闰,導致 module.hot 會報錯
  • 巧用查找類型
interface Person {
  addr: {
    city: string,
    street: string,
    num: number,
  }
}

當需要使用 addr 的類型時, 可以直接 Person["addr"]

  • 巧用ClassOf
abstract class Animal extends React.PureComponent {
  /* Common methods here. */
}
class Cat extends Animal {}
class Dog extends Animal {}

// `AnimalComponent` must be a class of Animal.
const renderAnimal = (AnimalComponent: Animal) => {
  return <AnimalComponent/>; // WRONG!
}

上面的代碼是錯的状蜗,因為 Animal 是實例類型,不是類本身鹉动。應該

interface ClassOf<T> {
  new (...args: any[]): T;
}
const renderAnimal = (AnimalComponent: ClassOf<Animal>) => {
  return <AnimalComponent/>; // Good!
}

renderAnimal(Cat); // Good!
renderAnimal(Dog); // Good!

  • 為第三方庫寫類型文件

首先我們需要明確包使用的導出規(guī)范轧坎,global/umd/commonjs/module 等
對于 global 導出的包我們使用:

declare namesapce MyLib {
  class A {}
  
  // 我們可以直接在代碼中使用
  // const a = new MyLib.A()
}

對于 umd/commonjs 導出的包我們使用:

declare module 'my-lib' {
  namespace MyLib {
    class A {}
    
    class B {}

    // 使用時
    // 我們可以使用
    // import * as MyLib from 'my-lib'
    // const a = new MyLib.A();

    // 如果開啟了 ES Module 融合模式 (esModuleInterop=true)
    // 我們可以使用
    // import { A } from 'my-lib'
    // const a = new A()
  }
  export = MyLib
}

對于 ES Module 導出的包我們使用:

declare module 'my-lib' {
  class MyLib {}
  
  export default MyLib
  
  // or other exorts
  export class A {}
  
  // 我們可以使用
  // import MyLib, {A} from 'my-lib'
  // const lib = new MyLib()
  // const a = new A()
}
  • 常量枚舉和普通枚舉區(qū)別
    常量枚舉在編譯階段會被刪除
export const enum T{
    a=1
}
T.a  // 會被編譯為 1 /*a*/

enum T{
    a=1
}
T.a 會被編譯為
var ITest;
(function (ITest) {
    ITest[ITest["a"] = 1] = "a";
})(ITest || (ITest = {}));
ITest.a
  • 一份較全的 tsconfig.json 配置(編譯上下文配置)
{
  "compilerOptions": {

    /* 基本選項 */
    "target": "es5",                       // 指定 ECMAScript 目標版本: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
    "module": "commonjs",                  // 指定使用模塊: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
    "lib": [],                             // 指定要包含在編譯中的庫文件
    "allowJs": true,                       // 允許編譯 javascript 文件
    "checkJs": true,                       // 報告 javascript 文件中的錯誤
    "jsx": "preserve",                     // 指定 jsx 代碼的生成: 'preserve', 'react-native', or 'react'
    "declaration": true,                   // 生成相應的 '.d.ts' 文件
    "sourceMap": true,                     // 生成相應的 '.map' 文件
    "outFile": "./",                       // 將輸出文件合并為一個文件
    "outDir": "./",                        // 指定輸出目錄
    "rootDir": "./",                       // 用來控制輸出目錄結構 --outDir.
    "removeComments": true,                // 刪除編譯后的所有的注釋
    "noEmit": true,                        // 不生成輸出文件
    "importHelpers": true,                 // 從 tslib 導入輔助工具函數(shù)
    "isolatedModules": true,               // 將每個文件做為單獨的模塊 (與 'ts.transpileModule' 類似).

    /* 嚴格的類型檢查選項 */
    "strict": true,                        // 啟用所有嚴格類型檢查選項
    "noImplicitAny": true,                 // 在表達式和聲明上有隱含的 any類型時報錯
    "strictNullChecks": true,              // 啟用嚴格的 null 檢查
    "noImplicitThis": true,                // 當 this 表達式值為 any 類型的時候,生成一個錯誤
    "alwaysStrict": true,                  // 以嚴格模式檢查每個模塊泽示,并在每個文件里加入 'use strict'

    /* 額外的檢查 */
    "noUnusedLocals": true,                // 有未使用的變量時缸血,拋出錯誤
    "noUnusedParameters": true,            // 有未使用的參數(shù)時蜜氨,拋出錯誤
    "noImplicitReturns": true,             // 并不是所有函數(shù)里的代碼都有返回值時,拋出錯誤
    "noFallthroughCasesInSwitch": true,    // 報告 switch 語句的 fallthrough 錯誤属百。(即记劝,不允許 switch 的 case 語句貫穿)

    /* 模塊解析選項 */
    "moduleResolution": "node",            // 選擇模塊解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
    "baseUrl": "./",                       // 用于解析非相對模塊名稱的基目錄
    "paths": {},                           // 模塊名到基于 baseUrl 的路徑映射的列表
    "rootDirs": [],                        // 根文件夾列表,其組合內(nèi)容表示項目運行時的結構內(nèi)容
    "typeRoots": [],                       // 包含類型聲明的文件列表
    "types": [],                           // 需要包含的類型聲明文件名列表
    "allowSyntheticDefaultImports": true,  // 允許從沒有設置默認導出的模塊中默認導入族扰。

    /* Source Map Options */
    "sourceRoot": "./",                    // 指定調(diào)試器應該找到 TypeScript 文件而不是源文件的位置
    "mapRoot": "./",                       // 指定調(diào)試器應該找到映射文件而不是生成文件的位置
    "inlineSourceMap": true,               // 生成單個 soucemaps 文件厌丑,而不是將 sourcemaps 生成不同的文件
    "inlineSources": true,                 // 將代碼與 sourcemaps 生成到一個文件中,要求同時設置了 --inlineSourceMap 或 --sourceMap 屬性

    /* 其他選項 */
    "experimentalDecorators": true,        // 啟用裝飾器
    "emitDecoratorMetadata": true          // 為裝飾器提供元數(shù)據(jù)的支持

    /* 編譯文件選擇 */
    "files":[],
    "include":[],
    "exclude":[],
  }
}

參考

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子轩拨,更是在濱河造成了極大的恐慌,老刑警劉巖耕驰,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異录豺,居然都是意外死亡朦肘,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門双饥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來媒抠,“玉大人,你說我怎么就攤上這事咏花∨可” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵昏翰,是天一觀的道長苍匆。 經(jīng)常有香客問我,道長棚菊,這世上最難降的妖魔是什么浸踩? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮统求,結果婚禮上检碗,老公的妹妹穿的比我還像新娘。我一直安慰自己球订,他們只是感情好后裸,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布瑰钮。 她就那樣靜靜地躺著冒滩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪浪谴。 梳的紋絲不亂的頭發(fā)上开睡,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天因苹,我揣著相機與錄音,去河邊找鬼篇恒。 笑死扶檐,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的胁艰。 我是一名探鬼主播款筑,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼腾么!你這毒婦竟也來了奈梳?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤解虱,失蹤者是張志新(化名)和其女友劉穎攘须,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體殴泰,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡于宙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了悍汛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捞魁。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖员凝,靈堂內(nèi)的尸體忽然破棺而出署驻,到底是詐尸還是另有隱情,我是刑警寧澤健霹,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布旺上,位于F島的核電站,受9級特大地震影響糖埋,放射性物質(zhì)發(fā)生泄漏宣吱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一瞳别、第九天 我趴在偏房一處隱蔽的房頂上張望征候。 院中可真熱鬧,春花似錦祟敛、人聲如沸疤坝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽跑揉。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間历谍,已是汗流浹背现拒。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留望侈,地道東北人印蔬。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像脱衙,于是被迫代替她去往敵國和親侥猬。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344