TypeScript安利指南

騷年,你感受過debug一年找不到問題,最後發現是變量名寫錯時的絕望嗎?
騷年,你感受過生產線上代碼出現Uncaught TypeError時的恐懼嗎?
騷年,你感受過寫代碼找一萬個文件還找不到方法定義時委屈嗎?

拿起鍵盤,讓我們對謀害生命的代碼拖進垃圾箱!(劃掉)

前言

據瞭解,目前有相當一部分同學不想去學習ts,畢竟沒(xue)時(bu)間(dong)。很不幸兩個月前我也是其中的一員。在看到尤大大都用ts寫vue3了,蠢蠢欲動的我小心翼翼的踏入了這個深坑。在經歷了長達一天的摸爬滾打之後,領悟到了真諦

真香

經過了一段時間的理解之後,寫了這篇文章,旨在給猶豫是否學習或者還在觀望TypeScript的同學做個使用ts的收益分析,希望能夠打動屏幕面前的你。

安利

ts難寫嗎?不難。最簡單的做法三步就搞定。

  1. 找一個js文件
  2. 按下重命名
  3. 把.js改成.ts

大功告成!

打臉

(打人別打臉,還要靠它吃飯的…)

⬇️ ts初體驗
gif0

-ts是什麼

ts是js的超集,意味着js本身的語法在ts裏面也能跑的通。ts一方面是對js加上了很多條條框框的限制,另一方面是拓展了js的一些能力,就像es6提供了那麼多神奇的語法糖一樣。只要按照一定的規則去書寫js,就能享受到ts帶來的好處。

當然因爲現在的ts足夠強大,並且有自家的vscode保駕護航,才方便了我們這些過去想都不(lan)敢(de)想的苦逼程序員。

js改造成ts的工作量很大程度取決於你想對自己的代碼限制的有多細緻,描述的有多完善。最簡單的就像上面說的,改個拓展名就行了(當然很大程度上可能會通過不了各種靜態檢查)。如果你寫的越多,用你代碼的同志就越大可能喜歡你寫的東西。

下面先簡單介紹一下ts語法,便於後面的理解。

-ts語法簡介

// 'xxx: number' 表示聲明一個number類型
const num: number = 123

// 聲明一個函數的參數類型(number以及any)和返回值(void)
function fn (arg1: number, arg2: any): void {
    // todo
}
fn(num, [1,2,3,4])

// 聲明一個接口
interface IPerson {
    name: string // IPerson需要包含一個name屬性,類型是string
    age: number // IPerson需要包含一個age屬性,類型是number
    family: string[] // IPerson需要包含一個family屬性,類型是數組,數組裏面都是string類型的數據
    sex?: '男' | '女' // IPerson可選一個sex屬性,值爲'男'或者'女'或者undefined
}
// 使用IPerson接口定義一個對象,如果對象不符合IPerson的定義,編譯器會飄紅報錯
const person: IPerson = {
    name: '小王',
    age: 12,
    family: ['爹', '娘'],
}

// type類似interface,以下寫法等同用interface聲明IPerson
type IPerson2 = {
    name: string
    age: number
    family: string[]
    sex?: '男' | '女'
}
// 因此可以直接定義過來
const person2: IPerson2 = person

可能有的同學看了上面的介紹,會說:

"要寫這麼多其他代碼,還增加了文件體積,搞個啥子咧"

一般情況下,ts需要編譯成js才能運行。編譯後長這樣:

// 'xxx: number' 表示聲明一個number類型
var num = 123;
// 聲明一個函數的參數類型(number以及any)和返回值(void)
function fn(arg1, arg2) {
    // todo
}
fn(num, [1, 2, 3, 4]);
// 使用IPerson接口定義一個對象,如果對象不符合IPerson的定義,編譯器會飄紅報錯
var person = {
    name: '小王',
    age: 12,
    family: ['爹', '娘'],
};
// 因此可以直接定義過來
var person2 = person;

通過人肉diff,發現編譯後的去掉了ts的所有代碼。

可能就又有同學想問了:

"學這些有啥好處?"

別急,接着往下看🤓

應用場景

這塊介紹ts的幾個應用場景,給點啓發~

-用我的代碼就要聽我的

