TypeScript (基礎)

kear發表於2019-04-16

TypeScript 是 JavaScript 的型別的超集,它可以編譯成純 JavaScript。編譯出來的 JavaScript 可以執行在任何瀏覽器上。TypeScript 編譯工具可以執行在任何伺服器和任何系統上。TypeScript 是開源的。

以下內容均出自於 TS入門教程

以及 Ts 官網的一些內容,沒有基礎的小夥伴直接看打了⭐️的內容即可。

嘗試

看了之後怎麼搭個環境寫一寫?

mkdir demo
cd demo
touch 1.ts
複製程式碼

開啟vscode,開啟控制檯,切換到問題 tab

歐了,開始嘗試 ts 吧

⭐️基礎型別

└boolean

let isDone: boolean = false;
// 使用建構函式 Boolean 創造的物件不是布林值
複製程式碼

└null & undefined

是所有型別的子型別

void型別不能賦值給 number

let u: undefined = undefined;
let n: null = null;
let num: number = undefined;
let u: undefined;
let num: number = u;
複製程式碼

└void 型別

一般表示函式沒有返回值。用在變數上沒有什麼卵用。

function warnUser(): void {
    console.log("This is my warning message");
}
let a: void = undefined
let a: void = 'undefined' // 報錯,這是字串
複製程式碼

跟它相似的型別還有undefinednull 在不開啟嚴格空檢查的情況下 --strictNullChecks,他們可以賦值給所有已經定義過***其他型別***的變數。 也就是說他們是所有型別的子型別

let a: undefined = undefined
let a: null = null
複製程式碼

└數字 number

TypeScript裡的所有數字都是浮點數。 這些浮點數的型別是 number。支援十進位制和十六進位制字面量二進位制和八進位制字面量。

let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
// ES6 中的二進位制表示法
let binaryLiteral: number = 0b1010;
// ES6 中的八進位制表示法
let octalLiteral: number = 0o744;
let notANumber: number = NaN;
let infinityNumber: number = Infinity;
複製程式碼

└字串 string

單雙引'' "",模板字元的都被視為字串

let str:string = ''
複製程式碼

⭐️陣列型別

有多種宣告陣列的方式

  • 最簡單的方法是使用型別 + []來表示陣列:
const arr: number[] = [1,2,3]
const arr2: string[] = ['1','2']
複製程式碼
  • 陣列泛型定義方式
const arr2: Array<number> = [1,2,3,3]
const arr2: Array<string> = [1,2,3,3]
複製程式碼
  • 介面表示陣列
interface NumArr {
    [index: number]: number;
}
let numArr: NumArr = [1,2,3];
複製程式碼
  • any 型別陣列
let list:any[] = [1,"z",{}]
複製程式碼
  • 元組型別宣告
// 表示一個確定陣列長度和型別的寫法
const arr:[string,number] = ['2',3]
複製程式碼
  • 類陣列

就是偽陣列的定義

官方已給了各自的定義介面 Arguments, NodeList, HTMLCollection

function sum() {
    let args: IArguments = arguments;
}
複製程式碼

列舉 enum

js中沒有這型別,仿照強型別語言來的。值只能為數字,不定義預設值得情況為從0開始。

enum Color {Red, Green, Blue}
let c: Color = Color.Green;
// c = 1

enum Number {one = 10, two}
let c: Number = Number.two;
// c = 11
複製程式碼

⭐️any 型別(任意值)

指代所有的型別

let a: any = '123'
let a = 123; // 不宣告預設 any
複製程式碼

never

表示永遠不存在的值,一般會用來寫丟擲異常或推斷為返回值為never的函式。(比如return一個其他的never型別)

function error(message: string): never {
    throw new Error(message);
}
error('a')
複製程式碼

object 型別

非簡單型別 也就是除number,string,boolean,symbol,null或undefined之外的型別。

function create(o: object | null): void{
  console.log(o);
};
create({ prop: 0 }); // OK
create(null); // OK
create([]); // OK
create('a'); // error
複製程式碼

⭐️interface 介面

在 TypeScript 中,我們使用介面(Interfaces)來定義物件的型別。

物件的描述

介面一般首字母大寫。

賦值的時候,變數必須和介面保持一致

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

let tom: Person = {
    name: 'Tom',
    age: 25
};
複製程式碼

└可選屬性

不想完全匹配某個介面,通過?表示這個屬性是可選的

仍然不允許新增未定義的屬性

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

let tom: Person = {
    name: 'Tom'
};
複製程式碼

└任意屬性

