玩轉TypeScript-基礎


搞一搞TypeScript,做了一點筆記,奧里給,肝了兄弟們!

Part1內容

安裝typescript編譯器

全局安裝:npm install -g typescript

在命令行中查看ts編譯器版本判斷是否安裝成功。

1609407038460

TypeScript 文件默認以 .ts 為后綴,TypeScript 是 JavaScript 的擴展,所以 TypeScript 代碼要在 瀏覽器/Node 環境下運行,需要把 TypeScript 代碼編譯為 JavaScript 代碼。

ts初體驗

1609407628309

ts-node

這是一個基於Node.js的運行typescript的REPL環境,適用於typescript@>=2.7

ts-node這個插件方便我們直接執行ts文件,不用手動執行ts編譯后的js文件,很方便。

Github地址

全局安裝:npm install -g ts-node

1609408022120

tsconfig.json

當使用 tsc 並不指定 要編譯的ts文件 的情況下,會從當前運行命令所在的目錄開始逐級向上查找 tsconfig.json 文件。

tsconfig.json 文件用來配置 tsc 的編譯配置選項。

我們也可以通過 --project(-p) 來指定一個包含 tsconfig.json 文件的目錄來進行編譯。

編譯選項

{
  "compilerOptions": {
      "module": "ES2015",
      "target": "ES5",
      "outDir": "./dist"
  },
  "include": [
    "./src/**/*"
  ]
}

compilerOptions字段定義了編譯相關設置

  • module:指定編譯后的代碼要使用的模塊化系統
  • target:指定編譯后的代碼對應的ECMAScript版本
  • outDir:指定編譯后的代碼文件輸出目錄
  • outFile:將輸出文件合並成一個文件(合並的文件順序為加載和依賴順序)

include字段指定了要包含的編譯文件目錄,它的值是一個目錄數組,使用glob模式

  • *匹配0或多個字符(不包括目錄分隔符)
  • ?匹配一個任意字符(不包括目錄分隔符)
  • **/遞歸匹配任意子目錄

exclude:指定不要包含的編譯文件目錄,值也是一個目錄數組,類似include,默認會排除node_modulesoutDir指定的目錄。

1609462985685

類型系統

類型注解(類型聲明、類型約束)

JavaScript是動態語言,變量隨時可以被賦予不同類型的值,變量值的類型只有在運行時才能決定。

在編碼(編譯)階段無法確定數據類型,會給程序在實際運行中帶來極大的隱患,不利於編碼過程中的錯誤排查。

使用類型注解就能夠在變量聲明的時候確定變量存儲的值的類型,用來約束變量或參數值的類型,這樣在編碼階段就可以檢查出可能出現的問題,避免把錯誤帶到執行期間。

語法

語法:let變量:類型

當變量接收了與定義的類型不符的數據會導致編譯失敗(警告)。

1609466002083

1609466035984

類型

官方文檔:https://www.tslang.cn/docs/handbook/basic-types.html

typescript中定義的類型有:

數字、字符串、布爾值

null、undefined

數組、元組、枚舉

void、any、Never

字符串&數字類型
let a: string;
a = 'asd';
// a = 1;

let b: string = 'test';

let c: String = 'test123';    // 可以把基本數據類型賦值給對應的包裝對象類型
let d: String = new String('test456');
// let e: string = new String('test789');    // 不可以把包裝對象類型賦值給基本類型

let f: number;
f  = 123;
// f = '123';   // 報錯

string、number、boolean屬於基本類型

String、Number、Boolean:屬於對象類型

注意:

  • 包裝類型可以賦值給對應包裝對象
  • 包裝對象不可以賦值給對應基本類型
let s: String = 'some string...';		// 正確
let s: string = new String('some string...');		// 錯誤
數組

TypeScript像JavaScript一樣可以操作數組元素。 有兩種方式可以定義數組。 第一種,可以在元素類型后面接上 [],表示由此類型元素組成的一個數組:

let list: number[] = [1, 2, 3];

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

let list: Array<number> = [1, 2, 3];
/**
 * 定義數組的方式有2種,第一種可以在元素后面接上[],表示由此類型元素組成的一個數組
 * 第二種方式就是使用數組泛型,Array<元素類型>
 */