平時爲了代碼的健壯性,不得不對代碼做很多容錯的操作。

假如成功避免了因爲自己年齡大了而眼睛花了,使用自己寫的方法時這裏漏了一個參數,那裏傳錯了參數類型。
經常會有些不靠譜的使用者,不看你辛辛苦苦耕耘的api文檔,瞎jb傳參。最後出了問題還怪你沒有做好兼容處理,領導羣裏一頓數落。

我們就得像孩子他媽一樣,考慮熊孩子會傳些什麼亂七八糟的東西進來,然後在代碼裏面加上各種分支。

現在用ts,就可以在傳參的時候友好的提示出來“你寫了個什麼玩意”的意思。

-w149

首先用ts定義一個函數

interface IArgs {
    name: string
    age: string
}

function youFoo (arg1: string, arg2: 'a'|'b', arg3: IArgs) {
    // 這裏啥都不幹,你傳參吧
}

假如同事小明這麼寫

youFoo('sss', 'c', {
    name: 'xiaoming',
    age: 18
})

他就會發現哪裏好像不太對

第二個參數要求’a’或者’b’,於是小明默默的改過來了,但是又發現

原來age是要求傳string類型。

於是小明一邊心裏mmp一邊改了過來。

-找文檔

平時在幹活的時候,我們一般喜歡多一個屏幕,可以開個chrome,查查問題找找文檔等。不過經常還得看網速,用搜索去搜api啥的,遇到在鄉下寫代碼,分分鐘有想shi的心。

有了ts,我們就完(da)美(gai)的決掉了這個問題:

首先按照這樣的結構去寫方法:

/**
 * 一個方法:生成錯誤提示信息
 * 
 * @param {string} message 提示信息,比如`you have a error`
 * @param {number | string} code 錯誤碼,數字和字符都行
 * @param {string} type 類型,請寫`demo1`或者`demo2`
 * 
 * [還不懂?點這裏吧](https://www.google.com)
 * 
 * ```js
 * // demo
 * genErrMsg('demo', 10086)
 * 
 * ```
 */
export function genErrMsg (message: string, code: number | string, type?: ('demo1' | 'demo2')): string {
    return (message || `網絡繁忙,請稍候再試`) + (code ? `(${code})` : ``)
}

然後在使用過程中的體驗如下:

在更完善的lib當中,體驗更佳,除了開頭的jquery外,還比如:

-粗心大意

閱讀以下js代碼,
提問:分割線以下的代碼有幾處bug?

// careless.js
let foooo = 1
let fooo = 1
let fooooooo = 1
let foo = 1
let foooooo = 1
let test = 12
const obj = {
    fn1 () {},
    fn2 () {},
    fn4 () {},
}

/*************** 分割線以下的代碼有哪些地方有bug? **************** */

obj.fn3()

console.leg(fooooo)

function test () {
    alert(tast)
}

/*

**

**

***** 答案分界線 *****

**

**

*/

是不是覺得眼睛有點要瞎了?

試試把.js改成.ts

-隱藏的問題

如果說之前的js代碼還能憑眼神立刻看出哪裏不對,那麼下面這些就沒那麼簡單了

閱讀以下js代碼,
提問:代碼有幾處bug?

import * as utils from './utils'

utils.genErrMsg(10086, 'this is error') // 上面提到的genErrMsg函數

let dom = window.document.getElementById('foo')
dom.className = 'add'

/*

**

**

***** 答案分界線 *****

**

**

*/

試試把.js改成.ts

可知問題如下:

1.genErrMsg的第一個參數應該是string

2.getElementById返回值還可能是null

-接口數據不知道

在維護代碼的過程中,可能經常遇到某個接口不知道有啥數據,通常這個時候我們需要去查接口文檔。然而當次數一多,或者後臺大佬一坑起來,改了字段,可能會查到懷疑人生。

如果使用ts,可能手裏的劇本就不一樣了

假如有個接口如下所示

我們針對這個接口寫出瞭如下ts代碼:

interface IPriceData {
    /** 標識 */
    cbf: string
    /** id */
    id: string
    /** 市場價格 */
    m: string
    /** 後臺價 */
    op: string
    /** 前臺價 */
    p: string
}