讓介面允許新增任意的屬性值

[propName: string]: any;

interface Person {
    name: string;
    age?: number;
    [propName: string]: any;
}

let tom: Person = {
    name: 'Tom',
    gender: 'male'
};
複製程式碼

一旦定義了任意屬性, 那麼確定屬性和?可選屬性都必須是任意屬性的子集

interface Person {
    name: string;
    age?: number;
    [propName: string]: string;
}

let p:Person = {
    name: 'zzc',
    age: 12, // error , 定義了 propName 必須將值設定為 string 型別
    gender: 'male' ,
}
複製程式碼

└只讀屬性 readonly

相當於是常量了,初次賦值後不能重新賦值 做為變數使用的話用 const,若做為屬性則使用readonly

interface demo {
  readonly a: string; // readonly定以後不能改值
  b: number
}
let obj: demo = {
  a: 'ss',
  b: 1
}
obj.a = 'aa' // error
obj.b = 2 // success
複製程式碼

只讀的約束存在於第一次給物件賦值的時候,而不是第一次給只讀屬性賦值的時候

interface Person {
    readonly id: number;
}
const tom: Person = {} // error
tom.id = 1 // error,
複製程式碼

會報兩次錯,第一個是因為指定了 id,但沒有給 id 賦值

第二個錯是給只讀屬性id賦值了

└ReadonlyArray

通過ReadonlyArray定義的陣列,再也無法改變了。

let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = [1,2,3];
a[0] = 10 // success
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // 注意! 將readonly的值賦值給一個可變得陣列也是不行的。
a = ro as Array<any> // 但是可以用斷言重寫
複製程式碼

⭐️函式型別

常見的函式宣告方式有: 函式宣告 & 函式表示式

用 ts 定義函式要考慮它的輸入和輸出

  • 函式宣告方式定義
function sum(a:number,b:number):number{
    return a+b
}
// 形參和實引數量要一致
sum(1) // error
sum(1,2) //3
sum(1,2,3) // error
複製程式碼
  • 函式表示式定義
// 方式 1
let sum = function(a:number,b:number):number {
    return a + b;
}
// 方式二
let sum: (x: number, y: number) => number = function (x: number, y: number): number {
    return x + y;
};
複製程式碼

方式一中只對等號右側的匿名函式定義了型別,左邊是ts通過型別推論定義出來的

方式二才是給 sum 定義型別,**其中的 => 不是 es6的 => ** ,它用來表示函式的定義,左邊是輸入型別,需要用括號括起來,右邊是輸出型別。

因為和 es6箭頭函式可能造成混淆,最好用方式一;

└可選引數

通過?給函式定義可選引數

可選引數後面不允許再出現必須引數了

如果給引數新增了預設值,ts 會自動識別為可選,且不受上一條規則的限制。

function sum(a:number,b?:number){}
function sum(a?:number,b:number){} // error
function sum(a:number = 1,b:number){} // 預設值,識別為可選,且不報錯
複製程式碼

└...rest

使用…rest獲取剩餘引數,使用陣列型別去定義它

剩餘引數必須是函式的最後一個引數

function (a, ...items:any[]){}
function (...items:any[], a){} // error
複製程式碼

└函式的過載

過載允許一個函式接受不同數量或型別的引數時,作出不同的處理。

可以重複定義一個函式的型別

function say(somthing:string):string;
function say(somthing:number):string;
// 以上是函式定義

// 以下是函式實現
function say(somthing:string|number):string|number {
    return somthing
}
複製程式碼

注意,TypeScript 會優先從最前面的函式定義開始匹配,所以多個函式定義如果有包含關係,需要優先把精確的定義寫在前面。

⭐️型別斷言

型別斷言(Type Assertion)可以用來手動指定一個值的型別。

斷定這個變數的型別是啥

型別斷言不是型別轉換

兩種寫法

<型別>值 or 值 as 型別

如果在 tsx 語法中使用,必須用 as

  • 例子

聯合型別可以指定一個變數為多種型別,此變數只能訪問型別們的共有方法。

但一些情況下我們必須使用某一型別的方法或屬性時,就可以用斷言

function say(something:number|string):void{
    alert(something.length) // 聯合型別,報錯
}
// ==> 使用斷言, 在變數前加上 <型別>
function say(something:number|string):void{
    alert( (<string>something).length ) // success
}
複製程式碼

斷言成一個聯合型別中不存在的型別是不允許的

function say(something:number|string):void{
    alert(<boolean>something.length) // 聯合型別沒有 boolean ,error
}
複製程式碼

