淺談TypeScript類型、接口、裝飾器

image.png

前言

TypeScript 對於前端人員甚至後臺人員都不算是特別陌生的東西了(身邊不少java朋友看ts都以爲還不錯),今天來聊聊這玩意,基礎用法,以及項目中你們都是怎麼用的。 順便一說,ts這東西實在是太大了,由於他的類型能夠很靈活的去組合,不過放心,本文不會涉及太多概念性的東西(也說不完),由於其實一個大項目,每每就是這些基本類型用得最多,像type和interface這些東西,不過也分職位,若是你是項目組長或者是大家公司前端負責人要求的特別嚴格或者你是寫工具的,封裝一些公用組件的,用這些特別的東西機會會比較多javascript

在ts裏,類型最好是規定的越死越好,由於這東西自己就是來規範本身規範團隊的,若是要是全用any的話,不如直接用js,若是項目只有本身,那就更不必上這玩意了,給本身找麻煩前端

基礎類型

ts裏你們多少應該都聽過,一些number類型string類型包括函數的參數類型什麼nerver、void本文就再也不多贅述了,由於這東西實在太簡單了。。。這裏就簡單的列一下vue

基本類型: number/string/boolean/Array/objectjava

any null、undefined void nevernode

工具

由於作實驗的時候每次都須要tsc編譯一下,而後node 文件 太麻煩了,我這裏簡單寫了個gulp(沒用webpack由於gulp比較簡單方便而且快),你們願意用能夠直接用webpack

評論區有人指出來只編譯的話能夠直接tsc -w 實際上是同樣的,不過我主要是爲了本身方便watch的時候清屏,以及一切其餘的文件操做,因此gulp方便一點,這裏就留個架子,有興趣能夠在評論區或者gulp官網找更多方便的命令web

用法:typescript

  1. cnpm i -g gulp-cli  - 安裝gulp自己
  2. cnpm i  - 安裝本地依賴庫
  3. gulp watch - 運行gulp的watch任務

package.jsonnpm

{
  "name": "test",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "gulp": "^4.0.2",
    "gulp-clean": "^0.4.0",
    "gulp-run": "^1.7.1",
    "gulp-typescript": "^6.0.0-alpha.1",
    "gulp-watch": "^5.0.1",
    "typescript": "^3.7.4"
  }
}

複製代碼

gulpfile.jsjson

const gulp = require('gulp'),
  watch = require('gulp-watch'),
  ts = require('gulp-typescript'),
  run = require('gulp-run'),
  clean = require('gulp-clean');

gulp.task('watch', () => {
  return watch('./1.ts', () => {
    gulp
      .src('./1.ts')
      .pipe(
        ts({
          target: 'ES6',
          outFile: '1.js',
          experimentalDecorators: true,
        }),
      )
      .on('error', err => {
        // console.error(err);
      })
      .pipe(gulp.dest('build'))
      .pipe(run('node build/1.js'));
  });
});

複製代碼

項目結構以下

image.png

運行結果

這個build不用本身手動建立,直接運行gulp,會自動建立而且運行的

image.png

數組

數組的花樣其實還相對來講挺多的,這裏先介紹基礎用法,下文會講到和其餘的一些東西配合

let arr = Array<number>; // 規定只能是裝數字的數組
// 能夠簡寫成 let arr = number[];
複製代碼

Type - Interface

常常有人問我 ts type 和 interface 有啥區別,首先確定一點,這倆功能確定是有相同點,也確定有區別,要否則做者也就不有病搞兩個出來了,這裏咱們先說分別的用法,再說區別

我們暫時理解成這兩個都是自定義類型的

首先ts約定類型是能夠約定json內部的,這個你們都知道就像這樣 這樣是報錯的

image.png

嚴格遵照才能夠

image.png

type

可是若是有一個類型特別經常使用,好比說是用戶類型,假設也是確定有名字和年齡,每次定義變量的時候寫一堆那確定是不行

image.png

這樣就能夠在多個地方用了

interface

上面的例子直接改爲interface也是同樣的

image.png

區別

其實這麼一看例子,你們可能會想,這不同麼,有啥區別

image.png
其實不是,這個interface嚴格翻譯來講叫 接口  其實這個東西我們前面那種用法根本是不對的,也不能說不對,能夠說是否是真正適合他的地方,這東西不是當類型用的,真正的用法是 須要把他用於實現  可能這時候有人會以爲,說了跟沒說同樣,怎麼就實現,實現什麼啊,說人話等等

想象如今有個需求,我有一個類,這個累的是對http請求的封裝,這裏面能夠直接把數據變成字符串發到服務器而後還能夠拿回來的時候再解析成json,在寫這個例子前,咱們先來介紹另外一個東西

implements

implements  這個東西跟extends有點像,extends是從一個類繼承出來,這個implements不是繼承一個類,而是實現一個接口

那麼結合interface我們來寫個例子

interface serializeable {
  tostring(): string;
  fromString(str: string): void;
}

class SendData implements serializeable {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  public tostring() {
    return JSON.stringify({
      name: this.name,
      age: this.age,
    });
  }

  public fromString(str: string) {
    let data = JSON.parse(str);
    this.name = data.name;
    this.age = data.age;
  }
}

複製代碼

順便一說這個,implements是能夠同時實現多個接口的,就直接跟名字就能夠了,像這樣

interface serializeable {
  tostring(): string;
  fromString(str: string): void;
}

interface serializeable2 {

}

class SendData implements serializeable, serializeable2 {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  public tostring() {
    return JSON.stringify({
      name: this.name,
      age: this.age,
    });
  }

  public fromString(str: string) {
    let data = JSON.parse(str);
    this.name = data.name;
    this.age = data.age;
  }
}

複製代碼

看到這。。。我相信有人還有疑問,那。。。。這東西到底怎麼用呢

參考我們上面提到的 http的那個需求,嘗試用一下這玩意,假設,如今要發送給服務器的數據必須得實現我這個接口

interface serializeable {
  tostring(): string;
  fromString(str: string): void;
}

class SendData implements serializeable {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  public tostring() {
    return JSON.stringify({
      name: this.name,
      age: this.age,
    });
  }

  public fromString(str: string) {
    let data = JSON.parse(str);
    this.name = data.name;
    this.age = data.age;
  }
}

function sendToServer(obj: serializeable) {}

sendToServer(new SendData('name', 18));

複製代碼

這時候看控制檯是沒有任何錯誤的

可是我們隨便換一個類

image.png

我這個SendData2沒有實現個人接口,這個時候他是否是就直接爆了呀,可能仍是會有人以爲,那。。。。這有什麼用呢? 注意⚠️這個其實對於挑錯頗有用,正常狀況下,用js去寫的話,是否是須要在 sendToServer 這個函數裏執行 obj.tostring 或者這個接口的其餘方法纔會保存,那這個就變成運行時的報錯了,若是項目大起來,找錯是特別麻煩的事,因此這個東西能直接避免一部分錯誤,真不錯對吧?

泛型

泛型稍微有點特殊,我們先來看個例子,更能讓你們明白這東西的做用 這裏先寫一個函數,先不考慮實用性,就是有一個函數,能夠傳進去一個數字,和循環的次數,而後返回一個數字數組

function repeat(item: number, count: number): number[] {
  let result: number[] = [];

  for (let i = 0; i < count; i++) {
    result.push(item);
  }

  return result;
}

let arr: number[] = repeat(13, 4);
console.log(arr);

複製代碼

image.png

首先東西確定是能出來,可是。。。。 repeat是吧,如今實現的只是循環數字,有沒有可能未來須要循環字符串,布爾值,那這個一個一個寫得寫多少 因此我們如今得想辦法把類型傳過來,固然了 any固然也能夠,不過。。。。參考我寫的前言那裏 若是用any的話乾脆用js算了 固然了,我們這主要說明泛型,其實在這用any也是能夠的

image.png

function repeat<T>(item: T, count: number): T[] {
  let result: T[] = [];

  for (let i = 0; i < count; i++) {
    result.push(item);
  }

  return result;
}

let arr: number[] = repeat<number>(13, 4);
console.log(arr);

let arr2: string[] = repeat<string>('aaa', 4);
console.log(arr2);
複製代碼

image.png

是否是很簡單呢?

其實,你們看這東西有沒有感受眼熟,是的沒錯,在ts裏數組就是一個泛型,好比 Array<string>  而且,再引入一個概念就是

類型推測

就這剛纔我們的例子來講 其實直接不傳類型,也是能夠出來的

image.png

ts是很聰明的,他能夠本身根據你傳過來的類型,來推測你是什麼類型,固然了,該簡的簡,稍微複雜點的,好比說一個泛型類,內部聲明個數組,而後有個add方法,第一次是數字,第二次是字符串那他就推測不出來了,過份了確定不行。 其實這個泛型提及來,很是的龐大,這個你們若是感興趣能夠留言或者評論單開一章專門講它,由於這個泛型有一些變種,比方說有多兩個泛型,三個的,分別用在什麼地方,並且還能夠有可選的類型,替換的類型,聯合的類型,交叉的類型,爛七八糟稀奇古怪的東西多了去了,因此。。。有興趣的話你們留言哈~

裝飾器

這個裝飾器其實我我的是很喜歡用的,他能夠直接給class附加一些功能 其實這是有有人可能會有疑問,爲啥我要用這玩意加,我直接加上不就完了麼,還省事,其實能夠想象一下,如今須要用的用戶數據附加到我這個class身上,首先確定一點,挨個加確定是能夠的,可是就是麻煩麼,俗話說得好,懶是推動人類進步最大的動力麼不是。 其實若是瞭解vue 2.x ts版的應該知道,他就是充滿了裝飾器的寫法(順便一說,目前vue3放出來的消息是拋棄了這個裝飾器的寫法了,多是由於這東西暫時是實驗性特性,具體還須要等後面通知)

image.png

看了vue的用法,我們先來看看簡單的裝飾器該怎麼寫

類裝飾器

