1. 程式人生 > 程式設計 >詳解TS數字分隔符和更嚴格的類屬性檢查

詳解TS數字分隔符和更嚴格的類屬性檢查

概述

TypeScript 2.4 為識別符號實現了拼寫糾正機制。即使咱們稍微拼錯了一個變數、屬性或函式名,TypeScript 在很多情況下都可以提示正確的拼寫。

TypeScript 2.7 支援 ECMAScript 的數字分隔符提案。 這個特性允許使用者在數字之間使用下劃線(_)來對數字分組(就像使用逗號和點來對數字分組那樣)。

const worldPopulationIn2017 = 7_600_000_000;
const leastSignificantByteMask = 0b1111_1111;
const papayawhipColorHexCode = 0xFF_EF_D5;

數字分隔符不會改變數字字面量的值,但分組使人們更容易一眼就能讀懂數字。

這些分隔符對於二進位制和十六進位制同樣有用。

let bits = 0b0010_1010;
let routine = 0xC0FFEE_F00D_BED;
let martin = 0xF0_1E_

注意,可能有些反常識,js裡的數字表示信用卡和電話號並不適當,這種情況下使用字串更好。

當咱們將target設定為es2015編譯的上述程式碼時,TypeScript 將生成以下js程式碼:

const worldPopulationIn2017 = 7600000000;
const leastSignificantByteMask = 255;
const papayawhipColorHexCode = 16773077;

in操作符細化和精確的 instanceof

TypeScript 2.7帶來了兩處型別細化方面的改動 - 通過執行“型別保護”確定更詳細型別的能力。

首先,instanceof操作符現在利用繼承鏈而非依賴於結構相容性, 能更準確地反映出 instanceof操作符在執行時的行為。 這可以幫助避免一些複雜的問題,當使用 instanceof去細化結構上相似(但無關)的型別時。

其次,in操作符現在做為型別保護使用,會細化掉沒有明確宣告的屬性名。

interface A { a: number };
interface B { b: string };

function foo(x: A | B) {
    if ("a" in x) {
        return x.a;
    }
    return x.b;
}

更智慧的物件字面量推斷

在 JS 裡有一種模式,使用者會忽略掉一些屬性,稍後在使用的時候那些屬性的值為undefined。

let foo = someTest ? { value: 42 } : {};

在以前TypeScript會查詢{ value: number }和{}的最佳超型別,結果是{}。 這從技術角度上講是正確的,但並不是很有用。

從2.7版本開始,TypeScript 會“規範化”每個物件字面量型別記錄每個屬性, 為每個undefined型別屬性插入一個可選屬性,並將它們聯合起來。

在上例中,foo的最型別是{ value: number } | { value?: undefined }。 結合了 TypeScript 的細化型別,這讓咱們可以編寫更具表達性的程式碼且 Typwww.cppcns.comeScript 也可理解。 看另外一個例子:

// Has type
//  | { a: boolean,aData: number,b?: undefined }
//  | { b: boolean,bData: string,a?: undefined }
let bar = Math.random() < 0.5 ?
    { a: true,aData: 100 } :
    { b: true,bData: "hello" };

if (bar.b) {
    // TypeScript now knows that 'bar' has the type
    //
    //   '{ b: boolean,a?: undefined }'
    //
    // so it knows that 'bData' is available.
    bar.bData.toLowerCase()
}

這裡,TypeScript 可以通過檢查b屬性來細化bar的型別,然後允許我們訪問bData屬性。

unique symbol 型別和常量名屬性

TypeScript 2.7 對ECMAScript裡的symbols有了更深入的瞭解,你可以更靈活地使用它們。

一個需求很大的用例是使用symbols來宣告一個型別良好的屬性。 比如,看下面的例子:

const Foo = Symbol("Foo");
const Bar = Symbol("Bar");

let x = {
    [Foo]: 100,[Bar]: "hello",};

let a = x[Foo]; // has type 'number'
let b = x[Bar]; // has type 'string'

可以看到,TypeScript 可以追蹤到x擁有使用符號Foo和Bar宣告的屬性,因為Foo和Bar被宣告成常量。 TypeScript 利用了這一點,讓Foo和Bar具有了一種新型別:unique symbols。

unique symbols是symbols的子型別,僅可通過呼叫Symbol()或Symbol.for()或由明確的型別註釋生成。 它們僅出現在常量宣告和只讀的靜態屬性上,並且為了引用一個存在的unique symbols型別,你必須使用typeof操作符。 每個對unique symbols的引用都意味著一個完全唯一的宣告身份。

// Works
declare const Foo: unique symbol;

// Error! 'Bar' isn't a constant.
let Bar: unique symbol = Symbol();

// Works - refers to a unique symbol,but its identity is tied to 'Foo'.
let Baz: typeof Foo = Foo;

// Also works.
class C {
    static readonly StaticSymbol: unique symbol = Symbol();
}

因為每個unique symbols都有個完全獨立的身份,因此兩個unique symbols型別之前不能賦值和比較。

const Foo = Symbol();
const Bar = Symbol();

// Error: can't compare two unique symbols.
if (Foo === Bar) {
    // ...
}

另一個可能的用例是使用 symbols做為聯合標記。

// ./ShapeKind.ts
export const Circle = Symbol("circle");
export const Square = Symbol("square");

// ./ShapeFun.ts
import * as ShapeKind from "./ShapeKind";

interface Circle {
    kind: twsDynEZpeypeof ShapeKind.Circle;
    radius: number;
}

interface Square {
    kind: typeof ShapeKind.Square;
    sideLength: number;
}

