【TS 演化史 -- 17】各文件的JSX工廠 、有條件類型和映射類型修飾符

做者:Marius Schulz
譯者:前端小智
來源: https://mariusschulz.com/
點贊再看,養成習慣

本文 GitHub https://github.com/qq44924588... 上已經收錄,更多往期高贊文章的分類,也整理了不少個人文檔,和教程資料。歡迎Star和完善,你們面試能夠參照考點複習,但願咱們一塊兒有點東西。前端

各文件的JSX工廠

TypeScript 2.8容許我們在每一個文件的基礎上指定JSX工廠名。在早期版本,只能經過--jsxFactory編譯器選項指定JSX工廠名。此設置適用於整個項目中的每一個JSX文件。如今,我們還能夠經過在文件的開頭添加一個特殊的@jsx註釋來覆蓋項目範圍的--jsxFactory設置。node

假設我們要使用Preact渲染字符串 "Hello World!" 並放入<div id="app">容器中。 Preact 使用h函數來建立 JSX 元素。 我們能夠在.tsx文件的開頭添加特殊的/ ** @jsx h */註釋(也稱爲「pragma」):react

有了/** @jsx h */編譯指示後,編譯器將爲上述文件生成如下 JS 代碼:git

/** @jsx h */
import { h, render } from "preact";
render(
  h("h1", null, "Hello World!"),
  document.getElementById("app")
);

下面是用來編譯代碼的tsconfig.json配置文件:github

{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "moduleResolution": "node",
    "jsx": "react",
    "strict": true
  }
}

請注意,若是您使用/** ... */塊註釋語法,則編譯器僅識別編譯指示。 若是使用// ...單行註釋語法,則不會更改JSX出廠設置。面試

什麼是JSX工廠

JSX不是 ECMAScript 標準的一部分;也就是說,它自己不是有效的 JS。所以,包含JSX的腳本或模塊不能直接在瀏覽器中運行。與帶有類型註釋的文件同樣,JSX 文件首先須要編譯成純 JS 文件。--jsxFactory選項告訴 TypeScript 編譯器應該如何編譯JSX元素。typescript

注意 <h1> Hello World!</h1>如何轉換爲 h("h1", null, "Hello World!")Preact 使用函數h建立虛擬 DOM 元素,這就是爲何我們將h指定爲JSX工廠名稱的緣由。 咱們還須要從preact包中導入h,以便它在模塊中可用。json

指定每一個文件和每一個項目的JSX工廠

那麼,何時須要在每一個文件的基礎上指定JSX工廠呢?若是我們在項目中只將JSX與單個 JS庫一塊兒使用,則不須要對每一個文件進行配置。在這種狀況下,更容易在tsconfig中更改--jsxFactory選項。這樣它就能夠應用於項目中的全部JSX文件:數組

{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "moduleResolution": "node",
    "jsx": "react",
    "jsxFactory": "h",
    "strict": true
  }
}

默認狀況下,使用--jsx react選項時,--jsxFactory選項設置爲React.createElement。 所以,若是我們使用的是 React,則徹底不須要指定--jsxFactory選項,也沒必要添加/ ** @jsx ... * /編譯指示。瀏覽器

若是在同一項目中將多個JS庫與JSX一塊兒使用,則JSX工廠的按文件配置頗有用。 例如,我們可能想將Vue組件添加到主要用 eact 編寫的Web應用程序中。 / ** @jsx ... * / 編譯指示容許我們爲這些文件指定不一樣的 JSX 工廠,而沒必要具備多個tsconfig.json文件。

有條件類型

TypeScript 2.8 引入了有條件類型,這是類型系統的強大而使人興奮的補充。 有條件類型使我們能夠表達非均勻類型映射,即,根據條件而不一樣的類型轉換。

有條件的類型會以一個條件表達式進行類型關係檢測,從而在兩種類型中選擇其一:

T extends U ? X : Y

上面的類型意思是,若T可以賦值給U,那麼類型是X,不然爲Y

