TypeScript 入門指南 【大白話】

前言

聊聊為何要學習TypeScript?

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

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

2.png
2.png

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

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

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

入手導圖

reWaP1.png
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)
# 輸出結果為[ '測試不同數據 '222false ]
# 使用場景: 當你不知道類型 或 一個對象 或數據 需要多個類型時,使用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
reJsYR.png
 interface User <T,S,Y> {
     name: T;
     hobby: S;
     age: Y;
 }

 class People implements User<String,String,Number{
     nameString;
     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 getDataInfosT extends DataInfo> (obj: T) : T {
     return obj
 }

 let book = {
     title'前端進階',
     price50,  
     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 = {
    code200,
    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 =  {
    code200,
    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 = {
    code200,
    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()

更多精彩文章在主頁

最後

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

Tags: