前言

聊聊為何要學習TypeScript?

  1. 從開發角度來講, TypeScript 作為強型別語言,對屬性有型別約束。在日常開發中少了減少了不必要的因引數型別造成的BUG,當你在使用同事封裝好的函式時,型別不知道怎麼傳遞,在開發TS 時,它會有很友好型別的提示,此時你就知道該函式需要傳遞哪些引數,並且引數型別是什麼型別。
  2. 從專案結構來講, 使用TypeScript 可以很好的管控專案,通過建立各個模組的型別管理,通過interface 或者 型別斷言 來管理專案中型別,在對應的模組使用型別,當專案需要迭代更新時,只需要進行對對應的模組型別進行更改。
  3. 從前端全域性觀來講,社群三大框架早已結合TypeScript 了, 社群也很完善,已經過了 該不該學習TypeScript 的階段了

去年還有很多朋友猶豫用不用學習TypeScript , 很多被社群朋友發的文章誤導, TypeScript 就是 AnyScript


2.png

TypeScript 對沒有接觸過 強型別語言的朋友有點學習成本,而且在使用TypeScript 進行開發時,前期可能程式碼會很臃腫,讓你覺得看起來有很多 無用的程式碼 , 不過在後期,你就可以感覺到 TypeScript 給你帶來的樂趣了。

學會 TypeScript也對你在職場競爭有點優勢,在跳槽時,假如你已經使用 TypeScript 結合 框架 做過一些專案,面試官也會優先考慮你的,薪水從而也提升了。
前端之路還有很長,新的技術/框架更新頻率很快, 最終還是離不開 JavaScript

下面,我們一起來看看 TypeScript,本文是對標 TypeScript 文件進行來講解,更加大白話的簡單講述如何使用TypeScript .

入手導圖


reWaP1.png

TypeScript

一,安裝環境

#npm install -g typescript

1.1 VSCode 配置自動編譯檔案

#1. 在目錄下  
 tsc --init   自動生成 tsconfig.json 
 tsconfig.json 下 outdir 是輸出的路徑
#2.  任務--- 執行任務   監視 tsconfig.json

二,基本語法

2.1 陣列

定義使用
// 第一種定義方法   let 陣列名:型別[] = []
var arr:number[] = [1,2,3];
console.log(arr);
// 第二種定義方法    let 陣列名:Array[型別] = []
var newArr:Array<number> = [1,2,3];
console.log(newArr)

2.2 元組

它表示 已經 元素的個數和元素型別的陣列,各個元素型別可以不一樣。

訪問元組長度 和 元素
var strArr:[number,string,boolean] = [22,'測試',false]
console.log(strArr.length)
console.log(strArr[0])
#它只能按型別的優先順序輸入內容,否則報錯

2.3 列舉 enum

enum型別是對JavaScript標準資料型別的一個補充。

  • 如果沒有給列舉指定索引的話,預設為 0 , 通過 列舉物件[索引] 可以獲取值
  • 如果指定了列舉索引為字串的話,通過 列舉.屬性 獲取的 它的值
enum Sex {Man,Woman}

let m:Sex = Sex.Man;
console.log(m) //0

let w: string = Sex[1]
console.log(w) //Woman

enum Animal {Dog = 3, Cat, Tiger};

console.log(Animal[5]) //Tiger

enum info {student = '學生', teacher = '教師',  parent = '家長' };

console.log(info.teacher)  //教師

2.4 任意型別 any

any 為 任意型別,一般在獲取dom 使用

// 任意型別
const newArrs:any = ['測試不同資料 ',222,false]
console.log(newArrs)
# 輸出結果為[ '測試不同資料 ', 222, false ]
# 使用場景: 當你不知道型別 或 一個物件 或資料 需要多個型別時,使用any

2.5 undefined 型別

let num:number | undefined ;
console.log(num) // 輸出 undefined, 但是不會報錯
let newNum:number | undefined = 33;
console.log(newNum)  // 輸出 33 

2.6 never 型別

never 代表不存在的值型別,常用作為 丟擲異常或者 無限迴圈的函式返回型別

