約束即類型、TypeScript 編程內參(一)

本文是《約束即類型、TypeScript 編程內參》系列第一篇:約束即類型,主要記述 TypeScript 的基本使用和語法。前端

PS: 本文語境下的「約束」指的是「類型對值的約束」java

1、瞭解 TypeScript

TS 你們都據說或者使用過,是 Angular 的官方語言,提供了靜態類型檢查,是 JavaScript 這門語言的超集,也就是說:node

TS = JS + 靜態類型檢查程序員

TS 今年開始火了,愈來愈多的 js 項目開始用 ts 來實現,所以有了一句廣爲流傳的名言(捏他)typescript

任何用 js 寫的項目終將用 ts 重構npm

那麼,你瞭解 ts 嗎?類型本質上是對變量的約束,理解類型,首先要理解的是變量的值,而後 ......編程

本文是本系列的第一篇約束即類型,面向的是「有必定 JS 開發經驗的學習者」 ,推薦前端/node工程師學習,建議跟隨本文的代碼邊寫邊看,包教不教會。json

2、初始化 TypeScript 項目

經過如下方式初始化一個 ts 項目並編譯運行:promise

$ npm i -g typescript   # 安裝 ts
$ mkdir my-ts-learn     # 搭建 playground
$ cd my-ts-learn     # 進入目錄
$ tsc --init            # 初始化一個 ts 工程
$ echo "console.log('hello, ts');" >> h.ts # 建立代碼
$ tsc       # 編譯
$ node h    # 運行
複製代碼

💡💡💡bash

關於 tsconfig.json 咱們會在本系列的其餘篇目介紹,敬請期待 對於初學者,暫時先使用默認的配置就好

3、any 類型

「any 即無約束」它表明着任意,以下所示:

let a: any = 123;

a = {};
a = () => {};
a = '不會報錯';
複製代碼

電視裏常說 AnyScrtipt 指的就是無理由的在 TS 裏大量使用 any;越是使用 any,則 ts 越像 js,不建議這樣作,這樣會失去使用 ts 的意義。

💡💡💡

類型自己就是對程序的證實

4、基本類型及類型推斷

const num: number = 123;
const str: string = 'eczn';
複製代碼

不少狀況下,咱們並不須要顯式地指明類型是什麼,ts 會幫咱們自動地進行「類型推斷」,比方說下面這樣寫的話,ts 會自動推斷出 val 的類型是 string:

let val = '123';
val = 123; // 不行,會報錯
複製代碼

💡💡💡

好的 ts 代碼老是這樣的:大部分變量的類型是 ts 自動推斷出來的,而不是程序員處處給變量加類型(這樣就成 java 了)

5、對象類型

通常狀況下,咱們能夠利用 interfacetype 聲明來創造對 JS 對象的「約束」。

interface Person1 {
    name: string;
}

type Person2 = {
    name: string;
}

const p1: Person1 = { name: '001 號居民' };
const p2: Person2 = { name: '002 號居民' };
複製代碼

可是有一點要清楚,TS 的類型學名上叫作 結構化類型 Structral Type,跟其餘語言裏的 具名類型 Naming Type 不太同樣,譬如說:

interface Person3 = {
    name: string;
}

const p1: Person1 = { name: '001 號居民' };
const p3: Person3 = p1;
// 在 naming type 的語言中,這樣會報錯
// 但在 ts 這種 structral type 的語言下,這樣不會報錯
複製代碼

簡而言之,structral type 進行類型檢查的時候比較的是兩類型的結構,若是同樣說明類型同樣;而 naming type 僅是比較類型的標識符是否是同樣,不同則認爲類型不合。

因此我更傾向於認爲,structral type 的做用,其實對值的一種約束。

💡💡💡

type 和 interface 二者在不少狀況下是能夠等價互相轉換的,但實際上二者是有很大不一樣的,文章系列後文會描述

6、函數類型

函數的類型由這三者描述:i 入參ii 返回值iii 上下文

interface Person { name: string; };

// 需注意,這裏的 this 通過 ts 編譯後會消失
// 同窗們能夠自行編譯體會個中奧義
function sayName(this: Person, suffix: string) {
    return this.name + ' ' + suffix;
}

const 無名先生 = { sayName };
const 阿J = { name: 'j', sayName };

// 報錯,無名先生無名,不知足 this 上下文類型約束
無名先生.sayName('先生');

// 這裏 阿 J 有 name 屬性,知足 this 約束
阿J.sayName('先生');

// 注意,箭頭函數在語言定義上是沒有上下文的
// 所以下面這個會報錯
const test = (this: any, x: string): string => x;
複製代碼

固然,在類裏面聲明的類型,其 this 已經默認爲其自己了:

class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    
    sayName(suffix: string) {
        // 這裏 ts 能正確識別 this 指的是 Person 類
        // 不會報錯
        return this.name + ' ' + suffix;
    }
}
複製代碼

💡💡💡

不少時候咱們沒必要吧函數的上下文也定義出來,通常都是這樣定義函數的:

const func = (a: X): Y = { /* function body */ }

7、字面量類型

對於值來講,字面量類型是比基本類型更窄的約束,好比:

function sayHello(suffix: '先生') {
    return 'hello,' + suffix;
}
sayHello('先生'); // 知足字面量類型,不會報錯
sayHello('女士'); // 不知足,類型不合
複製代碼

咱們熟知的 number 類型也有對應的字面量類型,讀者能夠自行寫 demo 驗證推敲。

8、利用 typeof 動態地推斷

ts 拓展了 js 語法裏面的 typeof,使其能夠在 ts 進行類型聲明的時候獲取某個變量的類型:

let num = 123;
type MyNumber = typeof num;
// MyNumber 指的是就是 number 類型
複製代碼

ts 不少狀況下是不用聲明類型的,ts 會自動推斷的,好比下面 UP_POSITION 的類型 ts 會自動推斷爲類型 string

let UP_POSITION = '向上';
複製代碼

可是下面這種狀況呢?

const UP_POSITION_2 = '向上';
複製代碼

這種狀況下咱們獲得的類型是一個字面量類型 '向上',以下所示

鼠標放在 Test 上能夠看到精肯定義

形成這種緣由的結果是 const 聲明的變量不會再變了,所以出來的是字面量類型,而 let 聲明的類型是不肯定的,所以用的是 string

那若是,我必定要在 let 的狀況下推斷某變量的類型爲字面量呢?能夠這樣:

let UP_POSITION = '向上' as const;
type Test = typeof UP_POSITION;
// Test 是 '向上'
複製代碼

as const 會告訴 ts 請勿擴大範圍。

💡💡💡

仔細想想下面三行代碼以及背後的運做原理 🤔

let a = 123;
let t = typeof a; // "number"
type T = typeof a; // number
複製代碼

9、泛型

泛型的意義在於,他聲明瞭類型的上下文環境,使類型能夠做爲參數進行傳遞,好比我想實現一個數學上的常函數 x => x,ts 實現以下(須要用到泛型):

常函數 x => x

這裏的 ts 聲明描述了:

  1. 這裏 T 單獨尖括號標出的意思是告訴 ts,接下來的 T 是泛型
  2. 函數入參 x 和函數返回值的類型是 T

當具體 ts 去推斷 val 的類型的時候,就能夠發現 val 是 id('123') 這時候 T = '123' 所以 val 的類型就是 '123'。

泛型無處不在,它是類型的拓展,咱們通常利用泛型去定義 可拓展的數據結構/接口/類型, 如 js 一些原生類裏面就有泛型的影子:

// 求和 arr 並結果將其以 promise 的形式包裹返回
function sum(arr: Array<number>): Promise<number> {
    const sum = arr.reduce((a, b) => a + b);
    return Promise.resolve(sum);
}

sum([1, 2, 3]).then(console.log);
// => 6
複製代碼

💡💡💡

泛型裏的泛是寬泛的泛,而不是範式的範。

最後一例:利用泛型實現鏈表:

// 鏈表, 這裏聲明瞭泛型 T
class CommomList<T> {
    value: T;
    // 這裏的意思幾乎等價於下面這種寫法,用於聲明可能不存在的字段:
    // next: CommomList<T> | undefined;
    next?: CommomList<T>;
    
    // 構造函數
    constructor(value: T, next?: CommomList<T>) {
        this.value = value;
        this.next = next;
    }

    // 設置 next
    setNext(next: CommomList<T>) { // next 是下一個節點
        this.next = next;
        return next;
    }

    // 鏈表生長
    grow(value: T) { // value 下一個節點的值
        const t = new CommomList(value);
        this.setNext(t);
        return t;
    }

    // 遞歸地打印本身
    toString(): string {
        return this.next ?
            JSON.stringify(this.value) + ' -> ' + this.next.toString() :
            JSON.stringify(this.value) + ' -> null';
    }
}

const _1 = new CommomList(1);

_1.grow(2).grow(3).grow(4);

// _1 是頭
console.log(_1.toString());
複製代碼

本篇末

本文說明了 TS 的基本概念和使用方式,如下是總結 CheckList:

  1. 理解類型的內涵「類型是一種對於值的約束」
  2. 理解基本類型、函數類型、對象類型、字面量類型
  3. 體會到「類型自己就是對程序的證實」的思想
  4. 初步認識 any 和 typeof
  5. 初步理解了泛型的做用

本文的下一篇是「構造類型抽象、TypeScript 編程內參(二)」,敬請期待

相關文章
相關標籤/搜索