其實裝飾器就是一個函數,而後直接加一個@符放到class上就能夠了,注意須要注意參數,要麼ts會給你報錯

注意⚠️這個fn只有在類裝飾器的時候纔會只有一個參數,在屬性和方法的時候不同,這個下文會說

function fn(target){

}

@fn
class User {

}
複製代碼

順便一提,若是你用的是vscode或者是什麼其餘的編輯器的話,可能會給你報錯

image.png
這個也就是說剛纔提到的實驗版功能的緣由 看着礙眼的話能夠直接新建一個  tsconfig.json

{
  "compilerOptions": {
      "target": "ES5",
      "experimentalDecorators": true
  }
}
複製代碼

寫上這個,就不會報錯了

其實這個 target 就是我們的類的構造函數

image.png
這個時候是否是就簡單了,我們先直接給target加個屬性,像這樣

function fn(target) {
  console.log(target);
  target.a = 12;
}

@fn
class User {}

console.log(User.a);

複製代碼

這時候看結果會發現報了一個錯,而且結果還出來了,這個就很奇怪

image.png

事實上來講ts是很嚴格的,他必須在初始化的時候就得用,運行時是沒問題,結果也正常,可是人家就是檢測不到 那。。。怎麼辦呢? 很簡單,我們其實直接在類上定義這個屬性就能夠了,像這樣

function fn(target) {
  console.log(target);
  target.a = 12;
}

@fn
class User {
  static a: number;
}

console.log(User.a);

複製代碼

這時候再運行,就不會報錯了

image.png

裝飾器傳參

其實這個裝飾器傳參還稍微有點特殊,這個target(也就是 constructor)會傳到函數return出來的函數內部,而最外層纔是傳進來的參數,來看下代碼

function fn(num:number) {
  return function(constructor: Function){
    constructor.prototype.a = num
  }
}

@fn(12)
class User {
  a: number;
}

let obj = new User();
console.log(obj.a);

複製代碼

運行時會看到

image.png

成功了~ 很開心 而如今,我們直接寫兩個類,就能夠經過傳參數來區分了

function fn(num:number) {
  return function(constructor: Function){
    constructor.prototype.a = num
  }
}

@fn(12)
class User {
  a: number;
}

let obj = new User();
console.log(obj.a);

@fn(5)
class User2 {
  a: number;
}

let obj2 = new User2();
console.log(obj2.a);


複製代碼

結果: 

image.png

真棒,對不對

進階

其實裝飾器到上一步已經能知足大部分人的工做需求了,由於這個東西

  1. 是個實驗性的東西
  2. 工做中其實不多能用到

可是,仍是有點小東西挺有意思,順便來分享一下

前言:我們這個類,確定是不知一個實例對吧,那麼接下來,我們這麼寫,直接一個屬性一點毛病沒有,可是。。。萬一是個json呢,我們來看個例子

image.png

改爲json後,到這步還沒什麼錯,接下來

image.png

GG了,是否是改其中一個屬性另外一個也跟着一起改了呀,這也就是prototype這種方式的不完整,因此千萬別用剛纔的那種裝飾器傳餐來寫真是項目,會出人命的。 可是怎麼辦呢。。。 給誰加都不對,直接說正確作法了,能夠直接把以前的那個類,給它重寫了,可是有一個問題,又不能直接複製代碼再來一套,廢話很少,直接上代碼

function fn(num: number) {
  return function<T extends {new(...arg:any[]):{}}>(constructor: T) {
    return class extends constructor {
      json: object = { a: num };
    };
  };
}

@fn(12)
class User {
  json: {
    a: number;
  };
}

let obj = new User();
obj.json.a = 80;
console.log(obj.json);

let obj2 = new User();
console.log(obj2.json);

複製代碼

image.png

這個時候,就沒問題了~是否是很簡單 固然。。。放下大家手中的刀,可能有人會說,等會等會,這玩意怎麼就忽然變成這一大坨東西了,什麼 <T extends {new(...arg:any[]):{}}> 這都什麼玩意啊,對不對?

實際上是這樣的,我們能夠先拋棄這個來看一眼

image.png
會看到一個錯誤,說這個constructor不是一個一個function, 由於它多是一個 User,多是任何一個類,那怎麼辦呢? 因此,我們須要一個泛型函數

image.png

可是直接寫T也不行,直接說了,我們這個T要繼承一個接口,而且這個接口還不能是一個普通的接口,還須要是一個動態的接口,因此須要動態的建立一個函數  也就是上面代碼的 new () 而且呢,這裏面確定是什麼參數都有,因此全拿出來 new(...args:any[]) 什麼類型都有這種時候給一個 any 就能夠,而且還須要返回一個{},結合起來就是 <T extends {new(...arg:any[]):{}}>  是否是很簡單呢~~~~~

好的,本節ts教程就寫到這了,你們有什麼問題歡迎在評論區評論噢 或者能夠加個人qq和微信,我們一塊兒溝通 qq:

916829411
複製代碼

微信:

Dyy916829411
複製代碼
相關文章
相關標籤/搜索