function area(shape: Circle | Square) {
    if (shape.kind === ShapeKind.Circle) {
        // 'shape' has type 'Circle'
        return Math.PI * shape.radius *程式設計客棧* 2;
    }
    // 'shape' has type 'Square'
    return shape.sideLength ** 2;
}

更嚴格的類屬性檢查

TypeScript 2.7 引入了一個新的編譯器選項,用於類中嚴格的屬性初始化檢查。如果啟用了--strictPropertyInitialization標誌,則型別檢查器將驗證類中宣告的每個例項屬性

  • 是否有包含undefined的型別
  • 有一個明確的初始值設定項
  • 在建構函式中被明確賦值

--strictPropertyInitialization選項是編譯器選項系列的一部分,當設定--strict標誌時,該選項會自動啟用。 與所有其他嚴格的編譯器選項一樣,咱們可以將--strict設定為true,並通過將--strictPropertyInitialization設定為false來有選擇地退出嚴格的屬性初始化檢查。

請注意,必須設定--strictNullCheck標誌(通過—strict直接或間接地設定),以便--strictPropertyInitialization起作用。

現在,來看看嚴格的屬性初始化檢查。如果沒有啟用--strictpropertyinitialized標誌,下面的程式碼型別檢查就可以了,但是會在執行時產生一個TypeError錯誤:

class User {
  username: string;
}

const user = new User();

// TypeError: Cannot read property 'toLowerCase' of undefined
const username = user.username.toLowerCase();

出現執行時錯誤的原因是,username屬性值為undefined,因為沒有對該屬性的賦值。因此,對toLowerCase()方法的呼叫失敗。

如果啟用——strictpropertyinitialize,型別檢查器將會報一個錯誤:

class User {
  // Type error: Property 'username' has no initializer
  // and is not definitely assigned in the constructor
  username: string;
}

接下來,看看四種不同的方法,可以正確地輸入User類來消除型別錯誤。

解決方案1:允許定義

消除型別錯誤的一種方法是為username屬性提供一個包含undefined的型別:

class User {
  username: string | undefined;
}

const user = new User();

現在,username屬性儲存undefined的值是完全有效的。但是,當咱們想要將username屬性用作字串時,首先必須確保它實際包含的是字串而不是undefined的值,例如使用typeof

// OK
const username = typeof user.username === "string"
  ? user.username.toLowerCase()
  : "n/a";

解決方案2:顯式屬性初始化

消除型別錯誤的另一種方法是向username屬性新增顯式初始化。通過這種方式,屬性將立即儲存一個字串值,並且不會明顯的undefined:

class User {
  username = "n/a";
}

const user = new User();

// OK
const username = user.username.toLowerCase();

解決方案3: 使用建構函式賦值

也許最有用的解決方案是將username引數新增到建構函式中,然後將其分配給username屬性。這樣,每當構造User類的例項時,呼叫者必須提供使用者名稱作為引數:

class User {
  username: string;

  constructor(username: string) {
    this.username = username;
  }
}

const user = new User("mariusschulz");

// OK
consthttp://www.cppcns.com username = user.username.toLowerCase();

咱們 還可以通過刪除對類欄位的顯式賦值並將public修飾符新增到username建構函式引數來簡化User類,如下所示:

class User {
  constructor(public username: string) {}
}

const user = new User("mariusschulz");

// OK
const username = user.username.toLowerCase();

請注意,嚴格的屬性初始化要求在建構函式中所有可能的程式碼路徑中明確分配每個屬性。 因此,以下程式碼型別不正確,因為在某些情況下,我們將username屬性賦值為未初始化狀態:

class User {
  // Type error: Property 'username' has no initializer
  // and is not definitely assigned in the constructor.
  username: string;

  constructor(username: string) {
    if (Math.random() < 0.5) {
      this.username = username;
    }
  }
}

解決方案4:明確的賦值斷言

如果類屬性既沒有顯式初始化,也沒有undefined的型別,則型別檢查器要求直接在建構函式中初始化該屬性;否則,嚴格的屬性初始化檢查將失敗。如果咱們希望在幫助方法中初始化屬性,或者讓依賴項注入框架來初始化屬性,那麼這是有問題的。在這些情況下,咱們必須將一個明確的賦值程式設計客棧斷言(!)新增到該屬性的宣告中:

class User {
  username!: string;

  constructor(username: string) {
    this.initialize(username);
  }

  private initialize(username: string) {
    this.username = username;
  }
}

const user = new User("mariusschulz");

// OK
const username = user.username.toLowerCase();

通過向username屬性新增一個明確的賦值斷言,這會告訴型別檢查器,期望對username屬性進行初始化,即使它自己無法檢測到這一點。現在咱們的責任是確保在建構函式返回後明確地將屬性賦值給它,所以必須小心;否則,username屬性可能被明顯的undefined或者在執行時就會報TypeError錯誤。

顯式賦值斷言

儘管咱們嘗試將型別系統做的更富表現力,但我們知道有時使用者比TypeScript更加了解型別。

上面提到過,顯式賦值斷言是一個新語法,使用它來告訴 TypeScript 一個屬性會被明確地賦值。 但是除了在類屬性上使用它之外,在TypeScript 2.7裡你還可以在變數宣告上使用它!

let x!: number[];
initialize();
x.push(4);

function initialize() {
    x = [0,1,2,3];
}

假設我們沒有在x後面加上感嘆號,那麼TypeScript會報告x從未被初始化過。 它在延遲初始化或重新初始化的場景下很方便使用。

以上就是詳解TS數字分隔符和更嚴格的類屬性檢查的詳細內容,更多關於TS的資料請關注我們其它相關文章!