2022 年了,我纔開始學 TypeScript ,晚嗎?
來自:掘金,作者:嗆再首
鏈接:https://juejin.cn/post/7124117404187099172
前言
其實早在初學前端時,就有大致瞭解過 typescript , 但後面工作中基本 vue2 開發爲主,所以真正能夠接觸到 typescript 的機會其實並不多。
儘管在某些間歇性躊躇滿志的時刻,我也會上 X 站搜索 typescript最新教程
,但都很難堅持過 10 節視頻,又或者刷掘金的時候看到相關文章,我也會麻溜的點贊收藏一條龍,可是到現在也只是在我的收藏夾裏喫灰。
可能不是我太懶,只是這個世界誘惑太多,畢竟刷劇刷短視頻它不香嗎,學習,學個屁!
最近刷掘金看着大夥的年中總結,不是跳槽漲薪了,就是完成了多少 flag。嚇得我從牀上一個激靈蹦起,低頭看着自己日漸肥碩的肚子,仔細想想,這過去的大半年,除了完成日常的工作,我基本回家就開始躺平。
再這麼下去可不行,躺平的日子舒坦歸舒坦但多少感到有點乏味,還是得給自己整點事情幹。
拍拍自己的大肚皮,那就從現在開始,就把我那學了又相當於沒學的 typescript ,重新整起來吧
什麼是 TypeScript
TypeScript 是一種由微軟開發的自由和開源的編程語言。它是 JavaScript 的一個超集,而且本質上向這個語言添加了可選的靜態類型和基於類的面向對象編程。
簡而言之,TypeScript 是 JavaScript 的超集,具有可選的類型並可以編譯爲純 JavaScript。從技術上講 TypeScript 就是具有靜態類型的 JavaScript 。
TypeScript 優缺點
優點
-
增強代碼的可維護性,尤其在大型項目的時候效果顯著
-
友好地在編輯器裏提示錯誤,編譯階段就能檢查類型發現大部分錯誤
-
支持最新的 JavaScript 新特特性
-
周邊生態繁榮,vue3 已全面支持 typescript
缺點
-
需要一定的學習成本
-
和一些插件庫的兼容並不是特別完美,如以前在 vue2 項目裏使用 typescript 就並不是那麼順暢
-
增加前期開發的成本,畢竟你需要寫更多的代碼(但是便於後期的維護)
安裝環境
安裝 typescript
首先,我們可以新建一個空文件夾,用來學習 ts,例如我在文件夾下新建了個 helloworld.ts
npm install -g typescript // 全局安裝 ts
複製代碼
不記得自己是否已經安裝過 typescript 的,可以使用以下命令來驗證:
tsc -v
複製代碼
如果出現版本,則說明已經安裝成功
Version 4.6.3
複製代碼
生成 tsconfig.json 配置文件
tsc --init
複製代碼
執行命令後我們就可以看到生成了一個 tsconfig.json 文件,裏面有一些配置信息,我們暫時先按下不表
在我們helloworld.ts
文件中, 隨便寫點什麼
const s:string = "彼時彼刻,恰如此時此刻";
console.log(s);
複製代碼
控制檯執行 tsc helloworld.ts
命令,目錄下生成了一個同名的 helloworld.js 文件,代碼如下
var s = "彼時彼刻,恰如此時此刻";
console.log(s);
複製代碼
通過 tsc 命令,發現我們的 typescript 代碼被轉換成了熟悉的 js 代碼
我們接着執行
node helloworld.js
複製代碼
即可看到輸出結果
安裝 ts-node
那麼通過我們上面的一通操作,我們知道了運行 tsc 命令就可以編譯生成一個 js 文件,但是如果每次改動我們都要手動去執行編譯,然後再通過 node 命令才能查看運行結果豈不是太麻煩了。
而 ts-node 正是來解決這個問題的
npm i -g ts-node // 全局安裝ts-node
複製代碼
有了這個插件,我們就可以直接運行. ts 文件了
我們試一下
ts-node helloworld.ts
複製代碼
可以看到我們的打印結果已經輸出
後續我們的示例都可以通過這個命令來進行驗證
接下來我們就可以正式進入到 typescript 的學習之旅了
TypeScript 基礎類型
Boolean 類型
const flag: boolean = true;
複製代碼
Number 類型
const count: number = 10;
複製代碼
String 類型
let name: string = "樹哥";
複製代碼
Enum 類型
枚舉類型用於定義數值集合,使用枚舉我們可以定義一些帶名字的常量。使用枚舉可以清晰地表達意圖或創建一組有區別的用例。,如週一到週日,方位上下左右等
- 普通枚舉
初始值默認爲 0 其餘的成員會會按順序自動增長 可以理解爲數組下標
enum Color {
RED,
PINK,
BLUE,
}
const red: Color = Color.RED;
console.log(red); // 0
複製代碼
- 設置初始值
enum Color {
RED = 2,
PINK,
BLUE,
}
const pink: Color = Color.PINK;
console.log(pink); // 3
複製代碼
- 字符串枚舉
enum Color {
RED = "紅色",
PINK = "粉色",
BLUE = "藍色",
}
const pink: Color = Color.PINK;
console.log(pink); // 粉色
複製代碼
- 常量枚舉
使用 const 關鍵字修飾的枚舉,常量枚舉與普通枚舉的區別是,整個枚舉會在編譯階段被刪除 我們可以看下編譯之後的效果
const enum Color {
RED,
PINK,
BLUE,
}
const color: Color[] = [Color.RED, Color.PINK, Color.BLUE];
console.log(color); //[0, 1, 2]
//編譯之後的js如下:
var color = [0 /* RED */, 1 /* PINK */, 2 /* BLUE */];
// 可以看到我們的枚舉並沒有被編譯成js代碼 只是把color這個數組變量編譯出來了
複製代碼
Array 類型
對數組類型的定義有兩種方式:
const arr: number[] = [1,2,3];
const arr2: Array<number> = [1,2,3];
複製代碼
元組(tuple)類型
上面數組類型的方式,只能定義出內部全爲同種類型的數組。對於內部不同類型的數組可以使用元組類型來定義
元組( Tuple )表示一個已知數量和類型的數組, 可以理解爲他是一種特殊的數組
const tuple: [number, string] = [1, "zhangmazi"];
複製代碼
需要注意的是,元組類型只能表示一個已知元素數量和類型的數組,長度已指定,越界訪問會提示錯誤。例如,一個數組中可能有多種類型,數量和類型都不確定,那就直接 any[]。
undefined 和 null
默認情況下 null 和 undefined 是所有類型的子類型。也就是說你可以把 null 和 undefined 賦值給其他類型。
let a: undefined = undefined;
let b: null = null;
let str: string = 'zhangmazi';
str = null; // 編譯正確
str = undefined; // 編譯正確
複製代碼
如果你在 tsconfig.json 指定了 "strictNullChecks":true ,即開啓嚴格模式後, null 和 undefined 只能賦值給 void 和它們各自的類型。
// 啓用 --strictNullChecks
let x: number;
x = 1; // 編譯正確
x = undefined; // 編譯錯誤
x = null; // 編譯錯誤
複製代碼
any 類型
any 會跳過類型檢查器對值的檢查,任何值都可以賦值給 any 類型
let value: any = 1;
value = "zhangmazi"; // 編譯正確
value = []; // 編譯正確
value = {};// 編譯正確
複製代碼
void 類型
void 意思就是無效的, 一般只用在函數上,告訴別人這個函數沒有返回值。
function sayHello(): void {
console.log("hello 啊,樹哥!");
}
複製代碼
never 類型
never 類型表示的是那些永不存在的值的類型。例如 never 類型是那些總是會拋出異常或根本就不會有返回值的函數表達式或箭頭函數表達式的返回值類型
值會永不存在的兩種情況:
-
1 如果一個函數執行時拋出了異常,那麼這個函數永遠不存在返回值(因爲拋出異常會直接中斷程序運行,這使得程序運行不到返回值那一步,即具有不可達的終點,也就永不存在返回了)
-
2 函數中執行無限循環的代碼(死循環),使得程序永遠無法運行到函數返回值那一步,永不存在返回。
// 異常
function error(msg: string): never { // 編譯正確
throw new Error(msg);
}
// 死循環
function loopForever(): never { // 編譯正確
while (true) {};
}
複製代碼
Unknown 類型
unknown 與 any 一樣,所有類型都可以分配給 unknown:
let value: unknown = 1;
value = "zhangmazi"; // 編譯正確
value = false; // 編譯正確
複製代碼
unknown 與 any 的最大區別是:
任何類型的值可以賦值給 any,同時 any 類型的值也可以賦值給任何類型。unknown 任何類型的值都可以賦值給它,但它只能賦值給 unknown 和 any
對象類型
這裏所說的對象類型,就是我們常說的函數、{}、數組、類
object, Object 和 {} 類型
- object object 類型用於表示所有的非原始類型,即我們不能把 number、string、boolean、symbol 等 原始類型賦值給 object。在嚴格模式下,null 和 undefined 類型也不能賦給 object。
let object: object;
object = 1; // 報錯
object = "a"; // 報錯
object = true; // 報錯
object = null; // 報錯
object = undefined; // 報錯
object = {}; // 編譯正確
複製代碼
- Object
大 Object 代表所有擁有 toString、hasOwnProperty 方法的類型 所以所有原始類型、非原始類型都可以賦給 Object(嚴格模式下 null 和 undefined 不可以)
let bigObject: Object;
object = 1; // 編譯正確
object = "a"; // 編譯正確
object = true; // 編譯正確
object = null; // 報錯
ObjectCase = undefined; // 報錯
ObjectCase = {}; // ok
複製代碼
- {}
{} 空對象類型和大 Object 一樣 也是表示原始類型和非原始類型的集合
類
在 TypeScript 中,我們通過 Class 關鍵字來定義一個類
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
sayHi(): void {
console.log(`Hi, ${this.name}`);
}
}
複製代碼
數組
const flag1: number[] = [1, 2, 3];
const flag2: Array<number> = [1, 2, 3];
複製代碼
函數
函數聲明
function add(x: number, y: number): number {
return x + y;
}
複製代碼
函數表達式
const add = function(x: number, y: number): number {
return x + y;
}
複製代碼
接口定義函數
interface Add {
(x: number, y: number): number;
}
複製代碼
可選參數
function add(x: number, y?: number): number {
return y ? x + y : x;
}
複製代碼
默認參數
function add(x: number, y: number = 0): number {
return x + y;
}
複製代碼
剩餘參數
function add(...numbers: number[]): number {
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
sum += numbers[i];
}
return sum;
}
複製代碼
函數重載
函數重載或方法重載是使用相同名稱和不同參數數量或類型創建多個方法的一種能力。
function add(x: number, y: number): number;
function add(x: string, y: string): string;
function add(x: any, y: any): any {
return x + y;
}
複製代碼
上面示例中,我們給同一個函數提供多個函數類型定義,從而實現函數的重載
需要注意的是:
函數重載真正執行的是同名函數最後定義的函數體 在最後一個函數體定義之前全都屬於函數類型定義 不能寫具體的函數實現方法 只能定義類型
具體示例原理可參考 [1]
類型推論
如果沒有明確的指定類型,那麼 TypeScript 會依照類型推論的規則推斷出一個類型。
let x = 1;
x = true; // 報錯
複製代碼
上面的代碼等價於
let x: number = 1;
x = true; // 報錯
複製代碼
通過上述示例我們可以看出,我們沒有給 x 指定明確類型的時候,typescript 會推斷出 x 的類型是 number。
而如果定義的時候沒有賦值,不管之後有沒有賦值,都會被推斷成 any 類型而完全不被類型檢查:
let x;
x = 1; // 編譯正確
x = true; // 編譯正確
複製代碼
類型斷言
某些情況下,我們可能比 typescript 更加清楚的知道某個變量的類型,所以我們可能希望手動指定一個值的類型
類型斷言有兩種方式
- 尖括號寫法
let str: any = "to be or not to be";
let strLength: number = (<string>str).length;
複製代碼
- as 寫法
let str: any = "to be or not to be";
let strLength: number = (str as string).length;
複製代碼
非空斷言
在上下文中當類型檢查器無法斷定類型時,可以使用綴表達式操作符 !
進行斷言操作對象是非 null 和非 undefined 的類型,即 x! 的值不會爲 null 或 undefined
let user: string | null | undefined;
console.log(user!.toUpperCase()); // 編譯正確
console.log(user.toUpperCase()); // 錯誤
複製代碼
確定賦值斷言
let value:number
console.log(value); // Variable 'value' is used before being assigned.
複製代碼
我們定義了變量, 沒有賦值就使用,則會報錯
通過 let x!: number; 確定賦值斷言,TypeScript 編譯器就會知道該屬性會被明確地賦值。
let value!:number
console.log(value); // undefined 編譯正確
複製代碼
聯合類型
聯合類型用|
分隔,表示取值可以爲多種類型中的一種
let status:string|number
status='to be or not to be'
status=1
複製代碼
類型別名
類型別名用來給一個類型起個新名字。它只是起了一個新名字,並沒有創建新類型。類型別名常用於聯合類型。
type count = number | number[];
function hello(value: count) {}
複製代碼
交叉類型
交叉類型就是跟聯合類型相反,用&
操作符表示,交叉類型就是兩個類型必須存在
interface IpersonA{
name: string,
age: number
}
interface IpersonB {
name: string,
gender: string
}
let person: IpersonA & IpersonB = {
name: "師爺",
age: 18,
gender: "男"
};
複製代碼
person 即是 IpersonA 類型,又是 IpersonB 類型
注意:交叉類型取的多個類型的並集,但是如果 key 相同但是類型不同,則該 key 爲 never 類型
interface IpersonA {
name: string
}
interface IpersonB {
name: number
}
function testAndFn(params: IpersonA & IpersonB) {
console.log(params)
}
testAndFn({name: "黃老爺"}) // error TS2322: Type 'string' is not assignable to type 'never'.
複製代碼
類型守衛
類型保護是可執行運行時檢查的一種表達式,用於確保該類型在一定的範圍內。換句話說,類型保護可以保證一個字符串是一個字符串,儘管它的值也可以是一個數值。類型保護與特性檢測並不是完全不同,其主要思想是嘗試檢測屬性、方法或原型,以確定如何處理值。
換句話說:類型守衛是運行時檢查,確保一個值在所要類型的範圍內
目前主要有四種的方式來實現類型保護:
- 1、in 關鍵字
interface InObj1 {
a: number,
x: string
}
interface InObj2 {
a: number,
y: string
}
function isIn(arg: InObj1 | InObj2) {
// x 在 arg 打印 x
if ('x' in arg) console.log('x')
// y 在 arg 打印 y
if ('y' in arg) console.log('y')
}
isIn({a:1, x:'xxx'});
isIn({a:1, y:'yyy'});
複製代碼
- 2、typeof 關鍵字
function isTypeof( val: string | number) {
if (typeof val === "number") return 'number'
if (typeof val === "string") return 'string'
return '啥也不是'
}
複製代碼
typeof 只支持:typeof 'x' === 'typeName' 和 typeof 'x' !== 'typeName',x 必須是'number', 'string', 'boolean', 'symbol'。
- 3、instanceof
function creatDate(date: Date | string){
console.log(date)
if(date instanceof Date){
date.getDate()
}else {
return new Date(date)
}
}
複製代碼
- 4、自定義類型保護的類型謂詞
function isNumber(num: any): num is number {
return typeof num === 'number';
}
function isString(str: any): str is string{
return typeof str=== 'string';
}
複製代碼
接口
我們使用接口來定義對象的類型。接口是對象的狀態 (屬性) 和行爲 (方法) 的抽象(描述)
簡單理解就是:爲我們的代碼提供一種約定
我們使用關鍵字 interface 來聲明接口
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25
};
複製代碼
我們定義了一個接口 Person,接着定義了一個變量 tom,它的類型是 Person。這樣,我們就約束了 tom 的形狀必須和接口 Person 一致。
接口一般首字母大寫。(當然挺多人也習慣 I 大寫字母開頭,用來表示這是一個接口)
設置接口可選 | 只讀
interface Person {
readonly name: string;
age?: number;
}
複製代碼
-
可選屬性,我們最常見的使用情況是,不確定這個參數是否會傳,或者存在。
-
只讀屬性用於限制只能在對象剛剛創建的時候修改其值。此外 TypeScript 還提供了 ReadonlyArray 類型,它與 Array 相似,只是把所有可變方法去掉了,因此可以確保數組創建後再也不能被修改。
索引簽名
有時候我們希望一個接口中除了包含必選和可選屬性之外,還允許有其他的任意屬性,這時我們可以使用 索引簽名 的形式來滿足上述要求。
需要注意的是,一旦定義了任意屬性,那麼確定屬性和可選屬性的類型都必須是它的類型的子集
interface Person {
name: string;
age?: number;
[prop: string]: any; // propName字段必須是 string類型 or number類型。 值是any類型,也就是任意的
}
const p1 = { name: "張麻子" };
const p2 = { name: "樹哥", age: 28 };
const p3 = { name: "湯師爺", sex: 1 }
複製代碼
我們規定 以 string 類型的值來索引,索引到的是一個 any 類型的值
接口與類型別名的區別
實際上,在大多數的情況下使用接口類型和類型別名的效果等價,但是在某些特定的場景下這兩者還是存在很大區別。
TypeScript 的核心原則之一是對值所具有的結構進行類型檢查。而接口的作用就是爲這些類型命名和爲你的代碼或第三方代碼定義數據模型。
type(類型別名) 會給一個類型起個新名字。type 有時和 interface 很像,但是可以作用於原始值(基本類型),聯合類型,元組以及其它任何你需要手寫的類型。起別名不會新建一個類型 - 它創建了一個新名字來引用那個類型。給基本類型起別名通常沒什麼用,儘管可以做爲文檔的一種形式使用。
接口和類型別名都可以用來描述對象或函數的類型,只是語法不同
type MyTYpe = {
name: string;
say(): void;
}
interface MyInterface {
name: string;
say(): void;
}
複製代碼
都允許擴展
- interface 用
extends
來實現擴展
interface MyInterface {
name: string;
say(): void;
}
interface MyInterface2 extends MyInterface {
sex: string;
}
let person:MyInterface2 = {
name:'樹哥',
sex:'男',
say(): void {
console.log("hello 啊,樹哥!");
}
}
複製代碼
- type 使用
&
實現擴展
type MyType = {
name:string;
say(): void;
}
type MyType2 = MyType & {
sex:string;
}
let value: MyType2 = {
name:'樹哥',
sex:'男',
say(): void {
console.log("hello 啊,樹哥!");
}
}
複製代碼
不同點
- type 可以聲明基本數據類型別名 / 聯合類型 / 元組等,而 interface 不行
// 基本類型別名
type UserName = string;
type UserName = string | number;
// 聯合類型
type Animal = Pig | Dog | Cat;
type List = [string, boolean, number];
複製代碼
- interface 能夠合併聲明,而 type 不行
interface Person {
name: string
}
interface Person {
age: number
}
// 此時Person同時具有name和age屬性
複製代碼
泛型
泛型是指在定義函數、接口或類的時候,不預先指定具體的類型,而在使用的時候再指定類型的一種特性。
舉個例子,比如我們現在有個這樣的需求,我們要實現一個這樣的函數,函數的參數可以是任何值,返回值就是將參數原樣返回,並且參數的類型是 string,函數返回類型就爲 string?
你很容易寫下:
function getValue(arg:string):string {
return arg;
}
複製代碼
現在需求有變,需要返回一個 number 類型的值,你會說,聯合類型就完事了:
function getValue(arg:string | number):string | number {
return arg;
}
複製代碼
但是這樣又有一個問題,就是如果我們需要返回一個 boolean 類型,string 數組甚至任意類型呢,難道有多少個就寫多少個聯合類型?
是的,我們直接用 any 就行了!
function getValue(arg:any):any {
return arg;
}
複製代碼
儘管 any 大法好,很多時候 any 也確實能夠解決不少問題,但是這樣也不符合我們的需求了,傳入和返回都是 any 類型,傳入和返回並沒有統一
作爲一個騷有最求的程序員,我們還能不能有其他解決辦法呢?
這個時候就要祭出我們的泛型了
基本使用
泛型是指在定義函數、接口或類的時候,不預先指定具體的類型,而在使用的時候再指定類型的一種特性
上面的需求,我們如果用泛型來解決的話:
function getValue<T>(arg:T):T {
return arg;
}
複製代碼
泛型的語法是尖括號 <>
裏面寫類型參數,一般用 T
來表示第一個類型變量名稱,其實它可以用任何有效名稱來代替, 比如我們用NIUBI
也是編譯正常的
泛型就像一個佔位符一個變量,在使用的時候我們可以將定義好的類型像參數一樣傳入,原封不動的輸出
使用
我們有兩種方式來使用:
- 定義要使用的類型,比如:
getValue<string>('樹哥'); // 定義 T 爲 string 類型
複製代碼
- 利用 typescript 的類型推斷,比如:
getValue('樹哥') // 自動推導類型爲 string
複製代碼
多個參數
其實並不是只能定義一個類型變量,我們可以引入希望定義的任何數量的類型變量。比如我們引入一個新的類型變量 U
function getValue<T, U>(arg:[T,U]):[T,U] {
return arg;
}
// 使用
const str = getValue(['樹哥', 18]);
複製代碼
泛型約束
在函數內部使用泛型變量的時候,由於事先不知道它是哪種類型,所以不能隨意的操作它的屬性或方法:
function getLength<T>(arg:T):T {
console.log(arg.length); // 報錯,不能調用 length 屬性
}
複製代碼
因爲泛型 T 不一定包含屬性 length,那麼我想 getLength 這個函數只允許傳入包含 length 屬性的變量,該怎麼做呢
這時,我們可以使用extends
關鍵字來對泛型進行約束
interface Lengthwise {
length: number;
}
function getLength<T extends Lengthwise>(arg:T):T {
console.log(arg.length);
return arg;
}
複製代碼
使用:
const str = getLength('樹哥')
const arr = getLength([1,2,3])
const obj = getLength({ length: 5 })
複製代碼
這裏可以看出,不管你是 str,arr 還是 obj,只要具有 length 屬性,都可以
具體參考輕鬆拿下 TS 泛型 [2]
泛型接口
在定義接口的時候指定泛型
interface KeyValue<T,U> {
key: T;
value: U;
}
const person1:KeyValue<string,number> = {
key: '樹哥',
value: 18
}
const person2:KeyValue<number,string> = {
key: 20,
value: '張麻子'
}
複製代碼
泛型類
class Test<T> {
value: T;
add: (x: T, y: T) => T;
}
let myTest = new Test<number>();
myTest.value = 0;
myTest.add = function (x, y) {
return x + y;
};
複製代碼
泛型類型別名
type Cart<T> = { list: T[] } | T[];
let c1: Cart<string> = { list: ["1"] };
let c2: Cart<number> = [1];
複製代碼
泛型參數的默認類型
我們可以爲泛型中的類型參數指定默認類型。當使用泛型時沒有在代碼中直接指定類型參數,從實際值參數中也無法推測出時,這個默認類型就會起作用。有點 js 裏函數默認參數的意思。
function createArray<T = string>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
複製代碼
泛型工具類型
- typeof
關鍵詞除了做類型保護,還可以從實現推出類型,
//先定義變量,再定義類型
let p1 = {
name: "樹哥",
age: 18,
gender: "male",
};
type People = typeof p1;
function getName(p: People): string {
return p.name;
}
getName(p1);
複製代碼
- keyof
可以用來獲取一個對象接口中的所有 key 值
interface Person {
name: string;
age: number;
gender: "male" | "female";
}
type PersonKey = keyof Person; //type PersonKey = 'name'|'age'|'gender';
function getValueByKey(p: Person, key: PersonKey) {
return p[key];
}
let val = getValueByKey({ name: "樹哥", age: 18, gender: "male" }, "name");
console.log(val); // 樹哥
複製代碼
- in
用來遍歷枚舉類型:
type Keys = "a" | "b" | "c"
type Obj = {
[p in Keys]: any
} // -> { a: any, b: any, c: any }
複製代碼
- infer
在條件類型語句中,可以用 infer 聲明一個類型變量並且對它進行使用。
type ReturnType<T> = T extends (
...args: any[]
) => infer R ? R : any;
複製代碼
infer R 就是聲明一個變量來承載傳入函數簽名的返回值類型,簡單說就是用它取到函數返回值的類型方便之後使用。
- extends
有時候我們定義的泛型不想過於靈活或者說想繼承某些類等,可以通過 extends 關鍵字添加泛型約束。
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
複製代碼
現在這個泛型函數被定義了約束,因此它不再是適用於任意類型:
loggingIdentity(3); // Error, number doesn't have a .length property
複製代碼
當我們傳入合法的類型的值,即包含 length 屬性的值時:
loggingIdentity({length: 10, name: '張麻子'}); // 編譯正確
複製代碼
- 索引訪問操作符
使用 []
操作符可以進行索引訪問:
interface Person {
name: string;
age: number;
}
type x = Person["name"]; // x is string
複製代碼
內置工具類型
- Required
將類型的屬性變成必選
interface Person {
name?: string,
age?: number,
hobby?: string[]
}
const user: Required<Person> = {
name: "樹哥",
age: 18,
hobby: ["code"]
}
複製代碼
- Partial
與 Required 相反,將所有屬性轉換爲可選屬性
interface Person {
name: string,
age: number,
}
const shuge:Person = {
name:'樹哥'
} // error Property 'age' is missing in type '{ name: string; }' but required in type 'Person'.
複製代碼
從上面知道,如果必傳而我們少穿傳了的話,就會報錯
我們使用 Partial 將其變爲可選
type User = Partial<Person>
const shuge: User={
name:'樹哥'
} // 編譯正確
複製代碼
- Exclude
Exclude<T, U>
的作用是將某個類型中屬於另一個的類型移除掉, 剩餘的屬性構成新的類型
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number
複製代碼
- Extract
和 Exclude 相反,Extract<T,U>
從 T 中提取出 U。
type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T1 = Extract<string | number | (() => void), Function>; // () =>void
複製代碼
適用於:並集類型
- Readonly
把數組或對象的所有屬性值轉換爲只讀的,這就意味着這些屬性不能被重新賦值。
interface Person {
name: string;
age: number;
gender?: "male" | "female";
}
let p: Readonly<Person> = {
name: "hello",
age: 10,
gender: "male",
};
p.age = 11; // error Cannot assign to 'age' because it is a read-only property.
複製代碼
- Record
Record<K extends keyof any, T> 的作用是將 K 中所有的屬性的值轉化爲 T 類型。
type Property = 'key1'|'key2'
type Person = Record<Property, string>;
const p: Person = {
key1: "hello 啊",
key2: "樹哥",
};
複製代碼
- Pick
從某個類型中挑出一些屬性出來
type Person = {
name: string;
age:number;
gender:string
}
type P1 = Pick<Person, "name" | "age">; // { name: string; age: number; }
const user:P1={
name:'樹哥',
age:18
}
複製代碼
- Omit
與 Pick 相反,Omit<T,K>
從 T 中取出除去 K 的其他所有屬性。
interface Person {
name: string,
age: number,
gender: string
}
type P1 = Omit<Person, "age" | "gender">
const user:P1 = {
name: '樹哥'
}
複製代碼
- NonNullable
去除類型中的 null
和 undefined
type P1 = NonNullable<string | number | undefined>; // string | number
type P2 = NonNullable<string[] | null | undefined>; // string[]
複製代碼
- ReturnType
用來得到一個函數的返回值類型
type Func = (value: string) => string;
const test: ReturnType<Func> = "1";
複製代碼
- Parameters
用於獲得函數的參數類型所組成的元組類型。
type P1 = Parameters<(a: number, b: string) => void>; // [number, string]
複製代碼
- InstanceType
返回構造函數類型 T 的實例類型
class C {
x = 0;
y = 0;
}
type D = InstanceType<typeof C>; // C
複製代碼
tsconfig.json
在文章開頭環境安裝部分,記得我們有生成一個 tsconfig.json 文件,那麼這個文件究竟有什麼用呢
tsconfig.json 是 TypeScript 項目的配置文件。
tsconfig.json 包含 TypeScript 編譯的相關配置,通過更改編譯配置項,我們可以讓 TypeScript 編譯出 ES6、ES5、node 的代碼。
重要字段
-
files - 設置要編譯的文件的名稱;
-
include - 設置需要進行編譯的文件,支持路徑模式匹配;
-
exclude - 設置無需進行編譯的文件,支持路徑模式匹配;
-
compilerOptions - 設置與編譯流程相關的選項。
compilerOptions 選項
{
"compilerOptions": {
/* 基本選項 */
"target": "es5", // 指定 ECMAScript 目標版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
"module": "commonjs", // 指定使用模塊: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
"lib": [], // 指定要包含在編譯中的庫文件
"allowJs": true, // 允許編譯 javascript 文件
"checkJs": true, // 報告 javascript 文件中的錯誤
"jsx": "preserve", // 指定 jsx 代碼的生成: 'preserve', 'react-native', or 'react'
"declaration": true, // 生成相應的 '.d.ts' 文件
"sourceMap": true, // 生成相應的 '.map' 文件
"outFile": "./", // 將輸出文件合併爲一個文件
"outDir": "./", // 指定輸出目錄
"rootDir": "./", // 用來控制輸出目錄結構 --outDir.
"removeComments": true, // 刪除編譯後的所有的註釋
"noEmit": true, // 不生成輸出文件
"importHelpers": true, // 從 tslib 導入輔助工具函數
"isolatedModules": true, // 將每個文件做爲單獨的模塊 (與 'ts.transpileModule' 類似).
/* 嚴格的類型檢查選項 */
"strict": true, // 啓用所有嚴格類型檢查選項
"noImplicitAny": true, // 在表達式和聲明上有隱含的 any類型時報錯
"strictNullChecks": true, // 啓用嚴格的 null 檢查
"noImplicitThis": true, // 當 this 表達式值爲 any 類型的時候,生成一個錯誤
"alwaysStrict": true, // 以嚴格模式檢查每個模塊,並在每個文件里加入 'use strict'
/* 額外的檢查 */
"noUnusedLocals": true, // 有未使用的變量時,拋出錯誤
"noUnusedParameters": true, // 有未使用的參數時,拋出錯誤
"noImplicitReturns": true, // 並不是所有函數里的代碼都有返回值時,拋出錯誤
"noFallthroughCasesInSwitch": true, // 報告 switch 語句的 fallthrough 錯誤。(即,不允許 switch 的 case 語句貫穿)
/* 模塊解析選項 */
"moduleResolution": "node", // 選擇模塊解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
"baseUrl": "./", // 用於解析非相對模塊名稱的基目錄
"paths": {}, // 模塊名到基於 baseUrl 的路徑映射的列表
"rootDirs": [], // 根文件夾列表,其組合內容表示項目運行時的結構內容
"typeRoots": [], // 包含類型聲明的文件列表
"types": [], // 需要包含的類型聲明文件名列表
"allowSyntheticDefaultImports": true, // 允許從沒有設置默認導出的模塊中默認導入。
/* Source Map Options */
"sourceRoot": "./", // 指定調試器應該找到 TypeScript 文件而不是源文件的位置
"mapRoot": "./", // 指定調試器應該找到映射文件而不是生成文件的位置
"inlineSourceMap": true, // 生成單個 soucemaps 文件,而不是將 sourcemaps 生成不同的文件
"inlineSources": true, // 將代碼與 sourcemaps 生成到一個文件中,要求同時設置了 --inlineSourceMap 或 --sourceMap 屬性
/* 其他選項 */
"experimentalDecorators": true, // 啓用裝飾器
"emitDecoratorMetadata": true // 爲裝飾器提供元數據的支持
}
}
複製代碼
參考
一份不可多得的 TS 學習指南 [5]
TS 中文文檔 [6]
2021 typescript 史上最強學習入門文章 [7]
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/YE5h-rT4S6QZbQRqYEiInw