# 應用場景 
 #1. 拋錯誤
    const errDate = (message:string): never => {
        throw new Error(message)
    }
    #2.  死迴圈
    const date_for = (): never => {
        while(true) {}
    }
    
# never 型別是任意型別的子型別,沒有型別是never 的子型別
別的型別不能賦值給never型別, 而 never 型別可以賦值給任意型別

2.7 void 型別

void 為 函式沒有型別,一般用在沒有返回值的函式

# 如果方法型別為number, 則必須返回內容, 內容且必須為數字
function add():number{
    return 2323;
}

# 如果方法型別為void,不需要返回內容
function getAdd():void{
    console.log('測試')
}

# 如果方法型別為any,則可以返回任意型別
function getAny():any{
    return 999 + 'Hello TypeScript'
}

console.log(getAny())//999 'Hello TypeScript'

三,型別斷言

什麼是型別斷言?

  • 有時候你在定義一個變數時,起初是不知道是什麼型別,但在使用過程中知道是什麼型別,這時就會用到型別斷言了。
3.1第一種寫法 尖括號
const str = '測試'

const resLength : number = (<string>str).length
3.2第二種寫法 as
const str = '測試'

const resLength : number = (str as string).length

四,介面

TypeScript的核心原則之一是對值所具有的結構進行型別檢查。

驗證型別時,順序不影響驗證。

簡單的來說,它是型別約束的定義,當你使用這個定義介面時,它會一一匹對介面中定義的型別。

只要不滿足介面中的任何一個屬性,都不會通過的。

4.1 介面可選屬性

有時候,介面屬性不是必須全部需要的,滿足某些條件才會需要,這時,就可以採用可選屬性

格式 : 屬性 ?: 型別

interface  Login{
    userName: string,
    password: string,
    auth ?: string
}

function getLogin(obj: Login) {
    if(obj.auth == '管理員') {
        console.log('可以檢視所有選單')
    } else {
        console.log('您的許可權比較低,目前不能檢視')
    }
}

getLogin({
    userName:'zhangsanfeng',
    password: '12121121sd',
    auth: '管理員'
})    //可以檢視所有選單

getLogin({
    userName:'zhangsanfeng',
    password: '12121121sd'
})    //您的許可權比較低,目前不能檢視

4.2 介面 只讀屬性

只讀屬性: 意味著給屬性賦值了後,不可改變。

格式: readonly 屬性 : 型別

interface Menus {
    readonly title?:string,
    icon?:string,
    readonly path?:string,
    readonly Layout?:string
}

function getMenuInfo(data:Menus){
    console.log(data)
    data.icon = '修改圖示'   // 可以修改
    // data.path = '/home'    報錯,禁止修改,介面屬性為只讀
    console.log(data)
}

getMenuInfo({
    title: '主頁',
    icon:'homes',
    path:'/home',
    Layout: 'Layput'
})

4.3 介面函式型別

用來約束函式傳遞引數型別

  • 函式型別的型別檢查來說,函式的引數名不需要與接口裡定義的名字相匹配。
  • 格式: (引數1: 型別,引數2:型別) : 返回值型別
// 獲取使用者資訊
interface UserInfo {
    (name: string,adress: string,phone: number) : string
}

let getUsefInfo:UserInfo = function(name,adress,phone){
    return `${name}住在${adress},手機號為${phone}`
}

console.log(getUsefInfo('張鋒','天津南開區xx小區',188888888))
4.4 介面可索引型別

在定義一個數組時,可以定義一個 索引型別介面,這樣就約束了它必須傳遞哪些型別的值。

訪問: 通過 變數[索引]

interface Code{
    [index : number] : string
}

let errCode : Code = ['200 成功','301 重定向', '400 客戶端錯誤', '500  服務端出錯']

console.log(errCode[3])  //500  服務端出錯
4.5 型別介面實現

介面描述了類的公共部分,而不是公共和私有兩部分。 它不會幫你檢查類是否具有某些私有成員。

interface Animals {
    eye: number,
    leg: number,  
}