let list: number[] = [1,2,3];
console.log(list);
// console.log(list.push('4'));    // 直接報錯 不能把字符串類型的數據添加到number類型的數組中

// 數組泛型創建方式
let list2: Array<string> = ['a','b','c'];
console.log(list2);
console.log(list2.push('1,2,3','456'));
// console.log(list2.push(1,2,3));   // 直接報錯 不能把number類型的數據添加到string類型的數組中
console.log(list2);

元組

與數組類似,允許表示一個已知元素數量和類型的數組,各元素的類型不必相同,對於下標內的數據,數據順序必須與聲明中的類型一一對應。

/**
 * 與數組類似,但是可以存放多種不同類型
 */

let data1: [number, string, boolean];

// 注意:順序要對應
data1 = [1,'123',true]
聯合類型

多個類型中的一個,或的關系。

let a: string | number;
a = "m";

a = 1;
a = '1';
// a = false;   // 報錯,不能把boolean類型分配給string或者number類型
console.log(a);
枚舉類型

使用枚舉可以為一組數據賦予友好的名字,enum Color {Red, Green, Blue},默認情況下,元素編號從0開始,也可以手動編號enum Color {Red=1...}

// let gender:number = 1;    // 1:男,2:女
// if(gender==1) {   // 容易忘記1表示的是什么

// }else {}


enum Gender {Male, Female};  // enum Gender {Male=0, Female=1};

if(Gender.Male) {
  console.log("男");
}else {
  console.log("女")
}

// enum Gender {Male=1, Female};   // Female會從2開始
類型推導

有的時候不一定需要強制使用類型聲明,在某些情況下 TS 可以根據語境進行類型推導。

變量初始化的時候TS 會根據變量初始化的時候賦予的值進行類型推斷。

上下文推斷的時候TS 也會根據上下文進行類型的推斷,比如在事件函數中,函數的第一個參數會根據當前綁定的事件類型推斷處理事件對象。

/**
 * 其它類型
 */

//  let a:undefined;

//  a = undefined;

// 下面是可以的,可以把null賦值給其他類型,但是不能把其他類型的數據賦值給null
//  let a: number;

//  a = null;

//  let b: any;
//  b = false;
//  console.log(b)

// 類型推導
// let c = 1;   // ts會自動推導,c是number類型的
// c = 'm';   // 報錯


Part2內容

1609542817499

上面的代碼在頁面中沒有這個div元素的時候會報錯,因為document.querySelector('div')在頁面中沒有div的時候會返回一個null值,然后給這個null值添加顏色就會報錯,這種問題可以通過TS來解決。

注意:在js中,如果一個方法的返回值是一個對象,那么該方法在沒有返回值的情況下,接收的變量就會為null。

TS類似於ESLINT,通過定義各種類型檢測規則來約束代碼,減少隱形的錯誤。

函數

函數聲明

// 函數聲明寫法
function fn1(x: number, y: number): number {
  return x + y
}
let result = fn1(1,2);

函數表達式

// 函數表達式
let fn2 = function(x: number, y:number): number {
  return x+y;
}

完整函數類型寫法

// 完整的函數類型寫法
let fn2: (x:number, y:number) => number = function(x: number, y:number): number {
  return x+y;
}

// 根據類型推斷可以簡寫
let fn2: (x: number, y:number) => number = function(x,y) {
  return x+y;
}

可選參數寫法

// 可選參數,沒有返回值就使用viod關鍵字代替,可選參數使用?標識
function fn3(x: number, y?: number) :void {};

console.log(fn3(1));

參數默認值寫法

// 參數默認值
function fn3(x: number, y = 1): void {
  console.log(y);
}
console.log(fn3(0));

剩余參數寫法

// 剩余參數
function createName(firstName: string, ...args: string[]){
  return firstName + " " + args.join(" ");
}
let res = createName("Alex", "Bob", "Simth");
console.log(res);

函數重載

允許我們在TS中給函數傳遞多種對應類型的參數,前提是你已經定義好了參數的類型。

// function fn(x, y) {
//   return x + y;
// }

// 如果在js中,我們是可以這么寫的,因為沒有類型檢查,但是在TS中使用類型檢查后推薦使用函數重載方式實現
// console.log(fn(1, 2));  // 3
// console.log(fn('a', 'b'));    // ab