⭐️declare 宣告

第三方庫會暴露出一個變數,讓我們在專案中直接使用。

但是 ts 編譯時不知道這是啥,編譯無法通過。

此時我們就要用 declare var 宣告語句來定義他的型別

 // 比如 jquery
$('div') // ERROR: Cannot find name 'jQuery'.

// ==> 使用 declare var 第三方庫變數: (引數: string) => 返回型別
declare var $: (selector: string) => any;

$('#foo'); // success
複製程式碼

declare var 並不是真正的宣告一個變數,編譯完會刪除,僅僅是定義型別。

└宣告檔案

通常我們會把宣告語句放到一個單獨的檔案(*.d.ts)中,這就是宣告檔案

宣告檔案必需以 .d.ts 為字尾

假如仍然無法解析,那麼可以檢查下 tsconfig.json 中的 filesincludeexclude 配置,確保其包含了 jQuery.d.ts 檔案。

// src/jQuery.d.ts

declare var jQuery: (selector: string) => any;
複製程式碼

這只是非模組化專案中使用的例子

└第三方宣告檔案

當然,jQuery 的宣告檔案不需要我們定義了,社群已經幫我們定義好了:jQuery in DefinitelyTyped

我們可以直接下載下來使用,但是更推薦的是使用 @types 統一管理第三方庫的宣告檔案。

@types 的使用方式很簡單,直接用 npm 安裝對應的宣告模組即可,以 jQuery 舉例:

npm i @types/jquery -D
複製程式碼

可以在這個頁面搜尋你需要的宣告檔案。

└自定義宣告檔案

宣告檔案有以下方法

  • 全域性變數:通過 <script> 標籤引入第三方庫,注入全域性變數
  • npm 包:通過 import foo from 'foo' 匯入,符合 ES6 模組規範
  • UMD 庫:既可以通過 <script> 標籤引入,又可以通過 import 匯入
  • 模組外掛:通過 import 匯入後,可以改變另一個模組的結構
  • 直接擴充套件全域性變數:通過 <script> 標籤引入後,改變一個全域性變數的結構。比如為 String.prototype 新增了一個方法
  • 通過匯入擴充套件全域性變數:通過 import 匯入後,可以改變一個全域性變數的結構

這裡只記錄 npm 匯入的方法, 其他請看 書寫宣告檔案

在給一個第三方庫寫宣告檔案之前,先檢視這個庫有沒有宣告檔案。一般來說,npm 包的宣告檔案可能存在於兩個地方:

  1. 跟 npm 包綁在一起, package.json 中有type 欄位,或者有 index.d.ts 宣告檔案。一般常用的包都有了,自己要釋出 npm 包的時候最好也繫結在一起。
  2. 釋出到了@types](microsoft.github.io/TypeSearch/) 裡,一般這種情況是作者沒寫,其他人寫了發上去的。npm install @types/foo --save-dev 直接通過安裝。

如果都沒有,才自己寫。

宣告檔案存放的位置是有約束的,一般在兩個位置。

  1. node_modules 建立第三方庫的宣告檔案,但這種一般不採納。一般 node_modules 不會隨我們的應用釋出到伺服器|git上。
  2. 建立一個types 目錄來寫,要配合tsconfig.json 來使用。
# 專案結構
├── README.md
├── src
|  └── index.ts
├── types
|  └── foo
|     └── index.d.ts
└── tsconfig.json
複製程式碼
  • 配置
{
    "compilerOptions": {
        "module": "commonjs",
        "baseUrl": "./",
        "paths": {
            "*" : ["types/*"]
        }
    }
}
複製程式碼

不管採用了以上兩種方式中的哪一種,我都強烈建議大家將書寫好的宣告檔案(通過給原作者發 pr,或者直接提交到 @types 裡)釋出到開源社群中,享受了這麼多社群的優秀的資源,就應該在力所能及的時候給出一些回饋。只有所有人都參與進來,才能讓 ts 社群更加繁榮。

  • export

npm 包寫的宣告檔案 declare 不會宣告一個全域性變數,只有匯出的時候才會應用型別宣告。

export const name: string; // 簡單型別
export function getName(): string; // 函式
export class Animal { // class 宣告
    constructor(name: string);
    sayHi(): string;
}

export interface Options { // 介面
    data: any;
}

// ===> 對應使用到專案中
import { name, getName, Animal, Directions, Options } from 'foo';
let myName = getName();
let cat = new Animal('Tom');
let options: Options = {
    data: {
        name: 'foo'
    }
}
複製程式碼
  • 混用 declare export

