【TypeScript 演化史 -- 8】字面量類型擴展 和 無類型導入

做者:Marius Schulz前端

譯者:前端小智node

來源:mariusschulz.com/git

點贊再看,養成習慣
github


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

系列文章以下:typescript

【TypeScript 演化史 -- 1】non-nullable 的類型npm

【TypeScript 演化史 -- 2】基於控制流的類型分析 和 只讀屬性數組

【TypeScript 演化史 -- 3】標記聯合類型 與 never 類型bash

【TypeScript 演化史 -- 4】更多的字面量類型 與 內置類型聲明app

【TypeScript 演化史 -- 5】將 async/await 編譯到 ES3/ES5 (外部幫助庫)

【TypeScript 演化史 -- 6】對象擴展運算符和 rest 運算符及 keyof 和查找類型

【TypeScript 演化史 -- 7】映射類型和更好的字面量類型推斷

上一篇更好的類型推斷的文章中,解釋了 TypeScript 如何用 const 變量和 readonly 屬性的字面量始化來推斷字面量類型。這篇文章繼續討論這個,擴展和非擴展字面量類型之間的區別。

擴展字面量類型

當使用 const 關鍵字聲明局部變量並使用字面量值初始化它時,TypeScript 將推斷該變量的字面量類型

const stringLiteral = "https"; // Type "https"
const numericLiteral = 42;     // Type 42
const booleanLiteral = true;   // Type true
複製代碼

因爲 const 關鍵字,每一個變量的值都不能更改,因此字面量類型很是有意義。它保存了關於被賦值的確切信息。

若是若是 let 聲明上述的變量,那麼每一個字面量類型都被擴展爲相應的擴展類型:

let widenedStringLiteral = stringLiteral;   // Type string
let widenedNumericLiteral = numericLiteral; // Type number
let widenedBooleanLiteral = booleanLiteral; // Type boolean
複製代碼

const 變量相反,使用 let 聲明的變量是能夠修改的。若是 TypeScript 爲 let 變量推斷一個字面量類型,那麼嘗試爲指定的值之外的任何值賦值都會在編譯時產生錯誤。

所以,對於上述每一個let變量,都會推斷出擴展的類型,枚舉字面量也是如此:

enum FlexDirection {
  Row,
  Column
}

const enumLiteral = FlexDirection.Row; //  FlexDirection.Row 類型
let widenedEnumLiteral = enumLiteral; // FlexDirection 類型
複製代碼

總結一下,下面是擴大字面量類型的規則:

  • 字符串字面量類型被擴展爲 string 類型

  • 數字字面量類型被擴展爲 number 類型

  • 布爾字面量類型被擴展爲 boolean 類型

  • 枚舉字面量類型被擴展爲包含枚舉的類型

到目前爲止,我們一直在研究字面量類型的擴展,在必要時自動擴展。如今來看看非擴展字面量類型,如名所示,它們不會自動地擴展。

非擴展字面量類型

能夠經過顯式地將變量標註爲字面量類型來建立非擴展字面量類型的變量

const stringLiteral: "https" = "https"; // 類型 "https" (非擴展)
const numericLiteral: 42 = 42; // 類型 42 (非擴展)
複製代碼

將非擴展字面量類型的變量的值賦給另外一個變量,該變量將不會擴展。

let widenedStringLiteral = stringLiteral; // 類型 "https" (非擴展)
let widenedNumericLiteral = numericLiteral; // 類型 42 (非擴展)
複製代碼

非擴展字面量類型的好處

爲了理解非擴展字面量類型的是有用的,我們再來看看擴展字面量類型。在下面的例子中,一個數組是由兩個可擴展字符串字面量類型的變量建立的:

const http = "http"; // Type "http" (可擴展)
const https = "https"; // Type "https" (可擴展)

const protocols = [http, https]; // Type string[]

const first = protocols[0]; // Type string
const second = protocols[1]; // Type string
複製代碼

TypeScript 推斷數組 protocols 的類型爲 string[]。所以,像 firstsecond 這樣的數組元素類型被擴展爲 string。字面量類型 "http""https" 的概念在擴展過程當中丟失了。

若是我們顯式地將這兩個常量指定爲非擴展類型,則 protocols 數組將被推斷爲類型 ("http" | "https")[],它表示一個數組,其中僅包含字符串 "http""https":

const http: "http" = "http"; // Type "http" (非擴展)
const https: "https" = "https"; // Type "https" (非擴展

const protocols = [http, https]; // Type ("http" | "https")[]

const first = protocols[0]; // Type "http" | "https"
const second = protocols[1]; // Type "http" | "https"
複製代碼