// 函數重載,允許我們在TS中給函數傳遞多種對應類型的參數
function fn(x: number, y: number): number;
function fn(x: string, y: string): string;
function fn(x: any, y: any): any {
  return x + y;
}

console.log(fn(1,2));   // 3
console.log(fn('alex', 'zhang'));   // alexzhang
// console.log(fn('haha', 123));    // 直接報錯

this

注意:在TS中能不使用any類型就不使用,因為使用any類型TS就不會對其做類型檢測了,就沒有任何意義了。

因為普通函數中的 this 具有執行期綁定的特性,所以在 ts 中的this 在有的時候會指向隱式的指向類型 - any(並不是所有,比如事件函數)。

我們可以通過 --noImplicitThis 選項來解決 this 隱式 any 類型的錯誤。

我們可以在函數參數中提供一個顯示的 this 參數,this 參數是一個假的參數,它出現在參數列表的最前面。

/**
 * ts中默認情況下函數中的this默認指向 : any
 */

let obj = {
    a: 10,
    fn() {
        // 因為默認情況下,this是any類型,any類型ts不能提示有任何屬性方法
        // let document:any;
        // any的值,ts不能提示或者進行類型屬性檢測
        // console.log(this.b);

        // 使用noImplicitThis選項可以取消默認this的any來這個設置
        // this.a
    }
}

// obj.fn();


// ts會自動推導事件函數中的this
// document.onclick = function() {
//     this
// }


let obj1 = {
    a: 1,
    fn(this: Element|Document) {    // 在ts中函數的第一個this參數是用來設置this類型約束的
        // 這個this是一個假參數,運行過程中是不存在,是給ts檢測使用的
        // console.log(this);
    }
};

document.onclick = obj1.fn;
document.body.onclick = obj1.fn;

ts會自動推導事件函數中的this!!!

注意:TS是根據類型來做代碼檢測。

與 ES2015 中的 class 類似,同時新增了很多實用特性,與 ES2015 不同,TS 中的成員屬性可以提取到構造函數以外進行定義。

修飾符

通過修飾符可以對類中成員屬性與成員方法進行訪問控制,publicprotectedprivatereadonly

參數屬性:我們可以在參數中使用修飾符,它可以同時定義並初始化一個成員屬性。

class Person {
  /**
   * ts中的類,成員屬性必須要聲明后使用
   * ts中的類的成員屬性不是在構造函數中聲明的,是在class內,方法外聲明
   * public
   *    公開的,所有的地方都能訪問,屬性和方法默認是public
   * protected
   *    受保護的,在類的內部和他的子類中才能訪問
   * private
   *    私有的,只能在該對象(類)的內部才可以訪問
   */

  public username: string = '';
  // private username: string = '';
  // protected username: string = '';

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


class Student extends Person {
  say() {
    console.log(`${this.username}:哈哈哈`);
  }
}

let p1: Person = new Person('alex');
p1.username = 'john'
console.log(p1.username);

存取器

TS 支持 getters/setters 來截取對對象成員的訪問。

第一個需求:我們不希望年齡被修改。

解決方法:直接使用private關鍵字定義age成員屬性即可,因為private定義的屬性不能在外部訪問,只能在類的內部訪問。

此時,需求變動,我們允許用戶修改年齡,但是要在合理的訪問內修改,不能隨意修改,比如只能在0到150歲之間。

解決方法:在Person內中定義getAgesetAge方法來實現,getAge方法中直接返回類中的age屬性,setAge方法接收外部傳遞進來的參數,然后修改age屬性的值達到修改的效果,哦,別忘了加一個判斷,判斷通過才修改。

class Person {
  username: string = 'alexander';
  private _age: number = 21;

  getAge(): number {
    return this._age;
  }

  setAge(age: number): void {
    if (age > 0 && age < 150) {
      this._age = age;
    }else {
      console.log("[-]驗證未通過!");
    }
  }
}

let p1: Person = new Person();

// 需求:允許在外部獲取和修改age的值,但是不希望被修改成非法值,比如1000歲

console.log(p1);
console.log(p1.getAge());
console.log(p1.setAge(88));
console.log(p1.getAge());

使用TS的存取器來實現

class Person {
  username: string = 'alexander';
  private _age: number = 21;

