騷年,你感受過debug一年找不到問題,最後發現是變量名寫錯時的絕望嗎?
騷年,你感受過生產線上代碼出現Uncaught TypeError
時的恐懼嗎?
騷年,你感受過寫代碼找一萬個文件還找不到方法定義時委屈嗎?
拿起鍵盤,讓我們對謀害生命的代碼拖進垃圾箱!(劃掉)
前言
據瞭解,目前有相當一部分同學不想去學習ts,畢竟沒(xue)時(bu)間(dong)。很不幸兩個月前我也是其中的一員。在看到尤大大都用ts寫vue3了,蠢蠢欲動的我小心翼翼的踏入了這個深坑。在經歷了長達一天的摸爬滾打之後,領悟到了真諦
經過了一段時間的理解之後,寫了這篇文章,旨在給猶豫是否學習或者還在觀望TypeScript的同學做個使用ts的收益分析,希望能夠打動屏幕面前的你。
安利
ts難寫嗎?不難。最簡單的做法三步就搞定。
- 找一個js文件
- 按下重命名
- 把.js改成.ts
大功告成!
(打人別打臉,還要靠它吃飯的…)
⬇️ ts初體驗
-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,就可以在傳參的時候友好的提示出來“你寫了個什麼玩意”的意思。
首先用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上搜搜就解決了。
那麼這麼有用的工具,去哪可以學到呢?或許你可以參考下我學習的軌跡:
傳送門–爲 Vue3 學點 TypeScript , 體驗 TypeScript
傳送門–一篇樸實的文章帶你30分鐘捋完TypeScript,方法是正反對比
今年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),每週都有優質文章推送~