TypeScript 2.1介紹

2022-04-21 09:19 更新

keyof和Lookup類(lèi)型

在JavaScript中,使用期望屬性名稱(chēng)作為參數(shù)的API是相當(dāng)普遍的,但到目前為止,還無(wú)法表達(dá)這些API中出現(xiàn)的類(lèi)型關(guān)系。

輸入索引類(lèi)型查詢(xún)或keyof;索引類(lèi)型查詢(xún)keyof T可以為T(mén)生成允許的屬性名稱(chēng)類(lèi)型。keyof T類(lèi)型被認(rèn)為是一種string的子類(lèi)型。

示例

interface Person {
    name: string;
    age: number;
    location: string;
}

type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[];  // "length" | "push" | "pop" | "concat" | ...
type K3 = keyof { [x: string]: Person };  // string

其中的雙重屬性是索引訪問(wèn)類(lèi)型,也稱(chēng)為lookup類(lèi)型。從語(yǔ)法上講,它們看起來(lái)完全像元素訪問(wèn),但是寫(xiě)成類(lèi)型:

示例

type P1 = Person["name"];  // string
type P2 = Person["name" | "age"];  // string | number
type P3 = string["charAt"];  // (pos: number) => string
type P4 = string[]["push"];  // (...items: string[]) => number
type P5 = string[][0];  // string

您可以將此模式與類(lèi)型系統(tǒng)的其他部分一起使用,以獲得類(lèi)型安全的查找。

function getProperty<T, K extends keyof T>(obj: T, key: K) {
    return obj[key];  // Inferred type is T[K]
}

function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]) {
    obj[key] = value;
}

let x = { foo: 10, bar: "hello!" };

let foo = getProperty(x, "foo"); // number
let bar = getProperty(x, "bar"); // string

let oops = getProperty(x, "wargarbl"); // Error! "wargarbl" is not "foo" | "bar"

setProperty(x, "foo", "string"); // Error!, string expected number

映射類(lèi)型

一個(gè)常見(jiàn)的任務(wù)是采用現(xiàn)有類(lèi)型并使其每個(gè)屬性完全可選。假設(shè)我們有一個(gè)Person:

interface Person {
    name: string;
    age: number;
    location: string;
}

它的部分版本是:

interface PartialPerson {
    name?: string;
    age?: number;
    location?: string;
}

使用Mapped類(lèi)型,PartialPerson可以寫(xiě)為Person類(lèi)型的廣義轉(zhuǎn)換,例如:

type Partial<T> = {
    [P in keyof T]?: T[P];
};

type PartialPerson = Partial<Person>;

映射類(lèi)型是通過(guò)獲取文字類(lèi)型的并集,并為新對(duì)象類(lèi)型計(jì)算一組屬性來(lái)生成的。它們與Python中的列表推導(dǎo)類(lèi)似,但它們不是在列表中生成新元素,而是在類(lèi)型中生成新屬性。

除此了Partial之外,Mapped Types還可以在類(lèi)型上表達(dá)許多有用的轉(zhuǎn)換:

// Keep types the same, but make each property to be read-only.
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

// Same property names, but make the value a promise instead of a concrete one
type Deferred<T> = {
    [P in keyof T]: Promise<T[P]>;
};

// Wrap proxies around properties of T
type Proxify<T> = {
    [P in keyof T]: { get(): T[P]; set(v: T[P]): void }
};

Partial,Readonly,Record,和Pick

Partial和Readonly,如前所述,是非常有用的結(jié)構(gòu)。您可以使用它們來(lái)描述一些常見(jiàn)的JS例程,例如:

function assign<T>(obj: T, props: Partial<T>): void;
function freeze<T>(obj: T): Readonly<T>;

因此,它們現(xiàn)在默認(rèn)包含在標(biāo)準(zhǔn)庫(kù)中。

我們還包括另外兩種實(shí)用程序類(lèi)型:Record和Pick。

// From T pick a set of properties K
declare function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K>;

const nameAndAgeOnly = pick(person, "name", "age");  // { name: string, age: number }
// For every properties K of type T, transform it to U
function mapObject<K extends string, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U>

const names = { foo: "hello", bar: "world", baz: "bye" };
const lengths = mapObject(names, s => s.length);  // { foo: number, bar: number, baz: number }