  // getAge(): number {
  //   return this._age;
  // }

  // setAge(age: number): void {
  //   if (age > 0 && age < 150) {
  //     this._age = age;
  //   }else {
  //     console.log("[-]驗證未通過!");
  //   }
  // }

  // 存取器,這個age並不會作為方法,而是作為屬性去訪問,類似Vue中的computed計算屬性
  get age(): number {
    return this._age;
  }

  set age(age: number) {
    if (age > 0 && age < 150) {
      this._age = age;
    }
  }
}

let p1: Person = new Person();

// 需求:允許在外部獲取和修改age的值,但是不希望被修改成非法值,比如1000歲

console.log(p1.age)   // 21
p1.age = 121;    // set age 121
console.log(p1.age);  // 121
p1.age = 123;    //set age error
console.log(p1.age);    // 123

TS官方的demo

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);
}

注意下面幾點:

  • 使用存取器的成員需要被private修飾。

  • 編譯目標為 ES5+。

  • 只有 get 的存取器自動被推斷為 readonly只讀屬性。

沒看官方文檔之前,我還傻傻地使用p1.age(121)的方式嘗試修改_age屬性的值,始終不成功,看了文檔之后發現我的寫法是錯的,應該用=號賦值的方式來寫,總結一下,與其質疑框架、工具、庫版本問題,坑多難用,不如多看官方文檔,文檔看不好,Bug少不了!:)

靜態成員

類的一般成員屬性和方法都屬於實例對象的,也就是原型鏈上的,靜態成員屬於類(也就是構造函數)的,靜態成員不需要實例化對象,直接通過類即可調用。

通過下面的demo徹底理解

// 單例模式demo

// class Mysql {
//   // 成員屬性聲明, 默認public
//   host: string;
//   port: number;
//   username: string;
//   password: string;
//   dbname: string;

//   constructor(host = '127.0.0.1', port = 3306, username='root', password='', dbname='') {
//     this.host = host;
//     this.port = port;
//     this.username = username;
//     this.password = password;
//     this.dbname = dbname;
//   }

//   // 類方法
//   query(){console.log("query data...")}
//   insert(){console.log("insert data...")}
//   update(){console.log("update data...")}
// }

// /**
//  * 創建一個Mysql對象,通過這個對象來操作數據庫
//  * 如果我們不加以限制的話,這個Mysql是可以new出來多個對象的
//  * 每一個Mysql都會占用資源(內存)
//  * 
//  * 為了解決這個問題,我們需要對創建Mysql連接做限制,如果存在則直接使用已有的連接,不存在
//  * 則創建。
//  */

//  let db = new Mysql();
//  db.query();
//  db.insert();

//  let db1 = new Mysql();
//  db1.query();
//  db1.insert();


/**
 * 通過某種方式控制系統同時只有一個Mysql的對象在工作
 */

class Mysql {
  // 靜態屬性,不需要通過new出來的對象,直接是通過Mysql類來訪問
  public static instance;
  host: string;
  port: number;
  username: string;
  password: string;
  dbname: string;
  private constructor(host = '127.0.0.1', port = 3306, username = 'root', password = '', dbname = '') {
    this.host = host;
    this.port = port;
    this.username = username;
    this.password = password;
    this.dbname = dbname;
  }
  public static getInstance() {
    if (!Mysql.instance) {
      Mysql.instance = new Mysql();
    }
    return Mysql.instance;
  }
  query() { console.log("query data...") }
  insert() { console.log("insert data...") }
  update() { console.log("update data...") }
}

// let db = new Mysql();
console.log(Mysql.instance);
let db = Mysql.getInstance();
db.query();
console.log(Mysql.instance);
db.insert();
db.update();

繼承

class Person {
  // 在構造函數的參數中如果直接使用public等修飾符,則等同於同時創建了該屬性
  constructor(public username: string, public age:number) {
    this.username = username;
    this.age = age;
  }
}


class Student extends Person {
  /**
   * 如果子類沒有重寫構造函數,則直接使用父類的
   * 如果子類重寫了構造函數,則需要手動調用父類構造函數
   * super:關鍵字,表示父類
   */
   constructor(username: string, age:number, public gender: string) {
    super(username, age);   // 執行父類構造函數
    this.gender = gender
   }
}