下面是一個在 TypeScript 的lib.es5.d.ts類型定義文件中預約義的有條件類型的例子

/**
 * Exclude null and undefined from T
 */
type NonNullable<T> = T extends null | undefined ? never : T;

若是類型T可賦值給類型null或類型undefined,則NonNullable<T>類型爲never類型;不然它將保留類型 Tnever類型是 TypeScript 的底層類型,表示從未出現的值的類型。

分佈式有條件類型

那麼,爲何e 條件類型和never類型的組合是有用的呢?它有效地容許我們從聯合類型中刪除組成類型。若是有條件類型裏待檢查的類型是naked type parameter,那麼它也被稱爲「分佈式有條件類型」。 分佈式有條件類型在實例化時會自動分發成聯合類型。 例如,實例化T extends U ? X : YT的類型爲A | B | C,會被解析爲(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)

這個描述至關抽象,我們來看一個具體的例子。我們定義一個EmailAddress類型別名,它表示四種不一樣類型的聯合,包括nullundefined類型

type EmailAddress = string | string[] | null | undefined;

如今,我們將NonNullable<T>類型應用於EmailAddress,並逐步解析結果類型:

type NonNullableEmailAddress = NonNullable<EmailAddress>;

我們首先用別名的聯合類型替換EmailAddress

type NonNullableEmailAddress = NonNullable<
  | string
  | string[]
  | null
  | undefined
>;

這就是有條件類型的分配特性發揮做用的地方。NonNullable<T>類型應用於聯合類型,這至關於將有條件類型應用於聯合類型中的全部類型:

type NonNullableEmailAddress =
  | NonNullable<string>
  | NonNullable<string[]>
  | NonNullable<null>
  | NonNullable<undefined>;

如今,我們能夠在任何地方經過其定​​義替換NonNullable<T>

type NonNullableEmailAddress =
  | (string extends null | undefined ? never : string)
  | (string[] extends null | undefined ? never : string[])
  | (null extends null | undefined ? never : null)
  | (undefined extends null | undefined ? never : undefined);

接下來,我們必須解析這四種有條件類型。stringstring[]都不能賦值給 null | undefined,這就是前兩種類型選擇stringstring[]的緣由。nullundefined均可以賦值給null | undefined,這就是爲何後兩種類型都選擇never:

type NonNullableEmailAddress =
  | string
  | string[]
  | never
  | never;

由於never是每一個類型的子類型,因此能夠從聯合類型中省略它:

type NonNullableEmailAddress = string | string[];

這就是咱們指望的類型。

使用有條件類型的映射類型

如今讓我們看一個更復雜的例子,它將映射類型與條件類型組合在一塊兒。這裏,咱們定義了一個類型,它從一個類型中提取全部不可爲空的屬性鍵

type NonNullablePropertyKeys<T> = {
  [P in keyof T]: null extends T[P] ? never : P
}[keyof T];

這種類型乍一看彷佛至關神祕。再一次,將經過查看一個具體的示例並逐步解析獲得的類型來嘗試揭開它的神祕面紗。

假設我們有一個User類型,想要使用NonNullablePropertyKeys<T>類型來找出哪些屬性是不可空的:

type User = {
  name: string;
  email: string | null;
};

type NonNullableUserPropertyKeys = NonNullablePropertyKeys<User>;

下面是我們如何解析NonNullablePropertyKeys<User>。首先,我們將User類型做爲T類型參數的類型參數提供:

type NonNullableUserPropertyKeys = {
  [P in keyof User]: null extends User[P] ? never : P
}[keyof User];

其次,我們在映射類型中解析keyof UserUser類型有兩個屬性,nameemail,所以我們最終獲得一個帶有「name」「email」字符串字面量類型的聯合類型:

type NonNullableUserPropertyKeys = {
  [P in "name" | "email"]: null extends User[P] ? never : P
}[keyof User];

接下來,咱們將在映射中展開P in...,並將P類型替換爲「name」「email」

type NonNullableUserPropertyKeys = {
  name: null extends User["name"] ? never : "name";
  email: null extends User["email"] ? never : "email";
}[keyof User];