對(duì)象擴(kuò)展與休息

TypeScript 2.1支持ESnext Spread和Rest。

與數(shù)組擴(kuò)展類(lèi)似,擴(kuò)展對(duì)象可以很方便地獲得淺層副本:

let copy = { ...original };

同樣,您可以合并多個(gè)不同的對(duì)象。在以下示例中,merged將具有來(lái)自foo,bar和baz的屬性。

let merged = { ...foo, ...bar, ...baz };

您還可以覆蓋現(xiàn)有屬性并添加新屬性:

let obj = { x: 1, y: "string" };
var newObj = {...obj, z: 3, y: 4}; // { x: number, y: number, z: number }

指定擴(kuò)展操作的順序決定了生成的對(duì)象中最終的屬性;以后的屬性會(huì)在以前創(chuàng)建的屬性上“win out”。

對(duì)象休息是對(duì)象擴(kuò)展的雙重對(duì)象,因?yàn)樗鼈兛梢蕴崛≡诮鈽?gòu)元素時(shí)不會(huì)被拾取的任何額外屬性:

let obj = { x: 1, y: 1, z: 1 };
let { z, ...obj1 } = obj;
obj1; // {x: number, y:number};

下層異步功能

在TypeScript 2.1之前支持此功能,但僅在定位ES6/ES2015時(shí)。TypeScript 2.1為ES3和ES5運(yùn)行時(shí)提供了功能,這意味著無(wú)論您使用何種環(huán)境,您都可以自由地利用它。

注意:首先,我們需要確保我們的運(yùn)行時(shí)具有全局可用的ECMAScript兼容性Promise。這可能涉及為Promise獲取一個(gè)polyfill,或者依賴(lài)一個(gè)你可能在你所定位的運(yùn)行時(shí)間。我們還需要確保TypeScript知道Promise是存在的,通過(guò)將lib標(biāo)志設(shè)置為類(lèi)似于"dom", "es2015"或"dom", "es2015.promise", "es5"的東西。

示例

tsconfig.json
{
    "compilerOptions": {
        "lib": ["dom", "es2015.promise", "es5"]
    }
}
dramaticWelcome.ts
function delay(milliseconds: number) {
    return new Promise<void>(resolve => {
        setTimeout(resolve, milliseconds);
    });
}

async function dramaticWelcome() {
    console.log("Hello");

    for (let i = 0; i < 3; i++) {
        await delay(500);
        console.log(".");
    }

    console.log("World!");
}

dramaticWelcome();

編譯和運(yùn)行輸出應(yīng)該會(huì)導(dǎo)致ES3/ES5引擎上的正確行為。

支持外部助手庫(kù)(tslib)

TypeScript注入了一些輔助函數(shù),例如用于繼承的__extends,在對(duì)象文字中用于擴(kuò)展操作的__assign和JSX元素以及用于異步函數(shù)的__awaiter。

以前有兩種選擇:

  1. 在每個(gè)需要它們的文件中注入幫助器,或者
  2. 沒(méi)有幫助器--noEmitHelpers。

這兩個(gè)選項(xiàng)還有待改進(jìn);將幫助器捆綁在每個(gè)文件中對(duì)于試圖保持其包裝尺寸較小的客戶(hù)來(lái)說(shuō)是一個(gè)痛點(diǎn)。并且不包括幫助器,意味著客戶(hù)必須維護(hù)自己的幫助程序庫(kù)。

TypeScript 2.1允許在項(xiàng)目中將這些文件包含在一個(gè)單獨(dú)的模塊中,編譯器將根據(jù)需要向它們發(fā)出導(dǎo)入。

首先,安裝tslib實(shí)用程序庫(kù):

npm install tslib

其次,使用--importHelpers命令編譯文件:

tsc --module commonjs --importHelpers a.ts

因此,給定以下輸入,生成的.js文件將包含導(dǎo)入到tslib并使用其中的__assign幫助程序而不是內(nèi)聯(lián)它。

export const o = { a: 1, name: "o" };
export const copy = { ...o };
"use strict";
var tslib_1 = require("tslib");
exports.o = { a: 1, name: "o" };
exports.copy = tslib_1.__assign({}, exports.o);

