前言
什麼是 TypeScript?
一起養成寫作習慣!這是我參與「掘金日新計劃 · 4 月更文挑戰」的第21天,點擊查看活動詳情。
typeScript,簡稱 ts,是微軟開發的一種靜態的編程語言,它是 JavaScript 的超集。 那麼它有什麼特別之處呢?
- 簡單來說,js 有的 ts 都有,所有js 代碼都可以在 ts 裡面運行。
- ts 支持類型支持,ts = type +JavaScript。
那麼 ts 和 js 有什麼區別呢?
JavaScript 屬於動態編程語言,而ts 屬於靜態編程語言。
- js:邊解釋邊執行,錯誤只有在運行的時候才能發現
- ts:先編譯再執行,在寫的時候就會發現錯誤了(ts不能直接執行,需要先編譯成 js )
ts 完全支持 js ,可以直接轉換
簡單的去理解
JS 有的, TS 都有, JS 沒有的, TS 也有,畢竟 TS 是 JS 的超集嘛。
TS 的缺點:
- 不能被瀏覽器理解,需要被編譯成 JS
- 有學習成本,寫習慣了 JS 的我們,要上手需要花時間去理解,而且 TS 中有一些概念還是有點難,比如泛型。
什麼是類型註解?
就是給變數添加類型約束,可以顯示標記出代碼中的意外行為,從而降低了發生錯誤的可能性 語法:
let 變數名: 類型 = 初始值
let age: number = 18
這樣寫有啥好處呢?更好地規定了數據的類型,避免了不必要的錯誤。 如果你不小心寫錯了,他會直接提示你類型錯誤哦。是不是很貼心呢?
比如:
let a:number = '1'
代碼中的 : number 就是類型註解。
這個時候是時候介紹下ts的數據基本類型
//字元串
let str: string = "Domesy"
// 數字
let num: number = 7
//布爾
let bool: boolean = true
//symbol
let sym: symbol = symbol();
//bigint
let big: bigint = 10n
//null
let nu: null = null
//undefined
let un: undefined = undefined
需要注意:
- null 和 undefined 兩個類型一旦賦值上,就不能再賦值給任何其他類型
- symbol是獨一無二的,假設再定義一個 sym1,那麼sym === sym1 為 false
引用類型
Array
兩種方式:
- 類型名稱 + []
- Array<數據類型>
let arr1: number[] = [1, 2, 3]
let arr2: Array<number> = [1, 2, 3]
let arr2: Array<number> = [1, 2, '3'] // error
//要想是數字類型或字元串類型,需要使用 |
let arr3: Array<number | string> = [1, 2, '3'] //ok
Tuple(元組)
Tuple 可以說是 Array 的一種特殊情況,針對上面的 arr3,我們看他的類型可以是string也可以是number,但對每個元素沒有作出具體的限制。
那麼 Tuple 的作用就是限制元素的類型並且限制個數的數組,同時 Tuple這個概念值存在於TS,在JS上是不存在的
這裡存在一個問題:在TS中,是允許對 Tuple 擴增的(也就是允許使用 push方法),但在訪問上不允許
let t: [number, string] = [1, '2'] // ok
let t1: [number, string] = [1, 3] // error
let t2: [number, string] = [1] // error
let t3: [number, string] = [1, '1', true] // error
let t5: [number, string] = [1, '2'] // ok
t.push(2)
console.log(t) // [1, '2', 2]
let a = t[0] // ok
let b = t[1] // ok
let c = t[2] // error
另外還有object 和定義函數;
元祖越界問題
let aaa: [string, number] = ['aaa', 5];
// 添加時不會報錯
aaa.push(6);
// 列印整個元祖不會報錯
console.log(aaa); // ['aaa',5,6];
// 列印添加的元素時會報錯
console.log(aaa[2]); // error
什麼是函數類型介面
- 對方法傳入的參數和返回值進行約束
// 注意區別
// 普通的介面
interface discount1{
getNum : (price:number) => number
}
// 函數類型介面
interface discount2{
// 注意:
// 「:」 前面的是函數的簽名,用來約束函數的參數
// ":" 後面的用來約束函數的返回值
(price:number):number
}
let cost:discount2 = function(price:number):number{
return price * .8;
}
// 也可以使用類型別名
type Add = (x: number, y: number) => number
let add: Add = (a: number, b: number) => a + b
什麼是類類型介面
- 如果介面用於一個類的話,那麼介面會表示「行為的抽象」
- 對類的約束,讓類去實現介面,類可以實現多個介面
- 介面只能約束類的公有成員(實例屬性/方法),無法約束私有成員、構造函數、靜態屬性/方法
// 介面可以在面向對象編程中表示為行為的抽象
interface Speakable {
name: string;
// ":" 前面的是函數簽名,用來約束函數的參數
// ":" 後面的用來約束函數的返回值
speak(words: string): void
}
interface Speakable2 {
age: number;
}
class Dog implements Speakable, Speakable2 {
name!: string;
age = 18;
speak(words: string) {
console.log(words);
}
}
let dog = new Dog();
dog.speak('汪汪汪');
什麼是混合類型介面
- 一個對象可以同時做為函數和對象使用
interface FnType {
(getName:string):string;
}
interface MixedType extends FnType{
name:string;
age:number;
}
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
function getCounter(): Counter {
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
繼承 vs 多態
- 繼承:子類繼承父類,子類除了擁有父類的所有特性外,還有一些更具體的特性
- 多態:由繼承而產生了相關的不同的類,對同一個方法可以有不同的響應
這個如果有學過Java的 就非常容易理解,我是之前做Java開發的。
比如:
class Animal {
speak(word: string): string {
return 'Animal: ' + word;
}
}
class Cat extends Animal {
speak(word: string): string {
return 'Cat:' + word;
}
}
class Dog extends Animal {
speak(word: string): string {
return 'Dog:' + word;
}
}
let cat = new Cat();
console.log(cat.speak('hello'));
let dog = new Dog();
console.log(dog.speak('hello'));
什麼是可選鏈運算符的使用
- 可選鏈運算符是一種先檢查屬性是否存在,再嘗試訪問該屬性的運算符,其符號為 ?.
- 如果運算符左側的操作數 ?. 計算為 undefined 或 null,則表達式求值為 undefined 。否則,正常觸發目標屬性訪問、方法或函數調用。
- 可選鏈運算符處於 stage3 階段,使用 @babel/plugin-proposal-optional-chaining 插件可以提前使用,TS 3.7版本正式支持使用,以前的版本會報錯
a?.b;
// 相當於 a == null ? undefined : a.b;
// 如果 a 是 null/undefined,那麼返回 undefined,否則返回 a.b 的值.
a?.[x];
// 相當於 a == null ? undefined : a[x];
// 如果 a 是 null/undefined,那麼返回 undefined,否則返回 a[x] 的值
a?.b();
// 相當於a == null ? undefined : a.b();
// 如果 a 是 null/undefined,那麼返回 undefined
// 如果 a.b 不是函數的話,會拋類型錯誤異常,否則計算 a.b() 的結果
什麼是擴展全局變數的類型
interface String {
// 這裡是擴展,不是覆蓋,所以放心使用
double(): string;
}
String.prototype.double = function () {
return this + '+' + this;
};
console.log('hello'.double());
// 如果加了這個,就會報錯
// export {}
如何編寫 react + ts 版的 HOC
interface Greeting {
name: string;
age: number;
}
const Hello:React.FC<Greeting> = (props) => <h1>Hello {props.name}</h1>;
// 推薦使用第二種
const Hello2 = (props:Greeting) => <h1>Hello {props.name}</h1>;
函數賦值
JS 中變數隨便賦值沒問題,
但在 TS 中函數不能隨便賦值,會報錯的,
TS 進階
這一部分的內容就需要費點腦細胞了,畢竟學習一門語言,還是沒那麼容易的,最好把基礎的內容都理解透徹之後再來學進階。
主要要掌握聯合類型,交叉類型,類型別名,類型保護,類型斷言
抽象類,多態,條件類型等
總結:
我之前學習 TS,是死磕 TS 文檔,非常枯燥,上手難度大,學習效率很低,而且學了就忘。
後來我才發現,我們不一定需要學習那麼多知識,能上手就行,TS 的文檔比較適合入門之後查閱用,而不適合初學者用。基礎類型 · TypeScript中文網 · TypeScript——JavaScript的超集
很多對 TS 學習得很深的人,甚至還在做 TS 類型體操,把 TS 像 LeetCode 一樣刷,作為一個搬磚人,真的沒必要去跟風,畢竟應付日常增刪改查不需要學那麼深。
我的理念一直是,用到了再去學,不要焦慮,人精力有限。比如當我需要去理解 Vue3 的源碼或者設計一個庫的時候,目前的 TS 知識儲備不夠了,再去做做類型體操,也不遲。
最近重學一次 TS,查閱了很多相對官方文檔更通俗易懂的教程,總結了學習中的重點,形成了本文。