而後,經過查找User中的nameemail屬性的類型,我們能夠繼續並解析索引訪問類型User["name"]User["email"]

type NonNullableUserPropertyKeys = {
  name: null extends string ? never : "name";
  email: null extends string | null ? never : "email";
}[keyof User];

如今是應用條件類型的時候了。null不擴string,但它確實擴展了string | null,所以我們分別以「name」never類型結束:

type NonNullableUserPropertyKeys = {
  name: "name";
  email: never;
}[keyof User];

如今我們已經完成了映射類型和條件類型,再一次,我們將解析keyof Use

type NonNullableUserPropertyKeys = {
  name: "name";
  email: never;
}["name" | "email"];

我們如今有一個索引訪問類型,它查找nameemail屬性的類型。TypeScript 經過逐個查找每一個類型並建立聯合類型來解決這個問題:

type NonNullableUserPropertyKeys =
  | { name: "name"; email: never }["name"]
  | { name: "name"; email: never }["email"];

如今,我們能夠在兩個對象類型中查找nameemail屬性。name屬性的類型是「name」,而email屬性的類型是「never」

type NonNullableUserPropertyKeys =
  | "name"
  | never;

和前面同樣,我們能夠經過清除never類型來簡化生成的聯合類型:

type NonNullableUserPropertyKeys = "name";

User類型中惟一不可爲空的屬性鍵是「name」

我們進一步研究這個示例,並定義一個類型來提取給定類型的全部不可空屬性。咱們能夠將Pick <T,K>類型用於lib.es5.d.ts中預約義的類型:

/**
 * From T, pick a set of properties
 * whose keys are in the union K
 */
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

我們能夠結合NonNullablePropertyKeys<T>類型和Pick<T, K>來定義NonNullableProperties<T>,這是我們要查找的類型:

type NonNullableProperties<T> = Pick<T, NonNullablePropertyKeys<T>>;

type NonNullableUserProperties = NonNullableProperties<User>;
// { name: string }

實際上,這是我們指望的類型:在User類型中,只有name屬性不可空。

有條件類型中的類型推斷

有條件類型支持的另外一個有用特性是使用新的infer關鍵字推斷類型變量。在有條件類型的extends子句中,可使用新的infer關鍵字來推斷類型變量,從而有效地執行類型上的模式匹配

type First<T> =
  T extends [infer U, ...unknown[]]
    ? U
    : never;

type SomeTupleType = [string, number, boolean];
type FirstElementType = First<SomeTupleType>; // string

注意,推斷的類型變量(在本例中爲U)只能在條件類型的true分支中使用。

TypeScript 一個長期存在的特性要求是可以提取給定函數的返回類型。下面是ReturnType<T>類型的簡化版本,該類型是在lib.es5.d.ts中預約義的。它使用infer關鍵字來推斷函數類型的返回類型:

type ReturnType<T> =
  T extends (...args: any[]) => infer R
    ? R
    : any;

type A = ReturnType<() => string>;         // string
type B = ReturnType<() => () => any[]>;    // () => any[]
type C = ReturnType<typeof Math.random>;   // number
type D = ReturnType<typeof Array.isArray>; // boolean

注意,我們必須使用typeof來得到Math.random()Array.isArray()方法的返回類型。我們須要傳遞類型做爲類型參數T的參數,而不是值;這就是爲何ReturnType<Math.random>ReturnType<Array.isArray>是不正確的。

預約義的有條件類型

TypeScript 2.8 在lib.d.ts裏增長了一些預約義的有條件類型:

  • Exclude<T, U> -- 從T中剔除能夠賦值給U的類型。
  • Extract<T, U> -- 提取T中能夠賦值給U的類型。
  • NonNullable<T> -- 從T中剔除null和undefined。
  • ReturnType<T> -- 獲取函數返回值類型。
  • InstanceType<T> -- 獲取構造函數類型的實例類型。

Exclude<T, U>

Exclude<T, U>T中剔除能夠賦值給U的類型。