通過 declare 定義多個變數,一次性匯出

declare const name: string;
declare function getName(): string;
declare class Animal {
    constructor(name: string);
    sayHi(): string;
}

export {
	name,
    getName,
    Animal,
}
複製程式碼
  • 匯出預設值

只有 functionclassinterface 可以直接預設匯出,其他的變數需要先定義出來,再預設匯出

針對預設匯出,一般會把匯出語句煩惱歌在宣告檔案的最前面。

export default function foo(): string;
export default interface Options {
    data: any
}
export default class Person {
    constructor(name: string);
    sayHi(): string;
}
declare const str:string;
export default str;
複製程式碼
  • export namespace

namespace 本來是 TS 的模組化方案,隨著 es6越來越屌基本已經不在 ts 中使用了。

但是宣告檔案中還是很常用的,表示變數是一個包含了子屬性的物件型別。

像是lodash ,它是個物件,但提供了很多子屬性方法如 lodash.debunce

如果物件擁有深層的層級,則需要用巢狀的 namespace 來宣告深層的屬性的型別:

總的來說,用來匯出一個擁有子屬性的物件。

export namespace obj {
    const name: string;
    function fn(a:string,b?:nnumber):void;
    class Event {
        say(str:string):void
    };
    // 如果還有包含子屬性的物件,就巢狀
    namespace sonObj {
        const foo: string;
    }
}
複製程式碼

└宣告合併

當一個變數,既是函式又是物件。可以組合多個語句宣告。

export function objAndFn(foo:string):any;
export namespace objAndFn {
    function fn(boo:number):void;
    const name:string;
}
複製程式碼

└針對 commonjs 規範

用以下方式來匯出:

// 整體匯出
module.exports = foo;
// 單個匯出
exports.bar = bar;
複製程式碼

在 ts 中,針對這種匯出,有多種方式可以匯入,第一種方式是 const ... = require

// 整體匯入
const foo = require('foo');
// 單個匯入
const bar = require('foo').bar;
複製程式碼

第二種方式是 import ... from,注意針對整體匯出,需要使用 import * as 來匯入:

// 整體匯入
import * as foo from 'foo';
// 單個匯入
import { bar } from 'foo';
複製程式碼

第三種方式是 import ... require,這也是 ts 官方推薦的方式:

// 整體匯入
import foo = require('foo');
// 單個匯入
import bar = require('foo').bar;
複製程式碼

對於 commonjs 規範的庫,需要使用 export = 變數 的語法寫宣告檔案

準確地講,export = 不僅可以用在宣告檔案中,也可以用在普通的 ts 檔案中。實際上,import ... requireexport = 都是 ts 為了相容 AMD 規範和 commonjs 規範而創立的新語法

export = foo;
declare function foo():string;
declare namespace foo {
    const bar: nnumber;
}
複製程式碼

⭐️內建物件

TS定義了 js 中內建物件的型別,在 TypeScript 核心庫的定義檔案中。

包括 ECMAscript、DOM、Bom 等

這些內建物件的型別在.ts 中都可以直接使用

let b: Boolean = new Boolean(1);
let e: Error = new Error('Error occurred');
let d: Date = new Date();
let r: RegExp = /[a-z]/;
let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', function(e: MouseEvent) {
  // Do something
});
複製程式碼

還會在該內建物件中定位錯誤

Math.pow(10, '2'); // error 必須是兩個 number 型
document.addEventListener('click', function(e) {
    console.log(e.targetCurrent);
    // error,  MouseEvent 型別不存在 targetCurrent屬性
});

複製程式碼

內建物件不包含 Node ,如果要使用

npm install @types/node --save-dev
複製程式碼

這章是介紹物件導向程式設計

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
        console.log(this.greeting); // world
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}
let greeter = new Greeter("world");
複製程式碼

繼承

基本繼承 作為基類的類一般被叫做超類,繼承的類叫做派生類

class Car {
  name: string = '大眾';
  by() {
    console.log(this.name);
  }
}
class Baoma extends Car {
  name: string = '寶馬'
}
let bm = new Baoma()
bm.by()
複製程式碼

在子類constructor建構函式中***必須呼叫一下super***,它會執行基類的建構函式 在子類constructor建構函式呼叫this之前必須先呼叫super

class FClass {
  name: string = '超類'
  constructor(name: string) { this.name = name; }
  setAge(age:number) {
    console.log(`${this.name} is ${age}`);
  }
}
class SClass extends FClass {
  constructor(name:string) {
    super(name)
  }
}
let f = new SClass('zzc')
f.setAge(20)
複製程式碼

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