如今 firstsecond 的類型被推斷爲 "http" | "https"。這是由於數組類型沒有對索引 0 處的值 "http" 和索引 1 處的值 "https" 進行編碼。它只是聲明該數組只包含兩個字面量類型的值,無論在哪一個位置。

若是出於某種緣由,但願保留數組中字符串字面量類型的位置信息,能夠用以下的方式顯示指定:

const http = "http"; // Type "http" (可擴展)
const https = "https"; // Type "https" (可擴展)

const protocols: ["http", "https"] = [http, https]; // Type ["http", "https"]

const first = protocols[0]; // Type "http" (非擴展)
const second = protocols[1]; // Type "https" (非擴展)
複製代碼

如今,firstsecond 被推斷爲各自的非擴展字符串字面量類型。

無類型導入

從TypeScript 2.1 開始處理無類型化導入更加容易。之前,編譯器過於嚴格,當導入一個沒有附帶類型定義的模塊時,會出現一個錯誤:

從 TypeScript 2.1 開始,若是模塊沒有類型聲明,編譯器將再也不報錯。

如今,導入的 range 函數的類型爲 any。這樣作的好處是,將現有的 JS 項目遷移到 TypeScrip t能夠減小編譯時錯誤。缺點是,不會獲得任何自動完成建議或細粒度類型檢查,由於編譯器對模塊或其導出一無所知。

若是事後提供類型聲明,例如經過 npm 的類型聲明包,它們將優先於默認的任何類型。(不然,將沒法爲導入的模塊提供類型)

對於沒有聲明文件的模塊的導入,在使用了--noImplicitAny編譯參數後仍將被標記爲錯誤。

// Succeeds if `node_modules/asdf/index.js` exists
import { x } from "asdf";
複製代碼

支持--target ES2016,--target ES2017--target ESNext

TypeScript 2.1支持三個新的編譯版本值--target ES2016,--target ES2017--target ESNext

使用target--target ES2016將指示編譯器不要編譯ES2016特有的特性,好比**操做符。

一樣,--target ES2017將指示編譯器不要編譯ES2017特有的特性像async/await

--target ESNext則對應最新的ES提議特性支持.

改進any類型推斷

之前,若是 TypeScript 沒法肯定變量的類型,它將選擇any類型。

let x;      // 隱式 'any'
let y = []; // 隱式 'any[]'

let z: any; // 顯式 'any'.
複製代碼

使用TypeScript 2.1,TypeScript 不是僅僅選擇any類型,而是基於你後面的賦值來推斷類型。

僅當設置了--noImplicitAny編譯參數時,纔會啓用此選項。

示例

let x;

// 你仍然能夠給'x'賦值任何你須要的任何值。
x = () => 42;

// 在剛賦值後,TypeScript 2.1 知道'x'的類型是'() => number'let y = x();

// 感謝,如今它會告訴你,你不能添加一個數字到一個函數!
console.log(x + y);
//          ~~~~~
// 錯誤!運算符 '+' 不能應用於類型`() => number`和'number'。

// TypeScript仍然容許你給'x'賦值你須要的任何值。
x = "Hello world!";

// 而且如今它也知道'x''string'類型的!
x.toLowerCase();
複製代碼

如今對空數組也進行一樣的跟蹤。

沒有類型註解而且初始值爲[]的變量被認爲是一個隱式的any[]變量。變量會根據下面這些操做x.push(value)、x.unshift(value)x[n] = value向其中添加的元素來不斷改變自身的類型。

function f1() {
    let x = [];
    x.push(5);
    x[1] = "hello";
    x.unshift(true);
    return x;  // (string | number | boolean)[]
}

function f2() {
    let x = null;
    if (cond()) {
        x = [];
        while (cond()) {
            x.push("hello");
        }
    }
    return x;  // string[] | null
}
複製代碼

隱式 any 錯誤

這樣作的一個很大的好處是,當使用--noImplicitAny運行時,你將看到較少的隱式any錯誤。隱式any錯誤只會在編譯器沒法知道一個沒有類型註解的變量的類型時纔會報告。

示例

function f3() {
    let x = [];  // 錯誤:當變量'x'類型沒法肯定時,它隱式具備'any[]'類型。
    x.push(5);
    function g() {
        x;    // 錯誤:變量'x'隱式具備'any【】'類型。
    }
}
複製代碼

這邊順便給你們推薦一個好用的BUG監控工具 Fundebug

原文:

mariusschulz.com/blog/litera… mariusschulz.com/blog/untype…


交流

乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。

github.com/qq449245884…

我是小智,公衆號「大遷世界」做者,對前端技術保持學習愛好者。我會常常分享本身所學所看的乾貨,在進階的路上,共勉!

關注公衆號,後臺回覆福利,便可看到福利,你懂的。

clipboard.png
相關文章
相關標籤/搜索