定義:

/**
 * Exclude from T those types that are assignable to U
 */
type Exclude<T, U> = T extends U ? never : T;

事例:

type A = Exclude<string | string[], any[]>;      // string
type B = Exclude<(() => void) | null, Function>; // null
type C = Exclude<200 | 400, 200 | 201>;          // 400
type D = Exclude<number, boolean>;               // number

Extract<T, U>

經過Extract <T,U>類型,提取T中能夠賦值給U的類型。

定義:

/**
 * Extract from T those types that are assignable to U
 */
type Extract<T, U> = T extends U ? T : never;

事例:

type A = Extract<string | string[], any[]>;      // string[]
type B = Extract<(() => void) | null, Function>; // () => void
type C = Extract<200 | 400, 200 | 201>;          // 200
type D = Extract<number, boolean>;               // never

NonNullable<T>

我們上面使用了NonNullable<T>類型,該類型從T中過濾出nullundefined類型。

定義:

/**
 * Exclude null and undefined from T
 */
type NonNullable<T> = T extends null | undefined ? never : T;

事例:

type A = NonNullable<boolean>;            // boolean
type B = NonNullable<number | null>;      // number
type C = NonNullable<string | undefined>; // string
type D = NonNullable<null | undefined>;   // never

注意,空類型D是如何由never表示的。

ReturnType<T>

ReturnType<T> 獲取函數返回值類型。

定義:

/**
 * Obtain the return type of a function type
 */
type ReturnType<T extends (...args: any[]) => any> =
  T extends (...args: any[]) => infer R
    ? R
    : any;

事例:

type A = ReturnType<() => string>;         // string
type B = ReturnType<() => () => any[]>;    // () => any[]
type C = ReturnType<typeof Math.random>;   // number
type D = ReturnType<typeof Array.isArray>; // boolean

Parameters<T>

Parameters<T> 獲取構造函數類型的實例類型。

定義:

/**
 * Obtain the parameters of a function type in a tuple
 */
type Parameters<T extends (...args: any[]) => any> =
  T extends (...args: infer P) => any
    ? P
    : never;

注意,Parameters<T>類型在結構上與ReturnType<T>類型幾乎相同。 主要區別在於infer關鍵字的位置。

事例:

type A = Parameters<() => void>;           // []
type B = Parameters<typeof Array.isArray>; // [any]
type C = Parameters<typeof parseInt>;      // [string, (number | undefined)?]
type D = Parameters<typeof Math.max>;      // number[]

Array.isArray() 方法剛好須要一個任意類型的參數。 這就是爲何將B類型解析爲[any],即具備一個元素的元組的緣由。 另外一方面,Math.max() 方法指望任意多個數值參數(而不是單個數組參數);所以,類型D被解析爲number[](而不是[number []])。

InstanceType<T>

InstanceType<T>類型提取構造函數類型的返回類型,它至關於構造函數的ReturnType<T>

定義

/**
 * Obtain the return type of a constructor function type
 */
type InstanceType<T extends new (...args: any[]) => any> =
  T extends new (...args: any[]) => infer R
    ? R
    : any;

再次注意InstanceType <T>類型在結構上與ReturnType <T>ConstructorParameters <T>類型很是類似。

事例:

type A = InstanceType<ErrorConstructor>;    // Error
type B = InstanceType<FunctionConstructor>; // Function
type C = InstanceType<RegExpConstructor>;   // RegExp

代碼部署後可能存在的BUG無法實時知道,過後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug

原文:
https://mariusschulz.com/blog...
https://mariusschulz.com/blog...
https://mariusschulz.com/blog...


交流

文章每週持續更新,能夠微信搜索「 大遷世界 」第一時間閱讀和催更(比博客早一到兩篇喲),本文 GitHub https://github.com/qq449245884/xiaozhi 已經收錄,整理了不少個人文檔,歡迎Star和完善,你們面試能夠參照考點複習,另外關注公衆號,後臺回覆福利,便可看到福利,你懂的。

相關文章
相關標籤/搜索