無(wú)類(lèi)型的導(dǎo)入

傳統(tǒng)上,TypeScript對(duì)于如何導(dǎo)入模塊過(guò)于嚴(yán)格。這是為了避免拼寫(xiě)錯(cuò)誤并阻止用戶(hù)錯(cuò)誤地使用模塊。

但是,在很多時(shí)候,您可能只想導(dǎo)入可能沒(méi)有自含.d.ts文件的現(xiàn)有模塊。以前這是一個(gè)錯(cuò)誤。從TypeScript 2.1開(kāi)始,這現(xiàn)在變得更加容易。

使用TypeScript 2.1,您可以導(dǎo)入JavaScript模塊而無(wú)需類(lèi)型聲明。如果存在類(lèi)型聲明(例如,declare module "foo" { ... },或node_modules/@types/foo)仍然具有優(yōu)先權(quán)。

對(duì)沒(méi)有聲明文件的模塊的導(dǎo)入仍將被標(biāo)記為--noImplicitAny下的錯(cuò)誤。

示例

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

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

TypeScript 2.1支持三個(gè)新的目標(biāo)值--target ES2016,--target ES2017和--target ESNext。

使用target --target ES2016將指示編譯器不要轉(zhuǎn)換特定于ES2016的功能,例如**運(yùn)算符。

同樣,--target ES2017將指示編譯器不要轉(zhuǎn)換特定于ES2017的特性,如async/ await。

--target ESNext針對(duì)最新支持的ES提議功能。

改進(jìn)的any推理

以前,如果TypeScript無(wú)法確定變量的類(lèi)型,則會(huì)選擇any類(lèi)型。

let x;      // implicitly 'any'
let y = []; // implicitly 'any[]'

let z: any; // explicitly 'any'.

使用TypeScript 2.1,而不僅僅是選擇any,TypeScript將根據(jù)您最后分配的內(nèi)容推斷類(lèi)型。

僅在設(shè)置--noImplicitAny時(shí)才啟用此選項(xiàng)。

示例

let x;

// You can still assign anything you want to 'x'.
x = () => 42;

// After that last assignment, TypeScript 2.1 knows that 'x' has type '() => number'.
let y = x();

// Thanks to that, it will now tell you that you can't add a number to a function!
console.log(x + y);
//          ~~~~~
// Error! Operator '+' cannot be applied to types '() => number' and 'number'.

// TypeScript still allows you to assign anything you want to 'x'.
x = "Hello world!";

// But now it also knows that 'x' is a 'string'!
x.toLowerCase();

現(xiàn)在也對(duì)空數(shù)組進(jìn)行了相同類(lèi)型的跟蹤。

聲明為沒(méi)有類(lèi)型注釋且初始值為[]的變量被視為隱式any[]變量。然而,每個(gè)后續(xù)的x.push(value),x.unshift(value)或者x[n] = value操作根據(jù)添加到的元素來(lái)演變變量的類(lèi)型。

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
}

隱含任何錯(cuò)誤

這樣做的一個(gè)很大好處是,在運(yùn)行--noImplicitAny時(shí)你會(huì)看到更少的隱式any錯(cuò)誤。僅當(dāng)編譯器無(wú)法知道沒(méi)有類(lèi)型注釋的變量類(lèi)型時(shí),才會(huì)報(bào)告隱式any錯(cuò)誤。

示例

function f3() {
    let x = [];  // Error: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined.
    x.push(5);
    function g() {
        x;    // Error: Variable 'x' implicitly has an 'any[]' type.
    }
}

更好地推斷文字類(lèi)型

字符串,數(shù)字和布爾文字類(lèi)型(例如"abc",1和true)僅在存在顯式類(lèi)型注釋時(shí)才推斷。從TypeScript 2.1開(kāi)始,始終為const變量和readonly屬性推斷文字類(lèi)型。