let s1 = new Student('alexander', 21, '男');
console.log(s1);

抽象類

類是對具有相同特性的對象的抽象,抽象類是對具有相同特性的類的抽象,當派生類(子類)具有的相同的方法但有不同實現的時候,可以定義抽象類並定義抽象方法。

抽象方法只定義結構不定義實現,擁有抽象方法的類必須是抽象類,但是抽象類不一定擁有抽象方法,抽象類中也可以包含有具體細節的方法,abstract 關鍵字可以與 修飾符一起使用,繼承了抽象類的子類必須實現了所有抽象方法才能被實例化,否則該子類也必須聲明為抽象的。

第一次接觸這個概念感覺有億點點抽象,還是直接看代碼。

abstract class Person {   // 抽象類是不能實例化的
  username: string;
  constructor(username: string) {
    this.username = username;
  }

  say() {
    console.log("哈哈哈哈哈");
  }

  /* 
    雖然子類都會有這樣的特性,學習,但是子類的學習具體過程不一樣,所以在父類確定不了study方法
    的具體實現,父類只能有抽象的約定,接收什么參數,返回什么內容。
    如果一個類中有抽象的方法了,那么這個類2也必須是抽象的。
  */
  abstract study():void // 抽象方法是沒有具體代碼的
}

class Student extends Person {
  study() {
    console.log("學生有學生的學習方法 - 需要老師教授")
  }
}

class Teacher extends Person {
  study() {
    console.log("老師的學習方法 - 自學");
  }
}

// 如果一個類繼承了抽象的父類,就必須實現所有抽象方法,否則這個子類也必須是一個抽象類
abstract class P extends Person {}

let s1 = new Student('alex');
console.log(s1.say());
console.log(s1.study());

看完代碼就理解了上面的話了。

Part3內容

接口(interface)

Hello,Interface

接口為我們提供一種方式來定義某種結構,ts按照這種結構來檢測數據。

下面看一個基礎的例子。

/**
 * interface
 *  為我們提供一種方式來定義某種結構,ts按照這種結構來檢測數據
 * 
 *  寫法:
 *      interface 接口名稱 {
 *        // ...接口規則
 *      }
 * 
 * 
 *  接口中定義的規則只有抽象描述,不能有具體的值或實現代碼
 * 
 *  對象抽象 => 類  (把對象相似的部分提取出來通過這個類去描述對象)
 *  類抽象 => 抽象類  (如果一個類中有一個抽象方法沒有實現,那么這個類就是抽象類)
 *  抽象類 => 接口    (如果一個抽象類中的所有成員都是抽象的,這個類就是接口)
 */

// 定義一個名為Options接口,可以把接口看成是一個對象,但是不完全是,有些細節不一樣。
interface Options {
  // width: number = 1,   // 接口中的代碼不能有值
  width: number,
  height: number
}

function fn(opts: Options) {
  console.log(opts);
}

// fn();   // 報錯,沒有傳入參數
// fn({});   // 傳入的參數類型不對,ts會按照接口中定義的數據去檢測
// fn({width: 300});   // 缺少height屬性

// 細節:類型檢測只檢測必須的屬性是否存在,不會按照順序進行檢測,是無序的
fn({ width: 200, height: 150 });    // 正確

ts中的interface接口是用來定義規則的,這個規則是給ts用來做數據檢測的,上面的fn函數就是用了Options接口的規則來做數據檢驗,規則中定義了number類型的width屬性和number類型的height屬性,給fn函數傳參的時候就得按照這種規則傳,否則就報錯。

總結:

  1. ts中的接口是用來做類型檢驗的,必須嚴格按照接口中定義的規則來實現
  2. ts的類型檢測只檢測必須的屬性是否存在,不會按照順序進行檢測,是無序的

可選參數和只讀屬性

/**
 * 如果規則中有些是可選的,那么通過 ? 標識。
 * 只讀屬性通過 readonly 關鍵字標識
 */

interface Options {
  width: number,
  height: number,
  color?: string,
  readonly opcity: number
}

function fn(params: Options) {
  console.log(params);
  // params.opcity = 1.2    // 報錯,只讀屬性不能重新賦值
}

fn({
  width: 200,
  height: 250,
  opcity: 0.5
})

總結:

  1. 可選參數通過?標識
  2. 只讀屬性通過readonly關鍵詞標識

檢測約束

/* 
  如果我們希望檢測不要這么復雜
      -如果我們希望某些時候,只要包含其中一些規則即可
        - 通過可選參數 ? 方式實現
        - 通過 as 斷言   可以傳少,不能傳多
        - 通過變量轉換   可以傳多,不能傳少
*/

interface Options {
  width: number,
  height: number,
  color: string
}


function fn(params: Options) {
  console.log(params);
}


// fn({
//   width: 200,
//   height: 300
// } as Options);    // 明確告訴ts我傳入的就是Options,讓ts繞開檢測

// 先賦值給一個變量,也可以繞開規則檢測,原因是ts沒有對obj這個變量做類型檢測,但是這種方式只能傳多不能傳少
let obj = {
  height: 200,
  width: 100,
  color: 'red',
  a: 1,
  b: 2,
  c: 3
}

fn(obj);

索引簽名

/* 
  希望規則是:一組由數字進行key命名的對象,比如下標為0,1,2,3,4這樣的
  我們可以使用索引簽名
    為數據定義一組具有某種特性的key的數據

  索引key的類型只能是 number和string兩種
*/

// 需求類似下面這種結構
// interface Options {
//   0: string,
//   1: string,
//   2: string
// }


// 定義了一組規則,key是number類型,使用中括號包裹[變量名: 類型], value是any任意類型
interface Options {
  // key是number,value是any類型的
  // [attr: number]: any,

  // key是string的話,會同時支持string和number類型
  [attr: string]: any,
  length: number
}

function fn(params: Options) {
  console.log(params)
}

// 這種結構類似調用document.querySelectorAll('div')返回的NodeList結構
fn({
  0: 100,
  a: 'haha',
  length: 1
})

函數類型接口

通過接口的形式來定義函數。

一個簡單的函數類型接口定義。

/* 
  這個接口描述的是一個包含fn,並且值的類型為函數的結構體,並不是描述函數結構;  
  注意描述的是一個包含函數的對象結構
*/

interface Options {
  fn: Function
}

let o: Options = {
  fn: function () {console.log("I Am Function...")}
}
o.fn();   // I Am Function...

注意:我們不能把一個函數隨便賦值給事件,因為事件回調函數的參數類型是Event,這是JS規定的。

// 完整描述函數結構寫法
// let fn: (x: number, y: number) => number = function (x: number, y: number): number {
//   return x + y
// }

/* 
  定義一個事件函數,那么這個函數必須得有一定的規則;
  我們不能隨隨便便的把一個函數賦值給事件
*/

// document.onclick = fn;    // 報錯,因為fn函數定義的參數類型是number,而事件觸發后的事件對象是Event類型

function fn(x: Event) {
  
}
document.onclick = fn;    // 正確寫法

再來看一個函數類型接口的例子。

// 我們可以使用 interface 來約定函數的結構
// 定義的是函數類型接口,定義了一個x,y參數和返回值都為number類型的函數,根據這種規則進行檢測
interface IFn {
  (x: number, y: number): number
}

let fn: IFn = function(x: number, y: number): number {return x + y};

// 定義了一個接收一個MouseEvent類型參數的函數結構
// 其實函數接口就是定義一種函數規則,然后后面復用這種規則
interface MouseEventCallBack {
  (e: MouseEvent): any
}

let fn: MouseEventCallBack = function(a: MouseEvent){

}

document.onclick = fn

定義一個名為MouseEventCallBack的接口,接收的參數名為e,類型是MouseEvent類型,返回值是any。

fn函數按照MouseEventCallBack接口定義的規則做檢測,函數中參數a是一個MouseEvent類型,因此ts檢測通過,可以將fn函數賦值給事件。

再來舉一個例子。

/* 
  定義一個ResponseCallBack函數接口
    函數的第一個參數名為res,Response類型,返回值是any類型的
*/
interface ResponseCallBack {
  (res: Response): any
}

function todo(callback: ResponseCallBack) {
  callback(new Response)
}


todo(function(res: Response){

})