// 將IPriceData塞進數組裏
type IPriceDataArray = IPriceData[]

function getPrice () {
    // Promise的泛型參數使用了IPriceDataArray類型,then裏面返回的數據就是IPriceDataArray類型
    return new Promise<IPriceDataArray>((resolve, reject) => {
        $.get('https://xxxxxxx/prices/pgets?ids=P_100012&area=&source=', data => {
            resolve(data)
        })
    })
}

當調用getPrice函數時,體驗如下:

以後每次維護這段函數的時候都不需要去看文檔啦。如果後臺突然改了字段,在檢查的過程中我們可以馬上發現問題,然後拿着數據去質問:你tm改了東西讓我來背鍋…(此處省略1萬個字)

-增強後的class和enum

衆所周知,js裏面的class就是個語法糖,想學強類型語言,寫法又是個半吊子。

但是在ts當中,class被增強了(當然還是個語法糖,只不過更甜了)

咱們看圖說話:

vscode中對ts下的共有屬性、私有屬性、保護屬性和靜態屬性開了小竈,實例下只有公有屬性纔會被允許使用和提示出來。

另外ts還提供了enum語法糖:

enum HttpCode {
    /** 成功 */
    '200_OK' = 200,
    /** 已生成了新的資源 */
    '201_Created' = 201,
    /** 請求稍後會被處理 */
    '202_Accepted' = 202,
    /** 資源已經不存在 */
    '204_NoContent' = 204,
    /** 被請求的資源有一系列可供選擇的回饋信息 */
    '300_MultipleChoices' = 300,
    /** 永久性轉移 */
    '301_MovedPermanently' = 301,
    /** 暫時性轉移 */
    '302_MoveTemporarily' = 302,
}

HttpCode['200_OK']
HttpCode[200]

相比簡單對象定義的key-value,只能通過key去訪問value,不能通過value訪問key。但是在enum當中,正反都可以當做key來用。

編譯後的代碼有興趣的同學可以瞭解下~

"use strict";
var HttpCode;
(function (HttpCode) {
    /** 成功 */
    HttpCode[HttpCode["200_OK"] = 200] = "200_OK";
    /** 已生成了新的資源 */
    HttpCode[HttpCode["201_Created"] = 201] = "201_Created";
    /** 請求稍後會被處理 */
    HttpCode[HttpCode["202_Accepted"] = 202] = "202_Accepted";
    /** 資源已經不存在 */
    HttpCode[HttpCode["204_NoContent"] = 204] = "204_NoContent";
    /** 被請求的資源有一系列可供選擇的回饋信息 */
    HttpCode[HttpCode["300_MultipleChoices"] = 300] = "300_MultipleChoices";
    /** 永久性轉移 */
    HttpCode[HttpCode["301_MovedPermanently"] = 301] = "301_MovedPermanently";
    /** 暫時性轉移 */
    HttpCode[HttpCode["302_MoveTemporarily"] = 302] = "302_MoveTemporarily";
})(HttpCode || (HttpCode = {}));
HttpCode['200_OK'];
HttpCode[200];

優點以及不足

通過上面的幾個栗子,大概可以看出使用了ts後,可以獲得以下技能點:

  • 清晰的函數參數/接口屬性,增加了代碼可讀性和可維護性
  • 靜態檢查
  • 生成API文檔
  • 配合現代編輯器,各種提示
  • 活躍的社區

以及對應的技術成本

維護者(包的作者) 使用者
收益 清晰的函數參數/接口屬性 靜態檢查 生成api文檔 清晰的函數參數/接口屬性 配合現代編輯器,各種提示
代價 標記類型 聲明(interface/type) 和某些庫結合的不是很完美(沒錯,說的就是vue 2.x)

這裏提到的vue2.x由於ts先天能力的不足,導致vue的ts語法需要使用class風格(運行時會被轉換回原本的vue構造函數的語法),和我們平時熟悉的vue風格有些差異

這裏是因爲vue的this下的環境比較複雜,對於ide來說需要在運行時才能確定,因此在編寫ts的時候需要手動去設置屬性(比如props,data,methods等)到this下面,非常麻煩。早期ts並不支持手動編寫this的作用域,後來專門爲其設計了一個ThisType的方法。