為沒(méi)有類(lèi)型注釋的const變量或readonly屬性推斷的類(lèi)型是文字初始值設(shè)定項(xiàng)的類(lèi)型。為具有初始值設(shè)定項(xiàng)且沒(méi)有類(lèi)型注釋的let變量,var變量,參數(shù)或非readonly屬性推斷的類(lèi)型是初始化程序的擴(kuò)展文字類(lèi)型。對(duì)于字符串文字類(lèi)型的加寬類(lèi)型是string,number對(duì)于數(shù)字文字類(lèi)型,boolean對(duì)于true或false,以及包含枚舉文字類(lèi)型的枚舉。

示例

const c1 = 1;  // Type 1
const c2 = c1;  // Type 1
const c3 = "abc";  // Type "abc"
const c4 = true;  // Type true
const c5 = cond ? 1 : "abc";  // Type 1 | "abc"

let v1 = 1;  // Type number
let v2 = c2;  // Type number
let v3 = c3;  // Type string
let v4 = c4;  // Type boolean
let v5 = c5;  // Type number | string

可以通過(guò)顯式類(lèi)型注釋來(lái)控制文字類(lèi)型擴(kuò)展。具體來(lái)說(shuō),當(dāng)為沒(méi)有類(lèi)型注釋的const位置推斷出文字類(lèi)型的表達(dá)式時(shí),該const變量將推斷出一個(gè)加寬的文字類(lèi)型。但是,當(dāng)const位置具有顯式文字類(lèi)型注釋時(shí),該const變量將獲得非加寬文字類(lèi)型。

示例

const c1 = "hello";  // Widening type "hello"
let v1 = c1;  // Type string

const c2: "hello" = "hello";  // Type "hello"
let v2 = c2;  // Type "hello"

使用超級(jí)調(diào)用的返回值為'this'

在ES2015中,返回對(duì)象的構(gòu)造函數(shù)隱式地將this值替換為super()的任何調(diào)用者。因此,有必要捕獲super()的任何潛在返回值,并且使用this替換它。此更改允許使用Custom Elements,該元素利用此特性用用戶(hù)編寫(xiě)的構(gòu)造函數(shù)初始化瀏覽器分配的元素。

示例

class Base {
    x: number;
    constructor() {
        // return a new object other than `this`
        return {
            x: 1,
        };
    }
}

class Derived extends Base {
    constructor() {
        super();
        this.x = 2;
    }
}

輸出:

var Derived = (function (_super) {
    __extends(Derived, _super);
    function Derived() {
        var _this = _super.call(this) || this;
        _this.x = 2;
        return _this;
    }
    return Derived;
}(Base));
這種變化導(dǎo)致擴(kuò)展內(nèi)置類(lèi)(如,Error,Array,Map,等)的行為中斷。

配置繼承

通常,一個(gè)項(xiàng)目有多個(gè)輸出目標(biāo),例如ES5和ES2015,調(diào)試和生產(chǎn),CommonJS和System;這兩個(gè)目標(biāo)之間只有少數(shù)配置選項(xiàng)發(fā)生變化,維護(hù)多個(gè)tsconfig.json文件可能很麻煩。

TypeScript 2.1支持使用extends繼承配置,其中:

  • extends是tsconfig.json中一個(gè)新的頂級(jí)屬性(包括compilerOptions,files,include,和exclude)。
  • extends的值必須是包含要繼承的另一個(gè)配置文件的路徑的字符串。
  • 首先加載基本文件中的配置,然后由繼承配置文件中的配置覆蓋。
  • 不允許配置文件之間的循環(huán)。
  • files,include和exclude從繼承配置文件覆蓋基本配置文件中的那些。
  • 配置文件中找到的所有相對(duì)路徑將相對(duì)于它們所源自的配置文件進(jìn)行解析。

示例

configs/base.json:

{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true
  }
}

tsconfig.json:

{
  "extends": "./configs/base",
  "files": [
    "main.ts",
    "supplemental.ts"
  ]
}

tsconfig.nostrictnull.json:

{
  "extends": "./tsconfig",
  "compilerOptions": {
    "strictNullChecks": false
  }
}

新 --alwaysStrict

使用--alwaysStrict調(diào)用編譯器的原因:

  1. 以嚴(yán)格模式解析所有代碼。
  2. 在每個(gè)生成的文件上面寫(xiě)入指令"use strict";。

模塊在嚴(yán)格模式下自動(dòng)解析。對(duì)于非模塊代碼,建議使用新標(biāo)志。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)