class Dog  implements Animals {
    eye: number;
    leg: number;
    kind: string
    constructor(eye: number, leg: number, kind: string) {
        this.eye = eye
        this.leg = leg
        this.kind = kind
    }
    getDogInfo(){
        console.log(`品種為${this.kind},有${this.eye}隻眼,${this.leg}條腿`)
    }
}

let hashiqi = new Dog(2,4,'哈士奇');
hashiqi.getDogInfo() //品種為哈士奇,有2隻眼,4條腿
4.6 介面繼承(多合一)

介面之間可以互相繼承,這樣可以更靈活地將介面分割到可重用的模組裡。

interface Shape1 {
    data: string
}

interface Shape2  extends Shape1{
    code: number
    // Shape2 具有 Shape1 的特徵
}

class Message implements Shape2 {
    code: number;
    data: string;
    constructor(code : number,data: string) {
        this.code = code;
        this.data = data
    }
    getCodeInfo(){
        console.log(`狀態碼為${this.code},返回資訊為${this.data}`)
    }
}

let err = new Message(500,'服務端錯誤')
err.getCodeInfo()  //狀態碼為500,返回資訊為服務端錯誤
4.7 介面繼承類

當介面繼承了一個類,那麼介面也會擁有類的屬性和方法。

當別的類 實現這個 介面時,會同時實現 介面的屬性和方法, 繼承類的屬性和方法

class Log {
     time: string = '2020-11-2';
     getLog(){
         console.log('測試')
     }
}

interface  Shape3  extends Log{
    message : string
}

class ErrLog implements Shape3 {
    message: string ;
    time: string;
    constructor(message: string, time: string) {
        this.message = message;
        this.time = time
    }
    getLog(): void {
        console.log("Method not implemented.");
    }
}

let errs = new ErrLog('測試','2020-11-2')
errs.getLog()  //Method not implemented.

五,泛型

接觸過JAVA 的同學,應該對這個不陌生,非常熟了。

作為前端的我們,可能第一 次聽這個概念。 通過 字面意思可以看出,它指代的型別比較廣泛。

  • 作用: : 避免重複程式碼,程式碼冗餘

但是它和 any 型別 還是有區別的。

  • any 型別: 如果一個函式型別為any,那麼它的引數可以是任意型別,一般傳入的型別與返回的型別應該是相同的。如果傳入了一個 string 型別的引數,那麼我們也不知道它返回啥型別。
  • 泛型 : 它可以使 返回型別 和 傳入型別 保持一致,這樣我們可以清楚的知道函式返回的型別為什麼型別。
5.1 泛型介面

泛型介面可以這樣理解:

當你需要給介面指定型別時,但目前不知道屬性型別為什麼時,就可以採用泛型介面

你可以給介面指定引數為多個泛型型別,也可以單個;當使用時,明確引數型別即可。


reJsYR.png
 interface User <T,S,Y> {
     name: T;
     hobby: S;
     age: Y;
 }

 class People implements User<String,String,Number> {
     name: String;
     hobby: String;
     age: Number;
     constructor(name:string,hobby:string,age:number){
         this.name = name;
         this.hobby = hobby;
         this.age = age; 
     }
    getInfo(){
        console.log(this.name+"------------------"+this.hobby)
        console.log(`${this.name}的年齡為${this.age}`)
    }  
 }
 let xiaoZhou =  new People('小周','敲程式碼',22)
 xiaoZhou.getInfo() 
 //小周------------------敲程式碼
//  小周的年齡為22

5.2 泛型函式

定義泛型函式,可以讓 傳入引數型別引數 和 返回值型別保持一致。

泛型 標誌一般用字母大寫,T 可以隨意更換

格式 :  函式名<T> (引數1:T) : 返回值型別 T
function genericity<T> (data: T) : T {
    console.log(data)
    return data
}

genericity("測試")
genericity(666)
genericity(['前端','後端','雲端'])

5.3 泛型類
  1. 什麼是泛型類

它規定了類中屬性和方法的 型別,而且必須和型別定義的型別保持一致。

  1. 泛型類的作用

可以幫助我們確認類的所有屬性都在使用相同的型別

  1. 使用格式
class 類名<T> {
 name!: T;
 hobby!: T;
}

# 這樣這個類的所有型別為 number
let 例項 =  new 類名<number>();