在上面的代碼裏用了class的寫法,本身所有需要的屬性就在this下,規避了運行時才能確定this下需要的作用域的問題。

另一方面,由於ts提示能力比較侷限,比如在函數場景中,如果數據來源是獨立的對象,體驗就會比較糟糕。

請閱讀以下栗子(這一塊稍微超綱了標題’安利’的範疇,不太理解的新同學可以入坑以後再消化~)

interface IOptions {
    name: string
    age: number
    extra: {
        data: Object
        methods: Object
    }
}

// 參數options要求符合IOptions定義的規則
function sthConstructor (options: IOptions) {}

// options對象當中並沒有任何ts的靜態檢查和提示
const options = {
    name: 'peter',
    age: '13', // error: age應該爲數字
    extra: {
        data: [],
        methods: {}
    }
}
// options飄紅報錯,然而提示內容廢話太多,關鍵信息藏得太深
sthConstructor(options)

在上面的場景,我們希望在options當中能夠獲得完整的ts檢查能力。達成這個目的有三種方法:

1.將options裏面的東西挪進函數當中

2.將options用IObject定義

3.提供一個helper方法

這三種方式當中:

方法1是最簡單的方式,但是在大型項目當中,這樣的寫法反而很少見到。

方法2是維護者常用的方式,但是對於使用者而言,成本較高。因爲使用者需要去lib裏翻到方法對應的type類型,將它import進來。

方法3是個人覺得相對比較好的方式,只要維護者提供一個類似helper的函數包裝一下,就可以獲得對應的提示。是不是很像vue ts的裝飾器?

但上述三種解決方式我覺得都不優雅,這就是ts當前的不足之一。

ts在js中的玩法

TypeScript是和vscode都是微軟的親兒子,他們兄弟倆相互協作肯定會有更多小花樣,甚至你用的只是js文件,也可以享受到。

這裏拋磚引玉列出兩條:

-配置文件自動提示

只要有types文件,所有配置都可以自動提示:

/**
 * webpack配置自動提示
 * 
 * 先安裝對應的types包: `npm i @types/webpack -D`
 * 
 * @type {import('webpack').Configuration}
 */
const config = {
    
}

-js語法檢查

在js中也可以獲得自動提示和靜態檢查。只要在vscode的setting當中勾上Check JS即可。雖然你的js代碼可能會被各種飄紅🤪

⬇️ 之前的例子在js中也可以提示出一些bug了

寫在最後

有的同學會問:我才學js,可以學ts嗎?可以,並且建議,因爲會對js基礎知識加深理解。有用法問題在stackoverflow上搜搜就解決了。

那麼這麼有用的工具,去哪可以學到呢?或許你可以參考下我學習的軌跡:

傳送門–TypeScript 入門教程 (牆裂推薦)

傳送門–爲 Vue3 學點 TypeScript , 體驗 TypeScript

傳送門–一篇樸實的文章帶你30分鐘捋完TypeScript,方法是正反對比

傳送門–stack overflow (牆裂推薦)

傳送門–google

今年ts突然遍地開花,似乎成爲了潮流。各種ts改造、學習教程、心得出現在了各大學習、交友網站上。
有的同學可能也發現了:這不就就是java這類語言玩剩了的東西了嗎?

那年輕的時候誰不都想自由嘛,然而隨着年齡大了都被管的服服帖帖的


感謝 @俊寧 @zenghongtu 評論中指點

文章中原本使用了Array泛型(Array<xxx>)部分表示數組的部分已經全部改爲方括號(xxx[])表示。

原因是3.4後的ts在readonly場景下用Array泛型表示數組會有警告。都改爲使用方括號方式可以避免在複雜場景踩坑。

// readonly修飾只能用於方括號的數組和元組上
let err1: readonly Set<number>; // 錯誤!
let err2: readonly Array<boolean>; // 錯誤!

let okay: readonly boolean[]; // 無錯誤
let okay2: readonly [boolean, string]; // 無錯誤

特此註釋。


如果你覺得這篇內容對你有價值,歡迎點贊並關注我們前端團隊的官網和我們的微信公衆號(WecTeam),每週都有優質文章推送~
WecTeam

發佈了26 篇原創文章 · 獲贊 24 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章