函數todo接收一個callback參數作為回調函數並且按照ResponseCallBack函數接口做規則檢測,檢測的規則是接收一個名為res的參數,類型是Response類型,返回值是any類型,調用todo函數,傳入一個res參數名,類型為Response,然后調用callback並傳入一個Response對象。

部分代碼需要拿到瀏覽器環境跑,直接用node跑可能會報錯,例如這個Response。

再來看一個fetch的例子。

// fetch返回的是一個Promise對象,then方法成功回調函數的參數是一個Response類型對象
fetch('url').then( (a: string) => {
  a.indexOf('');    // ts會檢測到a不是一個string類型的,根本沒有indexOf方法,這時就會報錯
})

fetch('url').then((a: Response) => {
  // a.indexOf('');    // Response類型沒有這個indexOf方法,這里就會報錯
  return a.json();  // Response對象是有json方法的,所以檢測通過,可以參考MDN文檔的Response對象
})

不要嘗試去欺騙ts編譯器,沒有意義,ts並不會按照你寫的a:string就將a按照string類型進行檢測,因為then方法的回調函數參數就是一個Response對象,這是JS的規則。

再來看一個ajax的例子。

// 再看一個ajax的例子
interface AjaxData {
  code: number,
  data: any
}

interface AjaxCallBack {
  (res: AjaxData): any
}

// ajax函數的參數是一個callback回調函數,類型是AjaxCallBack,AjaxCallBack的參數類型是AjaxData類型
function ajax(callback: AjaxCallBack){
  callback({
    code: 1,
    data: [
      {goods_name: 'IKBC C87紅軸', goods_id: 1, goods_price: 299.99, goods_color: 'Black'},
      {goods_name: 'IKBC C87青軸', goods_id: 2, goods_price: 289.99, goods_color: 'Green'},
      {goods_name: 'IKBC C87黑軸', goods_id: 3, goods_price: 279.99, goods_color: 'White'},
    ],
    // message: '123'   // AjaxData沒有定義message屬性所以為報錯
  })


  // 可以通過之前學習到的變量賦值的方式繞過檢測
  // let obj = {
  //   code: 1,
  //   data: [],
  //   message: '123'
  // }

  // callback(obj)
}

ajax(function(x: AjaxData){
  console.log(x);
})

// 總結:函數接口其實就是定義某種函數結構,接收的參數是什么類型,返回值是什么類型,做嚴格約束
// typescript倒逼我們學習了更多javascript語言本身的東西,例如各種規則

總結:函數接口其實就是定義某種函數結構,接收的參數是什么類型,返回值是什么類型,做嚴格約束。

第一次看這個的時候一臉懵逼=>萬臉懵逼=>遞歸懵逼,只能怪自己太蠢,后面靜下心來反復看,反復理解,反復寫就明白了,slow is fast

類類型接口

/**
 * 類接口
 *      使用接口讓某個類去符合某種契約
 * 
 * 類可以通過 implements 關鍵字去實現某個接口
 *      - implements 某個接口的類必須實現這個接口中確定所有的內容
 *      - 一個類只能有一個父類,但是可以implements多個接口,多個接口使用逗號分隔
 */

interface ISuper {
    fly(): void;
}

class Man {

    constructor(public name: string) {
    }

}

class SuperMan extends Man implements ISuper {

    fly() {
        console.log('起飛');
    }

}

class Cat {

}

class SuperCat extends Cat implements ISuper {
    fly() {
        console.log('起飛');
    }
}

let kimoo = new SuperMan('Kimoo');
// kimoo

封裝http案例

js版本

function http(params) {
  return new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.open(params.method, params.url, params.isAsync);
    xhr.onload = function () {
      resolve(JSON.parse(xhr.responseText));
    }
    xhr.onerror = function () {
      reject({
        code: xhr.response.code,
        message: '出錯了!'
      })
    }
    xhr.send();
  })
}


// 期待的使用方式
// http('url').then( data => {} )

http({
  method: 'get',
  url: 'http://www.baidu.com/',
  isAsync: true
})

/* 
  問題:如果將method改成methods,因為沒有類型檢測,你很難在代碼里面看出來
  必須在運行之后才能看出來。
  這個時候如果使用ts來實現就會好很多。
*/

ts版本

interface HttpOptions {
  method: string,
  url: string,
  isAsync: true
}