public可以被除自己之外的所以子類訪問到 ts中所有成員預設為public 當一個成員被標記成private 時,無法被外部訪問 如果類的constructor宣告成了private那就沒辦法new 和繼承了。 protected 受保護的,跟private基本一樣,但它在子類中可以訪問;

class FClass {
  name: string = '超類'
  private constructor(name: string) { this.name = name; }
  setAge(age:number) {
    console.log(`${this.name} is ${age}`);
  }
}
let f = new FClass() // error
class Son extends FClass {} // error
複製程式碼

protected 受保護的例子

class F {
  protected name: string;
  constructor(name: string) { this.name = name; }
  setAge(age:number) {
    console.log(`${this.name} is ${age}`);
  }
}
class Son extends F {
  constructor() {
    super('son')
  }
  getName() {
    super.name // 可以在子類訪問
  }
}

let f = new F('super class')
let s = new Son()

console.log(s.name); // 不能在外部訪問
console.log(s.getName()); // 但可以通過子類的方法return出來獲取到
複製程式碼

在類中使用readonly

在類中或者constructor都還可以更改readonly的值,但在外部就無法更改了。

class F {
  readonly a:number = 8
  constructor(age:number) {
    this.a = age
  }
}
let f = new F(9)
f.a = 10 // error 無法在外部更改

複製程式碼
  • 引數屬性

constructor用一句話定義並賦值一個屬性 只要在引數前面加了訪問限定符就可以直接給一個屬性直接賦值 readonly protected public private static 是私有的,所以不能加。

class F {
  readonly a:number = 8
  constructor(readonly b:number) {
    b = 10
  }
}
let f = new F(9)

console.log(f); // {a,b}
複製程式碼

存取器 getter/setters

當一個屬性只有get方法的時候,它就是隻讀的。 這也是一種外部改變靜態屬性的方法

// 當a = 'a' 時,內部的_a才會等於賦的值,否則報錯。
class F {
  private _a:string;
  get a():string {
    return this._a
  }
  set a(newA:string) {
    if(newA === 'a') {
      this._a = newA
    }
    else {
      this._a = 'error'
    }
  }
}
let f = new F()

f.a = 'b'
console.log(f);
複製程式碼

靜態屬性 static

它只掛在class本身,而不是通過new例項化後出來的物件 所以你可以通過 類.static屬性 來呼叫,但不能用this

class F {
    static num: number;
    changeStatic() {
      F.num = 19;
    }
    constructor () {
      this.changeStatic()
      console.log(F.num);
    }
}
let f = new F();
複製程式碼

抽象類 & abstract

abstract 用來定義抽象類 和 在抽象類中定義抽象方法的 抽象類就是派生類的一個模板類,一般不會把它例項化,只是給子類繼承用的。

abstract class Animal { // 抽象一個Animal類
    abstract makeSound(): void;  // 抽象一個方法,必須在子類實現它
    move(): void {
        console.log('roaming the earch...');
    }
    constructor () {

    }
}
class Son extends Animal {
  constructor() {
    super()
  }
  makeSound() { // 必須實現抽象類中的方法
    return false
  }
  haha() {
    console.log('error');
    
  }
}

let s:Animal // 可以指定抽象類為一個型別
s.haha() // 如果上面的宣告瞭,那麼呼叫抽象類中不存在的haha方法是不允許

s = new Animal() // 不可以new 抽象類
s = new Son()  //  正確
s.makeSound() // 正確
複製程式碼

⭐型別推論

如果沒有明確的指定型別,那麼 TypeScript 會依照型別推論(Type Inference)的規則推斷出一個型別。

let myFavoriteNumber = 'seven';
myFavoriteNumber = 7; // error

// 等價於 ==>
let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7;
複製程式碼

如果定義的時候沒有賦值,不管之後有沒有賦值,都會被推斷成 any 型別而完全不被型別檢查

let myFavoriteNumber;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
複製程式碼

⭐️聯合型別

表示變數可以是多種型別其中的一種

通過 | 分隔

let numOrStr : string | number;
numOrStr = 7;
numOrStr = '7';
numOrStr = true; // false
複製程式碼

當呼叫聯合型別的方法時,只能呼叫倆型別中共有的方法。

let numOrStr : string | number;
numOrStr.length // 報錯  length 不是 number 的方法
numOrStr.toString() // 可以
複製程式碼

相關文章