class GenericityA<X>{
    sex!: X;
    age!: X;
}

let gen = new GenericityA<number>();

// gen.sex = '測試'   報錯
gen.age = 3
console.log(gen.age)
5.4 泛型約束
  1. 介面約束
  • 通過定義介面, 泛型函式繼承介面,則引數必須實現介面中的屬性,這樣就達到了泛型函式的約束
  1. 類約束
  • 通過給類的泛型指定為另一個類,這樣就規定了類泛型的型別都為另一個類
# 第一種
// 定義介面
 interface DataInfo{
     title: string,
     price: number
 }

//  泛型函式 繼承介面,進行對引數型別約束, 如果傳入的引數中,沒有包含介面屬性,則編譯不通過
 function getDataInfos< T extends DataInfo> (obj: T) : T {
     return obj
 }

 let book = {
     title: '前端進階',
     price: 50,  
     author: '小新'
 }

 console.log(getDataInfos(book)) //{ title: '前端進階', price: 50, author: '小新' }
# 第二種
//  通過類來約束
 class Login{
    username: string;
    password: string;
    constructor(username: string,password:string){
        this.username = username
        this.password = password
    }
 }

class Mysql<T>{
    login<T>(info:T):T{
        return info
    }
}

let  x = new Login('admin','12345');
let  mysql = new Mysql<Login>();
console.log(mysql.login(x)) //Login { username: 'admin', password: '12345' }

六,類 Class

說到類,做後端的朋友應該都瞭解,前端 在ES6 中,才出現了 類 Class 這個關鍵詞。

Class 有哪些特徵

  • 屬性
  • 構造器
  • 方法
  • 繼承 extends
  • 屬性 / 方法 修飾符
  • 靜態屬性
  • 抽象類
  • 存取器 getters/setters

6.1 修飾符

public 共有的

當屬性 / 方法 修飾符為 public 時, 如果前面沒有,預設會加上,我們可以自由的訪問程式裡定義的成員。

class Fruit {
    public name: string;
    price: number;
    // 以上為等價
    constructor(name: string, price: number) {
        this.name = name;
        this.price = price
    }
    getFruitInfo(){
        console.log(`您要購買的水果為${name},價格為${this.price}`)
    }
}
private 私有的

當成員被標記成 private時,它就不能在宣告它的類的外部訪問。

class Fruit {
    public name: string;
    private price: number;

    // 以上為等價
    constructor(name: string, price: number) {
        this.name = name;
        this.price = price
    }
    getFruitInfo(){
        console.log(`您要購買的水果為${name},價格為${this.price}`)
    }
}

const apple = new Fruit('蘋果',22)
// console.log(apple.price)   報錯, 例項不可以訪問私有屬性

protected受保護的

protected修飾符與 private修飾符的行為很相似,但有一點不同, protected成員在派生類中仍然可以訪問,不可以通過例項來訪問受保護的屬性。

class A {
    protected name : string;
    protected age : number;
    constructor(name: string , age: number) {
        this.name = name;
        this.age = age
    }
    getA(){
        console.log('A')
    }
}

class B extends A {
    protected job : string;
    constructor(name: string, job: string,age: number) {
        super(name,age)
        this.job = job
    }
    getB(){
        console.log(`B 姓名為${this.name} && 年齡為${this.age} && 職業為${this.job},`)
    }
}

let b = new B('小飛','前端工程師',22)
b.getA()  //A
b.getB()   //B 姓名為小飛 && 年齡為22 && 職業為前端工程師,
// console.log(b.name)  報錯,訪問不了,protected成員只能在派生類中可以訪問,不能通過例項來訪問。

6.2 靜態屬性

類的靜態成員(屬性 和 方法) 只能通過 類來可以訪問。

定義: static 屬性 / static 方法

class Food {
    public name: string;
    private price: number;
    static adress: string = '四川';
    // 以上為等價
    constructor(name: string, price: number) {
        this.name = name;
        this.price = price
    }
    getFruitInfo(){
        console.log(`您要購買的東西為${name},價格為${this.price}`)
    }
}

const spicy = new Food('辣條',3)

console.log(Food.adress)  //四川
// console.log(spicy.adress)  報錯  類的例項物件不可以訪問 類的靜態屬性。 只可以通過類.屬性來訪問

6.3 繼承 extend

繼承的本意很好理解,當子類繼承了父類,那麼子類就擁有了父類的特徵(屬性) 和 行為(方法),

class T {
    name:string;
    constructor(name:string){
        this.name = name
    }
    getNames(){
        console.log('繼承類T')
    }
}

class S extends T {
    constructor(name:string){
        // 派生類擁有T屬性和方法
        super(name)
    }
    getName(){
        console.log(this.name)
    }
}

let  ss = new S('測試繼承')
ss.getName()  
ss.getNames()
// 測試繼承
// 繼承類T

6.4 抽象類

  • 抽象類可以包含成員的實現細節。 abstract關鍵字是用於定義抽象類和在抽象類內部定義抽象方法。
  • 抽象類中的抽象方法不包含具體實現並且必須在派生類中實現。
abstract class E{
    abstract name: string;
    abstract speak():void;
    abstract play():void;
}

class F implements E {
    name: string;
    constructor(name:string){
        this.name = name
    }
    //  派生類 F 必須實現 抽象類E 的方法和屬性 
    speak(): void {
        console.log('具有聊天功能')
    }
    play(): void {
        console.log('具有娛樂功能')
    }
    get(){
        console.log(this.name)
    }
}

let f = new F('測試');
f.play() //具有娛樂功能
f.get() // 測試
f.speak()  //具有聊天功能

七,TS 中的函式

函式型別包括 引數型別 和 返回值型別

7.1 函式新增返回值型別

每個引數新增型別之後再為函式本身新增返回值型別.

TypeScript能夠根據返回語句自動推斷出返回值型別,因此我們通常省略它。

下面會介紹在TS 中,兩種寫函式的格式

//  第一種寫法
let getInterFaceInfo : (obj:object) => void = function(obj){
    console.log(obj)
}

let infos: object = {
    code: 200,
    message: '傳送成功'
}
getInterFaceInfo(infos)

//  第二種寫法
function getCode(code: number, message:string) : void {
    console.log(`code為${code},message為${message}`)
}

getCode(200,'接受成功') 
7.2 函式可選引數 / 預設引數

JavaScript裡,每個引數都是可選的,可傳可不傳。 沒傳參的時候,它的值就是undefined。

在TypeScript裡我們可以在引數名旁使用 ?實現可選引數的功能。

  • 可選引數必須放在必須引數後面。
格式 : 函式名(變數名?:型別):型別 {}  
  • 預設引數,在傳遞引數時,指定預設值
格式 : 函式名(變數名 :型別 = "xx"):型別 {}
//  可選引數
function getNetWork(ip:string,domain:string,agreement?:string){
    console.log(`ip地址為:${ip},域名為${domain},協議為${agreement}`)
}

getNetWork('127.0.0.1','www.xiaomi.com')  //ip地址為:127.0.0.1,域名為www.xiaomi.com,協議為undefined

// 預設引數
function getNetWorks(ip:string,domain:string,agreement:string = 'http'){
    console.log(`ip地址為:${ip},域名為${domain},協議為${agreement}`)
}
getNetWorks('127.0.0.1','www.xiaomi.com') //ip地址為:127.0.0.1,域名為www.xiaomi.com,協議為http    
7.3 函式剩餘引數

有時,你想同時操作多個引數,或者你並不知道會有多少引數傳遞進來。

在JavaScript裡,你可以使用 arguments來訪問所有傳入的引數。

在TypeScript 中,可以把所有引數集中在一個變數中,前面加上... 表示 剩餘引數。

注意

  • 直接通過變數訪問
  • 也可以通過索引訪問
  • 只能定義一個剩餘引數,且位置在 預設引數和可選引數後面
function getNumberInfo(num:number,...peopleArray: string []) {
    console.log(`人員個數為${num},成員為${peopleArray}`)  // 也可以通過索引來獲取元素
    console.log(`人員個數為${num},成員為${peopleArray[1]}`) 
}

getNumberInfo(4,'小明','小李','小紅','小張')  
//人員個數為4,成員為小明,小李,小紅,小張
//人員個數為4,成員為小李

八,列舉

列舉可以清晰地表達一組對應關係。

TypeScript支援數字的和基於字串的列舉。

8.1 數字列舉

預設列舉的順序以 0 開頭,然後自動遞增。

列舉順序也可以指定 值, 指定後,它前面第一個還是以0 遞增

訪問

  • 通過 列舉名.屬性 訪問到的是 序號
  • 通過 列舉名[序號] 訪問到的是 屬性名
enum Sex {
    x,
    man = 4,
    woman 
}

console.log(Sex.x)   //0
console.log(`小紅的性別為${Sex[5]}`) //小紅的性別為woman
console.log(`後端接受小紅的性別ID ${Sex.woman}`) //後端接受小紅的性別ID 5
8.2 字串列舉
enum Job {
    frontEnd = '前端',
    backEnd = '後端'
}

console.log(Job) //{ frontEnd: '前端', backEnd: '後端' } 

九,高階型別

9.1 交叉型別

它指 可以將多個型別合併為一個型別。識別符號為 & , 當指定一個變數型別為 交叉型別時,那麼它擁有交叉型別的所有屬性,也就是並集。

interface DonInterface {
    run():void;
}
interface CatInterface {
    jump():void;
}
//這裡的pet將兩個型別合併,所以pet必須保護兩個型別所定義的方法
let pet : DonInterface & CatInterface = {
    run:function(){},
    jump:function(){}
}

9.2 聯合型別
  • 聯合型別表示一個值可以是幾種型別之一。
  • 用豎線( |)分隔每個型別。
  • 一個值是聯合型別,我們只能訪問此聯合型別的所有型別裡共有的成員。
 function getMenus(info: string | number) {
     console.log(info)
 }

getMenus("測試")
getMenus(2)
// getMenus(false)  報錯

十,模組

模組: 定義的變數,函式,類等等,只能在自身的作用域裡使用。 如果想在外部訪問使用,那麼必須使用export 將其匯出即可。

使用模組: 通過 import 將模組內容匯入即可使用。

  • 模組是自宣告的;兩個模組之間的關係是通過在檔案級別上使用imports和exports建立的。
  • 模組使用模組載入器去匯入其它的模組。 在執行時,模組載入器的作用是在執行此模組程式碼前去查詢並執行這個模組的所有依賴。

10.匯出

10.1 匯出宣告

任何宣告(比如變數,函式,類,類型別名或介面)都能夠通過新增export關鍵字來匯出。

匯出可以對任何宣告 進行重新命名,防止命名衝突, 通過 as 來修改

# 模組A 檔案

// 匯出介面
 export interface A {
     getList() : void
 }

//  匯出變數
export const  GET_METHOD =  "get"
 

//  匯出類
export class S implements A {
    getList(): void {
        console.log("匯出類")
    }
    
}

function getQueryData():void {
    console.log("獲取分頁資料")
}

// 匯出模組 變數重新命名
export { getQueryData as getQuery}

# 檔案B
import {getQuery,S,A} from './模組A';

// 使用模組中的函式
getQuery()

// 例項模組中類的物件
const a = new S();
a.getList()  // 輸出匯出類

// 實現模組中的 A 介面
class Y implements A {
    getList(): void {
        throw new Error('Method not implemented.');
    }
    
}

10.2 組合模組使用

通常一個大的模組是多個子模組組成的。那麼我們可以通過 在大的模組中匯入多個子模組。

格式: export * from "模組"

使用組合模組: import * as 重新命名變數 from ‘組合模組路徑’

# 模組C
    //  匯出變數
    export const  GET_METHOD =  "get"
# 模組B

export const str: string = "B模組"

export  function getContent():void{
    console.log("我是模組B的內容")
}
#組合模組

 const  res : object =  {
    code: 200,
    message: "請求成功"
 }

export function getRes(): void {
     console.log(res)
 }

 
 # 匯出子模組
 export * from "./modulesC"
 export * from "./moduleB"
10.3 使用組合模組
import * as T from "./modulesA";

// C 模組中的
console.log(T.GET_METHOD)

// B 模組中的內容
console.log(T.str)  //B模組
T.getContent() //我是模組B的內容

// A 模組中的內容
T.getRes() //{ code: 200, message: '請求成功' } 
10.4 預設匯出

每個模組都可以有一個default匯出。 預設匯出使用 default關鍵字標記;並且一個模組只能夠有一個default匯出。

#模組

export interface K {
    name:string;
    birth:string;
}

export default class Student implements K {
    name: string;
    birth: string;
    constructor(name:string,birth:string){
        this.name = name;
        this.birth = birth;
    } 
    getStudentInfo(){
        console.log(this.name+this.birth)
    }
}
#檔案A
import D,{K} from './modulesD'

//  使用預設匯出
 const d = new D('小明','1998')
 d.getStudentInfo()

// 引數型別為介面K 
 function getMessage(obj: K): void {
    console.log(obj)
 }
 let obj = {
     name:"小紅",
     birth: "1998"
 }
 getMessage(obj);

10.5 export = 和 import = require()

CommonJS和AMD的環境裡都有一個exports變數,這個變數包含了一個模組的所有匯出內容。

CommonJS和AMD的exports都可以被賦值為一個物件

exports 和 export default 用途一樣,但是 export default 語法不能相容CommonJS和AMD的exports

在TypeScript 中,為了達到這樣效果,可以這樣寫:

匯出: export = 等於 exports

匯入: import module = require("module")

# 模組
// 相當於預設匯出
export = class Mongodbs{
    host:string;
    user:string;
    password:string;
    port:number;
    databaseName:string;
    constructor(host:string,user:string,password:string,port:number,databaseName:string) {
        this.host = host;
        this.user = user;
        this.password = password;
        this.port = port;
        this.databaseName = databaseName
    }
    query(table:string){
        console.log(`select * from ${table}`)
    }
}

#使用模組

import MogoDb = require("./modulesE")  

const mogodb = new MogoDb('1270.0.1','admin','123456',3006,'TypeScript')

mogodb.query('Vue') //select * from Vue

十一, 名稱空間

  1. 定義
  • “內部模組”稱為“名稱空間”
  • “外部模組”稱為“模組”
  1. 作用
  • 減少命名衝突,將程式碼組織到一個空間內,便於訪問。
  1. 使用格式
  • 通過 namespace 空間名 { } ,內部通過 export 匯出來使用內部成員
namespace XiaoXin {
    export interface  GetData{
        name: string;
        price: number;
        getInfo(obj:object):any;
    }
    export interface  GetMessage {
        code: number;
        message: string;
    }

    export class Book implements  GetData{
        name: string;
        price: number;
        constructor(name:string,price:number){
            this.name = name;
            this.price = price
        }
        getInfo(obj: object) {
            throw new Error("Method not implemented.");
        }
        buyBook(obj: GetMessage) {
            console.log(obj)
        }
    }           
}

const fontEnd = new  XiaoXin.Book("前端開發手冊",99)

var obj = {
    code: 200,
    message:"購買成功"
}

fontEnd.buyBook(obj)  //{ code: 200, message: '購買成功' }

function test(obj:XiaoXin.GetMessage){
    console.log(obj)
}

test(obj)  //{ code: 200, message: '購買成功' } 

11.1 拆分名稱空間

當應用變得越來越大時,我們需要將程式碼分離到不同的檔案中以便於維護。

我們可以將名稱空間檔案拆分成多個檔案,但是它們的名稱空間名還是使用的同一個,各個檔案相互依賴使用。但是必須檔案最開頭引入 名稱空間檔案。

格式: /// <reference path="MS1.ts"/>

# 根名稱空間
namespace School {
    export const schoolName =  "清華大學"
}
# 子名稱空間1
/// <reference path="MS1.ts" />

namespace School{
    export class Teacher {
        faculty:string;
        name:string;
        age:number;
        constructor(faculty:string,name:string,age:number){
            this.faculty = faculty;
            this.name = name;
            this.age = age
        }
        getInfo(){
            console.log(`${this.name}為${this.faculty},年齡為${this.age}`)
        }
        getSchool(schoole:string){
            console.log(`${this.name}老師就職於${schoole}`)
        }
    }
}
#  子名稱空間2
///  <reference path="MS1.ts" />

 namespace School{
     export class Student{
         name:string;
         age:number;
         hobby:string;
         constructor(name:string,age:number,hobby:string) {
             this.name = name;
             this.age = age;
             this.hobby = hobby;
         }
         getInfo(){
             console.log(`${this.name}是一個學生,年齡為${this.age},愛好是${this.hobby}`)
         }
     }
 }

# 使用合併的名稱空間

匯入名稱空間
/// <reference path="MS1.ts" />
/// <reference path="MS2.ts" />
/// <reference path="MS4.ts" />

let teacher = new School.Teacher('計算機教授','張博士',34);
teacher.getInfo() //張博士為計算機教授,年齡為34
teacher.getSchool(School.schoolName)  //張博士老師就職於清華大學

let students = new School.Student('張三',17,'玩LOL');
students.getInfo()  //張三是一個學生,年齡為17,愛好是玩LOL

編譯名稱空間檔案
第一種方法: 會編譯為 一個js檔案
tsc --outFile sample.js Test.ts

第二種方法:  會編譯為多個js檔案,然後通過 <script> src 引入js檔案即可
tsc --outFile sample.js Validation.ts LettersOnlyValidator.ts ZipCodeValidator.ts Test.ts

十二,裝飾器

裝飾器是一種特殊型別的宣告,它能夠附加到類宣告、方法、訪問符、屬性、類方法的引數上,以達到擴充套件類的行為。

自從 ES2015 引入 class,當我們需要在多個不同的類之間共享或者擴充套件一些方法或行為的時候,程式碼會變得錯綜複雜,極其不優雅,這也是裝飾器被提出的一個很重要的原因。

12.1 修飾器分類
  • 類裝飾器
  • 屬性裝飾器
  • 方法裝飾器
  • 引數裝飾器
修飾器寫法: 1. 普通修飾器  (不傳引數)
     2.  裝飾器工廠 (傳引數)
12.2 類裝飾器

類裝飾器表示式會在執行時當作函式被呼叫,類的建構函式作為其唯一的引數。

使用場景:應用於類建構函式,可以用來監視,修改或替換類定義。

const extension = (constructor: Function):any => {
    constructor.prototype.coreHour = '10:00-15:00'
  
    constructor.prototype.meeting = () => {
      console.log('過載:Daily meeting!');
    }

  }

@extension
class Employee {
  public name!: string
  public department!: string

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

  meeting() {
    console.log('Every Monday!')
  }

}

let e: any = new Employee('裝飾器', '測試')
console.log(e)  //Employee { name: '裝飾器', department: '測試' }
 console.log(e.coreHour) // 10:00-15:00
 e.meeting()             // 過載:Daily meeting!
  
12.3 類屬性裝飾器

作用於類屬性的裝飾器表示式會在執行時當作函式被呼叫,傳入下列3個引數 targetnamedescriptor

  • target: 對於靜態成員來說是類的建構函式,對於例項成員是類的原型物件
  • name: 成員的名字
  • descriptor: 成員的屬性描述符

執行順序: 當呼叫有裝飾器的函式時,會先執行裝飾器,後再執行函式。

通過修飾器完成一個屬性只讀功能,其實就是修改資料描述符中的 writable 的值 :

function readonly(value: boolean){
    return function(target:any,name:string,descriptor:PropertyDescriptor) {
        descriptor.writable = value
    }
}

class Student{
    name:string;
    school:string = '社會大學'
    constructor(name:string) {
        this.name = name
    }
    @readonly(false)
    getDataInfo(){
        console.log(`${this.name}畢業於${this.school}`)
    }
}

let sss = new Student('小李子')
// 報錯,  只能讀,不能修改
// sss.getDataInfo = () => {
//     console.log("測試修改")
// }

sss.getDataInfo()

更多精彩文章在主頁

最後

文中如有錯誤,歡迎碼友在評論區指正,如果對你有所幫助,歡迎點贊和關注~~~