interface HttpResponseData {
  code: number,
  data: any
}

function http(options: HttpOptions) {
  // 默認值處理
  let params: HttpOptions = Object.assign({
    method: 'get',
    url: '',
    isAsync: true
  }, options)
  return new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.open(params.method, params.url, params.isAsync);
    xhr.onload = function () {
      let data: HttpResponseData = JSON.parse(xhr.responseText);
      resolve(data);
    }
    xhr.onerror = function () {
      reject({
        code: 0,
        data: []
      })
    }
    xhr.send();
  })
}


// 期待的使用方式
// http('url').then( data => {} )

http({
  method: 'get',
  // methods: 'get',   // 報錯,因為HttpOptions接口中沒有定義methods屬性,ts會幫助我們在程序運行之前檢測代碼是否錯誤
  url: 'https://www.baidu.com/',
  isAsync: true
}).then( res => {
  console.log(res);
})

總結

安裝TS:npm install -g typescript

安裝ts-node編譯器:npm install -g ts-node

tsconfig.json作用是什么?

是用來對typescript編譯器進行配置的,比如配置編譯模式、模塊化系統、輸出目錄、編譯文件目錄、包含文件的層級設置、空值檢查、取消this默認指向any等等。。。

類型系統,常用有哪些類型?

格式:let a: 類型 = 123

  • string
  • number
  • boolean
  • array
  • tuple(元組)
  • enum

聯合類型:可以使用定義好的類型中任意一種類型進行賦值

類型推導:typescript編譯器會根據初始化變量或上下文來做類型推導,例如一個函數的2個參數都是定義為number類型的,那么返回值根據類型推導也是number類型,此時可以省略不寫。

函數創建方式:

  • 函數聲明 function fn1(x: number, y: number): number {return x+y};
  • 函數表達式 let fn2 = function(x: number, y: number):number {return x+y};

完整函數寫法:

let fn2: (x:number, y:number) => number = function(x:number, y:number):number{return x+y};

根據類型推斷簡寫:

let fn2: (x:number, y:number) => number = function(x,y){return x+y};

可選參數寫法:使用?標識,如果沒有返回值使用void代替

function fn3(x: number, y?:number):void{console.log(y)}

參數默認值寫法:和javascript一樣使用=號

function fn3(x: number, y=1): void {console.log(y)}

剩余參數寫法:

function fn3(x: number, ...args: string[]){ return x + " " + args.join(" "); }

函數重載:允許我們在TS中給函數傳遞多種對應類型的參數,前提是你已經定義好了參數的類型。

function fn(x: number, y: number);

function fn(x: string, y: string);

function fn(x: any, y: any): any {return x + y}

TS函數中的this:

在ts中,函數默認的this指向any(並不是所有,比如事件函數除外),這會導致ts的類型系統不會檢測this,因為ts不會檢測any類型,所以我們需要配置noImplicitThis:true來解決this默認指向any的問題。

在ts中函數的第一個this參數是用來設置this類型約束的,約定this指向的是什么類型。

類是對有相同特性對象的抽象。

修飾符

作用是為了對成員屬性和方法進行訪問控制

  • public:ts中類的默認修飾符,允許類中的屬性和方法公開訪問
  • private:私有修飾符,只允許屬性在類的內部進行訪問
  • protected:受保護修飾符,只允許屬性在類的內部和它的子類中訪問
  • readonly:將屬性設置為只讀的, 只讀屬性必須在聲明時或構造函數里被初始化

存取器:

截取對對象成員的訪問,get/set,注意使用get/set定義的方法在使用的時候不需要加括號,當作屬性使用即可,參考修改年齡代碼。

靜態成員:

不需要通過實例化的對象,直接通過類進行調用,適用於同時只能有一個對象在工作的情況,參考單例模式的Mysql連接代碼。

繼承:

類似ES6,使用extends關鍵字進行繼承,如果子類沒有重寫構造函數,則直接使用父類的,如果重寫了構造函數,則需要手動調用父類構造函數,使用super()關鍵字調用。

抽象類:

抽象類是對具有相同特性的類的抽象,當子類具有的相同的方法但有不同實現的時候,可以定義抽象類並定義抽象方法;

參考學生學習方法和老師學習方法的代碼。

源碼在這里


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM