TypeScript學習筆記

(一) 基礎類型

什麼是TypeScript

TypeScript 是 JavaScript 的一個超集,主要提供了類型系統對 ES6 的支持前端

官方定義node

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. Any browser. Any host. Any OS. Open source.es6

TypeScript 是 JavaScript 的類型的超集,它能夠編譯成純 JavaScript。編譯出來的 JavaScript 能夠運行在任何瀏覽器上。TypeScript 編譯工具能夠運行在任何服務器和任何系統上。TypeScript 是開源的。typescript

爲何要學習 TypeScript

  • Typescript 是對 JavaScript 的加強,它大大加強了代碼的可讀性和維護性,讓咱們編寫出更加健壯的代碼shell

  • 將來前端的發展趨勢npm

    • 最新發布的 Vue3 使用了TypeScript。
    • Angular 在 2.0 版本就內置了 TypeScript
    • React 對TypeScript 的支持也很絲滑

開始學習TypeScript

類型註解

學習TypeScript以前咱們先來了解類型註解這個概念TypeScript裏的類型註解是一種輕量級的爲函數或變量添加約束的方式。編程

// js
let num = 5 
num = 'hello' // 沒毛病

// ts
let num: number = 5 
num = 'hello' // 報錯,由於定義了num爲number類型的變量因此賦值爲string類型時會報錯
複製代碼
複製代碼

而後咱們來看看TypeScript中的基本類型json

  • 布爾類型(boolean)windows

  • 數字類型(number)數組

  • 字符串類型(string)

  • 數組類型(array)

  • 元組類型(tuple)

  • 枚舉類型(enum)

  • 任意值類型(any)

  • null 和 undefined

  • void類型

  • never類型

  • object 類型

  • 類型斷言

  • Symbol 類型

布爾值

最基本的數據類型就是簡單的true/false值,在JavaScript和TypeScript裏叫作boolean(其它語言中也同樣)。

let bool: boolean = true
複製代碼
複製代碼

數字

和JavaScript同樣,TypeScript裏的全部數字都是浮點數。 這些浮點數的類型是number。 除了支持十進制和十六進制字面量,TypeScript還支持ECMAScript 2015中引入的二進制和八進制字面量。

let num: number = 123
num = 0b1101 // 二進制
num = 0o164 // 八進制
num = 0x8b // 十六進制
複製代碼
複製代碼

字符串類型

和JavaScript同樣,可使用雙引號(")或單引號(')表示字符串。

let name: string = 'hui'
複製代碼
複製代碼

你還可使用_模版字符串_,它能夠定義多行文本和內嵌表達式。 這種字符串是被反引號包圍( **** ),而且以${ expr }`這種形式嵌入表達式

let name: string = `hui`
let hello: string = `hello, my name is ${name}`
複製代碼
複製代碼

數組

TypeScript像JavaScript同樣能夠操做數組元素。

有兩種方式能夠定義數組。 第一種,能夠在元素類型後面接上[],表示由此類型元素組成的一個數組:

let arr1: number[] = [1, 2, 3]
let arr2: string[] = ['a', 'b', 'c']
let arr3: (number | string)[] = [1, 'b', 3] // 數組元素既能夠是number類型也能夠是string類型
複製代碼
複製代碼

第二種方式是使用數組泛型,Array<元素類型>

let arr1: Array<number> = [1, 2, 3]
let arr2: Array<string> = ['a', 'b', 'c']
let arr3: Array<number | string> = [1, 'b', 2]
複製代碼
複製代碼

元組 Tuple

元組類型容許表示一個已知元素數量和類型的數組,各元素的類型沒必要相同。 好比,你能夠定義一對值分別爲stringnumber類型的元組。

let x: [string, number]
x = ['hui', 2] // OK
x = [2, 'hui'] // Error
複製代碼
複製代碼

枚舉類型

enum類型是對JavaScript標準數據類型的一個補充。 像C#等其它語言同樣,使用枚舉類型能夠爲一組數值賦予友好的名字。

enum Roles {
  SUPER_ROLE,
  ADMIN,
  USER
}
console.log(Roles.SUPER_ROLE) // 0
console.log(Roles.ADMIN) // 1
console.log(Roles.USER) // 2
複製代碼
複製代碼

默認狀況下,從0開始爲元素編號。 你也能夠手動的指定成員的數值。 例如,咱們將上面的例子改爲從1開始編號:

enum Roles {
  SUPER_ROLE = 1,
  ADMIN,
  USER
}
console.log(Roles.SUPER_ROLE) // 1
console.log(Roles.ADMIN) // 2
console.log(Roles.USER) // 3
複製代碼
複製代碼

任意值 any

有時候,咱們會想要爲那些在編程階段還不清楚類型的變量指定一個類型。 這些值可能來自於動態的內容,好比來自用戶輸入或第三方代碼庫。 這種狀況下,咱們不但願類型檢查器對這些值進行檢查而是直接讓它們經過編譯階段的檢查。 那麼咱們可使用any類型來標記這些變量:

let name:any = 'hui'
name = 123 // OK
name = true // OK
複製代碼
複製代碼

當你只知道一部分數據的類型時,any類型也是有用的。 好比,你有一個數組,它包含了不一樣的類型的數據:

let list: any[] = [1, true, 'hui']
複製代碼
複製代碼

儘可能少的使用 any, 不然你可能在用 AnyScript 寫代碼

空值

某種程度上來講,void類型像是與any類型相反,它表示沒有任何類型。 當一個函數沒有返回值時,你一般會見到其返回值類型是void

function getName():void {
  alert('my name is hui')
}
複製代碼
複製代碼

聲明一個void類型的變量沒有什麼大用,由於你只能爲它賦予undefinednull

let v: void
v = undefined // OK
複製代碼
複製代碼

Null 和 Undefined

TypeScript裏,undefinednull二者各自有本身的類型分別叫作undefinednull。 和void類似,它們的自己的類型用處不是很大:

let u: undefined = undefined
let n: null = null
複製代碼
複製代碼

Never

never類型表示的是那些永不存在的值的類型。never 類型用於返回 error 死循環

function getError(message: string): never {
    throw new Error(message);
}
function infiniteFunc(): never {
    while (true) {}
}
複製代碼
複製代碼

never類型是任何類型的子類型,也能夠賦值給任何類型;然而,_沒有_類型是never的子類型或能夠賦值給never類型(除了never自己以外)。 即便any也不能夠賦值給never

let neverVarible: never = (() => {
  while (true) {}
})()
let num: number = 123
let name: string = 'hui'
num = neverVarible // OK
neverVarible = name // error
複製代碼
複製代碼

類型斷言

類型斷言就是手動指定一個類型的值

類型斷言有兩種形式。 其一是「尖括號」語法:

let str: any = "this is a string";

let strLength: number = (<string>str).length;
複製代碼
複製代碼

另外一個爲as語法:

let str: any = "this is a string";

let strLength: number = (str as string).length;
複製代碼
複製代碼

(二) 接口

什麼是接口(interface)

TypeScript的核心原則之一是對值所具備的_結構_進行類型檢查。 它有時被稱作「鴨式辨型法」或「結構性子類型化」。 在TypeScript裏,接口的做用就是爲這些類型命名和爲你的代碼或第三方代碼定義契約。

基本用法

經過一個簡單的例子來看看接口是如何工做的

function getFullName(fullName: {firstName: string, lastName: string}) {
  return `${fullName.firstName} ${fullName.lastName}`
}
let fullName = {
  firstName: 'li',
  lastName: 'hh',
  age: 18
}
getFullName(fullName)
複製代碼
複製代碼

類型檢查器會查看getInfo的調用。 getInfo有一個參數,並要求這個對象參數有一個名爲firstName類型爲 string 和名爲 lastName 類型爲 string 的屬性。 須要注意的是,咱們傳入的對象參數實際上會包含不少屬性,可是編譯器只會檢查那些必需的屬性是否存在,而且其類型是否匹配。 然而,有些時候TypeScript卻並不會這麼寬鬆,咱們下面會稍作講解。

下面咱們重寫上面的例子,此次用接口描述:必須包含 firstNamelastName 屬性

interface FullName {
  firstName: string,
  lastName: string
}
function getFullName(fullName: FullName) {
  return `${fullName.firstName} ${fullName.lastName}`
}
let fullName = {
  firstName: 'li',
  lastName: 'hh'
}
getFullName(fullName)
複製代碼
複製代碼

FullName 接口就比如是一個名字,用來描述例子裏的要求,他表示了有 firstNamelastName 而且類型爲 string 的對象。還有一點值得提的是,類型檢查器不會去檢查屬性的順序,只要相應的屬性存在而且類型也是對的就能夠。

接口的名字首字母要大寫

可選屬性

接口裏的屬性有時候不都是必須的,有時候是可選的,咱們用 ? 用來定義可選屬性

interface Fruit {
  type: string,
  color?: string
}
const getFruit({type, color}: Fruit):string {
  return `A ${color ? (color + ' ') : ''} ${type}`
}
getFruit({
  color: 'red',
  type: 'apple'
}) // ok
getFruit({
  type: 'apple'
}) // ok
複製代碼
複製代碼

多餘屬性檢查

仍是上面那個例子,若是咱們在傳遞的參數中多加一個屬性,例如

getFruit({
  color: 'red',
  type: 'apple',
  size: 19
}) // err
複製代碼
複製代碼

這個時候 TypeScript 會告訴咱們傳的參數多了一個 size 的屬性,可是其實這個是不影響咱們的結果的,這個時候有兩種辦法來解決這個問題。

第一種使用類型斷言

getFruit({
  color: 'red',
  type: 'apple',
  size: 19
} as Fruit) // ok
複製代碼
複製代碼

第二種使用索引簽名

interface Fruit {
  type: string,
  color?: string,
  [prop: string]: any
}
const getFruit({type, color}: Fruit):string {
  return `A ${color ? (color + ' ') : ''} ${type}`
}
getFruit({
  color: 'red',
  type: 'apple',
  size: 19
} as Fruit) // ok
複製代碼
複製代碼

只讀屬性

接口還能夠設置只讀屬性,表示這個屬性不可被修改

interface Fruit {
  type: string,
  readonly color: string
}
const apple: Fruit = {
  type: 'apple',
  color: 'red'
}
apply.color = 'green' // err
複製代碼
複製代碼

函數類型

接口不只能夠定義對象的形式,還能夠定義函數形式

interface AddFunc {
  (number1: number, number2: number) => number
}
const addFunc: AddFunc = (n1, n2) => {
  return n1 + n2
}

複製代碼
複製代碼

索引類型

接口還定義索引類型

interface ArrInter {
  0: string,
  1: number
}
const arr: ArrInter = ['a', 1]
複製代碼
複製代碼

繼承接口

一個接口能夠繼承另外一個接口, 使用 extends 關鍵字來實現繼承

interface Fruit {
	type: string
}
interface Apple extends Fruit {
  color: string
}
cosnt apple: Apple = {
  color: 'red'
} // 報錯, 由於繼承 Fruit 接口因此必須有 type 屬性
複製代碼
複製代碼

混合類型接口

接口還能夠定義包含任意類型屬性的接口

interface Counter {
  (): void, // 一個函數
  count: number
}
const getCounter = ():Counter => {
  const c = () => c.count++
  c.count = 1
  return c
}
const counter: Counter = getCounter()
counter()
console.log(counter.count) // 2
counter()
console.log(counter.count) // 3
counter()
console.log(counter.count) // 4
複製代碼
複製代碼

(三) 類

基本例子

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}

let greeter = new Greeter("world");
複製代碼
複製代碼

咱們聲明瞭一個Greeter類,它包含三個成員:一個greeting屬性,一個greet方法,一個構造函數。最後一行,咱們使用new構造了Greeter類的一個實例。 它會調用以前定義的構造函數,建立一個Greeter類型的新對象,並執行構造函數初始化它。

繼承

類容許繼承,基本例子

class Animal {
    move(distanceInMeters: number = 0) {
        console.log(`Animal moved ${distanceInMeters}m.`);
    }
}

class Dog extends Animal {
    bark() {
        console.log('Woof! Woof!');
    }
}

const dog = new Dog();
dog.bark();
dog.move(10);
dog.bark();
複製代碼
複製代碼

在這個例子中,類從基類中繼承了屬性和方法,在這裏,Dog是一個派生類,派生自Animal基類,派生類一般被稱做子類,基類一般被稱做超類。 複雜例子

class Animal {
    name: string;
    constructor(theName: string) { this.name = theName; }
    move(distanceInMeters: number = 0) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

class Horse extends Animal { 
    constructor(name: string) { 
      // 必須先調用super函數 
      super(name); 
    }
    // 重寫父類的方法
    move(distanceInMeters = 45) {
        console.log("Galloping...");
        super.move(distanceInMeters);
    }
}

let tom: Animal = new Horse("Tommy the Palomino");
tom.move(34);
複製代碼
複製代碼

在這個例子中,子類中多了構造函數,當子類中包含構造函數時,必須 要先調用super(),它會執行超類的構造函數,而且必定要在構造函數訪問this以前。子類還能夠重寫父類的方法。 因此,tom雖然被聲明爲animal類型,但它的值時House,因此執行的move方法是House中的move方法。

Galloping...
Tommy the Palomino moved 34m.
複製代碼
複製代碼

公共,私有與受保護的修飾符

public

默認爲public,不須要特別去標記

private

私有屬性不能夠在類的外部被訪問。

class Animal {
    private name: string;
    constructor(theName: string) { this.name = theName; }
}

new Animal("Cat").name; // 錯誤: 'name' 是私有的.
複製代碼
複製代碼
protected

protected屬性與私有屬性比較類似,不一樣的是,protected能夠在派生類中被訪問,

class Person {
    protected name: string;
    constructor(name: string) { this.name = name; }
}

class Employee extends Person {
    private department: string;

    constructor(name: string, department: string) {
        super(name)
        this.department = department;
    }

    public getElevatorPitch() {
        // 派生類能夠訪問父類中的protected屬性
        return `Hello, my name is ${this.name} and I work in ${this.department}.`;
    }
}

let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); // 錯誤,protected屬性不可在類的外部訪問。
複製代碼
複製代碼
readonly

只讀屬性,只讀屬性必須在聲明時或構造函數裏被初始化

參數屬性
class Animal {
    constructor(private name: string) { }
    move(distanceInMeters: number) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}
複製代碼
複製代碼

將原來須要在構造函數聲明以前定義的屬性直接在構造函數裏使用private name: string參數來建立和初始化name成員,把聲明和賦值合併至一處。

存取器

TypeScript支持經過getters/setters來截取對對象成員的訪問。 它能幫助你有效的控制對對象成員的訪問。

let passcode = "secret passcode";

class Employee {
    private _fullName: string;

    get fullName(): string {
        return this._fullName;
    }

    set fullName(newName: string) {
        if (passcode && passcode == "secret passcode") {
            this._fullName = newName;
        }
        else {
            console.log("Error: Unauthorized update of employee!");
        }
    }
}

let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
    alert(employee.fullName);
}
複製代碼
複製代碼

首先,存取器要求你將編譯器設置爲輸出ECMAScript 5或更高。 不支持降級到ECMAScript 3。 其次,只帶有get不帶有set的存取器自動被推斷爲readonly。

靜態屬性

靜態屬性是存在於類自己的成員,而不是在類的實例中,不須要在類的實例化時才被初始化。 訪問靜態屬性,須要在屬性名前加上類名,如同在實例屬性上使用this.前綴來訪問屬性同樣。

class Grid {
    static origin = {x: 0, y: 0};
    calculateDistanceFromOrigin(point: {x: number; y: number;}) {
        let xDist = (point.x - Grid.origin.x);
        let yDist = (point.y - Grid.origin.y);
        return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
    }
    constructor (public scale: number) { }
}

let grid1 = new Grid(1.0);  // 1x scale
let grid2 = new Grid(5.0);  // 5x scale

console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));
複製代碼
複製代碼

抽象類

抽象類通常是其餘派生類的基類,通常不會實例化。 abstract 關鍵字主要是用於定義抽象類和抽象類中的方法,它的語法與接口的語法很類似,二者都是定義方法簽名但不包含方法體。 不一樣於接口的是:抽象類中的抽象方法不包含具體實現但必須在派生類中實現;抽象類能夠包含成員的實現細節;抽象方法必須包含abstract關鍵字而且能夠包含訪問修飾符。

abstract class Department {

    constructor(public name: string) {
    }

    printName(): void {
        console.log('Department name: ' + this.name);
    }

    abstract printMeeting(): void; // 必須在派生類中實現
}

class AccountingDepartment extends Department {

    constructor() {
        super('Accounting and Auditing'); // 在派生類的構造函數中必須調用 super()
    }

    printMeeting(): void {
        console.log('The Accounting Department meets each Monday at 10am.');
    }
    
    // 定義一個抽象基類中沒有聲明的方法,可是它沒辦法在實例中使用
    generateReports(): void {
        console.log('Generating accounting reports...');
    }
}

let department: Department; // 容許建立一個對抽象類型的引用
department = new Department(); // 錯誤: 不能建立一個抽象類的實例
department = new AccountingDepartment(); // 容許對一個抽象子類進行實例化和賦值
department.printName();
department.printMeeting();
department.generateReports(); // 錯誤: 方法在聲明的抽象類中不存在
複製代碼
複製代碼

高級類

構造函數

當你在TypeScript裏聲明瞭一個類的時候,實際上同時聲明瞭不少東西。 首先就是類的實例的類型。

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}
// 聲明greeter類的實例的類型是Greeter
let greeter: Greeter;
greeter = new Greeter("world");
console.log(greeter.greet());
複製代碼
複製代碼

這裏,咱們寫了let greeter: Greeter,意思是Greeter類的實例的類型是Greeter。

咱們也建立了一個叫作構造函數的值。 這個函數會在咱們使用new建立類實例的時候被調用。 下面咱們來看看,上面的代碼被編譯成JavaScript後是什麼樣子的:

let Greeter = (function () {
    function Greeter(message) {
        this.greeting = message;
    }
    Greeter.prototype.greet = function () {
        return "Hello, " + this.greeting;
    };
    return Greeter;
})();

let greeter;
greeter = new Greeter("world");
console.log(greeter.greet());
複製代碼
複製代碼

上面的代碼裏,let Greeter將被賦值爲構造函數。 當咱們調用new並執行了這個函數後,便會獲得一個類的實例。 這個構造函數也包含了類的全部靜態屬性。 換個角度說,咱們能夠認爲類具備實例部分與靜態部分這兩個部分。

(四) 開始使用 TypeScript

在開始使用 TypeScript 前你最好有如下準備:

  • Node.js > 8.0,最好是最新的穩定版(目前是V10.16.3 )
  • 一個包管理工具 npm 或者 yarn
  • 一個文本編輯器或者 IDE (筆者的是 vscode)

相關的 shell 命令僅適用於 *nix 系統,windows 系統不適用

安裝 TypeScript

TypeScript 的安裝很簡單,你能夠經過npm直接在全局安裝 TypeScript。

> npm install -g typescript
複製代碼

建立環境

隨後咱們要建立一個目錄:

mkdir ts-study && cd ts-study
複製代碼

接着建立 src 目錄:

mkdir src && touch src/index.ts
複製代碼

接着咱們用npm將目錄初始化:

npm init
複製代碼

此時咱們要使用 TypeScript 的話一般也須要初始化:

tsc --init
複製代碼

這個時候你會發現目錄下多了一個tsconfig.json文件.

這是 TypeScript 的配置文件,裏面已經包含官方初始化的一些配置以及註釋,咱們如今進行自定義的配置:

{
  "compilerOptions": {
    "target": "es5",                            // 指定 ECMAScript 目標版本: 'ES5'
    "module": "commonjs",                       // 指定使用模塊: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
    "moduleResolution": "node",                 // 選擇模塊解析策略
    "experimentalDecorators": true,             // 啓用實驗性的ES裝飾器
    "allowSyntheticDefaultImports": true,       // 容許從沒有設置默認導出的模塊中默認導入。
    "sourceMap": true,                          // 把 ts 文件編譯成 js 文件的時候,同時生成對應的 map 文件
    "strict": true,                             // 啓用全部嚴格類型檢查選項
    "noImplicitAny": true,                      // 在表達式和聲明上有隱含的 any類型時報錯
    "alwaysStrict": true,                       // 以嚴格模式檢查模塊,並在每一個文件里加入 'use strict'
    "declaration": true,                        // 生成相應的.d.ts文件
    "removeComments": true,                     // 刪除編譯後的全部的註釋
    "noImplicitReturns": true,                  // 不是函數的全部返回路徑都有返回值時報錯
    "importHelpers": true,                      // 從 tslib 導入輔助工具函數
    "lib": ["es6", "dom"],                      // 指定要包含在編譯中的庫文件
    "typeRoots": ["node_modules/@types"],
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": [                                  // 須要編譯的ts文件一個*表示文件匹配**表示忽略文件的深度問題
    "./src/**/*.ts"
  ],
  "exclude": [
    "node_modules",
    "dist",
    "**/*.test.ts",
  ]
}
複製代碼

而後在package.json中加入咱們的script命令:

{
  "name": "ts-study",
  "version": "1.0.0",
  "description": "",
  "main": "src/index.ts",
  "scripts": {
    "build": "tsc", // 編譯
    "build:w": "tsc -w" // 監聽文件,有變更即編譯
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "typescript ": "^3.6.4"
  }
}
複製代碼

編寫第一個 TypeScript 程序

src/index.ts中輸入如下代碼:

function greeter(person) {
    return "Hello, " + person
}

const user = "Jane User"
複製代碼

這個時候你會看到一個警告,這個警告在官方默認配置中是不會出現的,正是因爲咱們開啓了 noImplicitAny 選項,對於隱式含有 any 類型的參數或者變量進行警告⚠️.

2019-06-25-00-57-51

之因此一開始就開啓嚴格模式,是由於一旦你開始聽任any類型的泛濫,就會把 TypeScript 變成 AnyScript ,會很難改掉這個惡習,因此從一開始就要用規範的 TypeScript 編碼習慣。

咱們進行修改以下:

function greeter(person: string) {
    return "Hello, " + person
}
複製代碼

此時咱們能夠看到,greeter函數自動加上了返回值類型,這是 TypeScript 自帶的_類型推導_。

2019-06-25-01-08-12

參考學習:

深刻淺出TypeScript:從基礎知識到類型編程

相關文章
相關標籤/搜索