千鋒教育前端TypeScript入門視頻教程(陸神頂配版TS入門教程)

一、TypeScript概念
TypeScript 是一種由微軟開發(fā)的自由和開源的編程語言。它是 JavaScript 的一個超集,而且本質(zhì)上向這個語言添加了可選的靜態(tài)類型和基于類的面向?qū)ο缶幊獭?/p>
簡而言之,TypeScript是JavaScript的超集,具有可選的類型并可以編譯為純JavaScript。從技術(shù)上講TypeScript就是具有靜態(tài)類型的 JavaScript 。
二、TypeScript優(yōu)缺點
優(yōu)點
·???????增強(qiáng)代碼的可維護(hù)性,尤其在大型項目的時候效果顯著
·???????友好地在編輯器里提示錯誤,編譯階段就能檢查類型發(fā)現(xiàn)大部分錯誤
·???????支持最新的JavaScript新特特性
·???????周邊生態(tài)繁榮,vue3已全面支持 typescript
缺點
·???????需要一定的學(xué)習(xí)成本
·???????和一些插件庫的兼容并不是特別完美,如以前在 vue2 項目里使用 typescript就并不是那么順暢
·???????增加前期開發(fā)的成本,畢竟你需要寫更多的代碼(但是便于后期的維護(hù))
三、TypeScript安裝
首先,我們可以新建一個空文件夾,用來學(xué)習(xí) ts,例如我在文件夾下新建了個?helloworld.ts
npm install -g typescript // 全局安裝 ts
不記得自己是否已經(jīng)安裝過 typescript 的,可以使用以下命令來驗證:
tsc -v
如果出現(xiàn)版本,則說明已經(jīng)安裝成功
Version 4.6.3
生成 tsconfig.json 配置文件
tsc --init
執(zhí)行命令后我們就可以看到生成了一個 tsconfig.json 文件,里面有一些配置信息,我們暫時先按下不表
在我們helloworld.ts文件中,隨便寫點什么
1. const s:string = "你好,TS";
2. console.log(s);
控制臺執(zhí)行?tsc helloworld.ts?命令,目錄下生成了一個同名的 helloworld.js 文件,代碼如下
1. var s = "你好,TS ";
2. console.log(s);
在控制臺輸入下面的命令開啟了監(jiān)聽模式 實時監(jiān)測app.ts 同步到app.js
tsc app.ts -w
通過tsc命令,發(fā)現(xiàn)我們的typescript代碼被轉(zhuǎn)換成了熟悉的js代碼
我們接著執(zhí)行
node helloworld.js
即可看到輸出結(jié)果
四、TypeScript基礎(chǔ)類型
js的數(shù)據(jù)類型?
number 、string、 null 、 undefined、 boolean、 object、?function、 array
ts新增的數(shù)據(jù)類型
any任意類型; enum 枚舉 聯(lián)合類型 字面量類型 unknown void
Boolean 類型
聲明一個變量flag,同時指定它的類型為boolean
const flag: boolean = true;
Number 類型
聲明一個變量a,同時指定它的類型為number
const count: number = 10;
String 類型
聲明一個變量str,同時指定它的類型為string
let name: string = "樹哥";
Enum?枚舉類型
枚舉類型用于定義數(shù)值集合,使用枚舉我們可以定義一些帶名字的常量。 使用枚舉可以清晰地表達(dá)意圖或創(chuàng)建一組有區(qū)別的用例。,如周一到周日,方位上下左右等
(1)普通枚舉
1. enum Color {
2. ??RED,
3. ??PINK,
4. ??BLUE,
5. }
6. ?
7. const red: Color = Color.RED;
8. console.log(red); // 0
初始值默認(rèn)為 0 其余的成員會會按順序自動增長 可以理解為數(shù)組下標(biāo)
(2)設(shè)置初始值
1. enum Color {
2. ??RED = 2,
3. ??PINK,
4. ??BLUE,
5. }
6. const pink: Color = Color.PINK;
7. console.log(pink); // 3
(3)字符串枚舉
1. enum Color {
2. ??RED = "紅色",
3. ??PINK = "粉色",
4. ??BLUE = "藍(lán)色",
5. }
6. ?
7. const pink: Color = Color.PINK;
8. console.log(pink); // 粉色
(4)常量枚舉
使用 const 關(guān)鍵字修飾的枚舉,常量枚舉與普通枚舉的區(qū)別是,整個枚舉會在編譯階段被刪除 我們可以看下編譯之后的效果
1. const enum Color {
2. ??RED,
3. ??PINK,
4. ??BLUE,
5. }
6. ?
7. const color: Color[] = [Color.RED, Color.PINK, Color.BLUE];
8. console.log(color); //[0, 1, 2]
9. ?
10.//編譯之后的js如下:
11.var color = [0 /* RED */, 1 /* PINK */, 2 /* BLUE */];
12.// 可以看到我們的枚舉并沒有被編譯成js代碼 只是把color這個數(shù)組變量編譯出來了
Array 類型
對數(shù)組類型的定義有兩種方式:
1. ??const arr: number[] = [1,2,3];
2. ??const arr2: Array<number> = [1,2,3];
元組(tuple)類型
上面數(shù)組類型的方式,只能定義出內(nèi)部全為同種類型的數(shù)組。對于內(nèi)部不同類型的數(shù)組可以使用元組類型來定義
元組( Tuple )表示一個已知數(shù)量和類型的數(shù)組,可以理解為他是一種特殊的數(shù)組
元組,元組就是固定長度的數(shù)組???語法:[類型, 類型, 類型]
?const tuple: [number, string] = [1, "zhangmazi"];
需要注意的是,元組類型只能表示一個已知元素數(shù)量和類型的數(shù)組,長度已指定,越界訪問會提示錯誤。例如,一個數(shù)組中可能有多種類型,數(shù)量和類型都不確定,那就直接any[]。
undefined和null
默認(rèn)情況下 null 和 undefined 是所有類型的子類型。 也就是說你可以把 null 和 undefined 賦值給其他類型。
let a: undefined = undefined;
let b: null = null;
let str: string = 'zhangmazi';
str = null; // 編譯正確
str = undefined; // 編譯正確
如果你在tsconfig.json指定了"strictNullChecks":true ,即開啟嚴(yán)格模式后, null 和 undefined 只能賦值給 void 和它們各自的類型。 (這里感謝評論區(qū)指出) null 和 undefined 只能給它們自己的類型賦值
// 啟用 --strictNullChecks
let x: number;
x = 1; // 編譯正確
x = undefined;??// 編譯錯誤
x = null;??// 編譯錯誤
- undefined 可以給 void 賦值
let c:void = undefined // 編譯正確
let d:void = null // 編譯錯誤
any 類型
any會跳過類型檢查器對值的檢查,任何值都可以賦值給any類型 any類型就是js(建議不要用)
ts會有命名規(guī)范,不能命名為any
any 表示的是任意類型,一個變量設(shè)置類型為any后相當(dāng)于對該變量關(guān)閉了TS的類型檢測
使用TS時,不建議使用any類型
聲明變量如果不指定類型,則TS解析器會自動判斷變量的類型為any (隱式的any)
any是可以賦值任何類型
?let value: any = 1;
?value = "zhangmazi"; // 編譯正確
?value = []; // 編譯正確
?value = {};// 編譯正確
void 類型
void 意思就是無效的, 一般只用在函數(shù)上,告訴別人這個函數(shù)沒有返回值。
?function sayHello(): void {
???console.log("hello 啊,樹哥!");
?}
never 類型
拋出異常
never 類型表示的是那些永不存在的值的類型。 例如never 類型是那些總是會拋出異?;蚋揪筒粫蟹祷刂档暮瘮?shù)表達(dá)式或箭頭函數(shù)表達(dá)式的返回值類型
值會永不存在的兩種情況:
·???????1 如果一個函數(shù)執(zhí)行時拋出了異常,那么這個函數(shù)永遠(yuǎn)不存在返回值(因為拋出異常會直接中斷程序運(yùn)行,這使得程序運(yùn)行不到返回值那一步,即具有不可達(dá)的終點,也就永不存在返回了)
·???????2 函數(shù)中執(zhí)行無限循環(huán)的代碼(死循環(huán)),使得程序永遠(yuǎn)無法運(yùn)行到函數(shù)返回值那一步,永不存在返回。
// 異常
function error(msg: string): never { // 編譯正確
?throw new Error(msg);
}
?
// 死循環(huán)
function loopForever(): never { // 編譯正確
?while (true) { };
}
Unknown 類型
unknown與any一樣,所有類型都可以分配給unknown:
unknown 表示未知類型的值
只能賦值給unkonwn any
?let value: unknown = 1;
?value = "zhangmazi"; // 編譯正確
?value = false; // 編譯正確
unknown與any的最大區(qū)別是:
任何類型的值可以賦值給any,同時any類型的值也可以賦值給任何類型。unknown 任何類型的值都可以賦值給它,但它只能賦值給unknown和any
五、TypeScript對象類型
這里所說的對象類型,就是我們常說的函數(shù)、{}、數(shù)組、類
object, Object 和 {} 類型
(1)object
object 類型用于表示所有的非原始類型,即我們不能把 number、string、boolean、symbol等 原始類型賦值給 object。在嚴(yán)格模式下,null 和 undefined 類型也不能賦給 object。
let object: object;
object = 1; // 報錯
object = "a"; // 報錯
object = true; // 報錯
object = null; // 報錯
object = undefined; // 報錯
object = {}; // 編譯正確
(2)Object
大 Object 代表所有擁有 toString、hasOwnProperty 方法的類型 所以所有原始類型、非原始類型都可以賦給 Object(嚴(yán)格模式下 null 和 undefined 不可以)
let bigObject: Object;
object = 1; // 編譯正確
object = "a"; // 編譯正確
object = true; // 編譯正確
object = null; // 報錯
ObjectCase = undefined; // 報錯
ObjectCase = {}; // ok
(3){}
{} 空對象類型和大 Object 一樣 也是表示原始類型和非原始類型的集合
類
在 TypeScript 中,我們通過 Class 關(guān)鍵字來定義一個類
//類
// class 類名 {
//????屬性名: 類型;
//????constructor(參數(shù):類型){
//??????this.?屬性名 =?參數(shù)
//?????}
//????方法名(){
?
//?????}
// }
class Person {
?name: string;
?age: number;
?constructor(name: string, age: number) {
???this.name = name;
???this.age = age;
?}
?sayHi(): void {
???console.log(`Hi, ${this.name}`);
?}
}
數(shù)組
const flag1: number[] = [1, 2, 3];
const flag2: Array<number> = [1, 2, 3];
函數(shù)
函數(shù)聲明
function add(x: number, y: number): number {
?return x + y;
}
函數(shù)表達(dá)式
const add = function(x: number, y: number): number {
?return x + y;
}
接口定義函數(shù)
interface Add {
?(x: number, y: number): number;
}
可選參數(shù)
function add(x: number, y?: number): number {
?return y ? x + y : x;
}
默認(rèn)參數(shù)
function add(x: number, y: number = 0): number {
?return x + y;
}
剩余參數(shù)
function add(...numbers: number[]): number {
?let sum = 0;
?for (let i = 0; i < numbers.length; i++) {
???sum += numbers[i];
?}
?return sum;
}
函數(shù)重載
函數(shù)重載或方法重載是使用相同名稱和不同參數(shù)數(shù)量或類型創(chuàng)建多個方法的一種能力。
function add(x: number, y: number): number;
function add(x: string, y: string): string;
function add(x: any, y: any): any {
?return x + y;
}
上面示例中,我們給同一個函數(shù)提供多個函數(shù)類型定義,從而實現(xiàn)函數(shù)的重載
需要注意的是:
函數(shù)重載真正執(zhí)行的是同名函數(shù)最后定義的函數(shù)體 在最后一個函數(shù)體定義之前全都屬于函數(shù)類型定義 不能寫具體的函數(shù)實現(xiàn)方法 只能定義類型
六、類型推論
如果沒有明確的指定類型,那么 TypeScript 會依照類型推論的規(guī)則推斷出一個類型。
let x = 1;
x = true; // 報錯
上面的代碼等價于
let x: number = 1;
x = true; // 報錯
通過上述示例我們可以看出,我們沒有給 x 指定明確類型的時候,typescript 會推斷出 x 的類型是 number。
而如果定義的時候沒有賦值,不管之后有沒有賦值,都會被推斷成 any 類型而完全不被類型檢查:
let x;
x = 1; // 編譯正確
x = true; // 編譯正確
七、類型斷言
某些情況下,我們可能比typescript更加清楚的知道某個變量的類型,所以我們可能希望手動指定一個值的類型
類型斷言有兩種寫法
(1)尖括號寫法
let str: any = "to be or not to be";
let strLength: number = (<string>str).length;
(2)as 寫法
let str: any = "to be or not to be";
let strLength: number = (str as string).length;
1.非空斷言
在上下文中當(dāng)類型檢查器無法斷定類型時,可以使用綴表達(dá)式操作符?!?進(jìn)行斷言操作對象是非 null 和非 undefined 的類型,即x!的值不會為 null 或 undefined
?let user: string | null | undefined;
?console.log(user!.toUpperCase()); // 編譯正確
?console.log(user.toUpperCase()); // 錯誤
2.確定賦值斷言
let value:number
console.log(value); // Variable 'value' is used before being assigned.
我們定義了變量, 沒有賦值就使用,則會報錯
通過 let x!: number; 確定賦值斷言,TypeScript 編譯器就會知道該屬性會被明確地賦值。
let value!:number
console.log(value); // undefined 編譯正確
八、聯(lián)合類型
聯(lián)合類型用|分隔,表示取值可以為多種類型中的一種
可以使用 | 來連接多個類型(聯(lián)合類型)
let status:string|number
status='to be or not to be'
status=1
九、類型別名
類型別名用來給一個類型起個新名字。它只是起了一個新名字,并沒有創(chuàng)建新類型。類型別名常用于聯(lián)合類型。
type count = number | number[];
function hello(value: count) {}
十、交叉類型
交叉類型就是跟聯(lián)合類型相反,用&操作符表示,交叉類型就是兩個類型必須存在
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'.
十一、類型守衛(wèi)
類型保護(hù)是可執(zhí)行運(yùn)行時檢查的一種表達(dá)式,用于確保該類型在一定的范圍內(nèi)。 換句話說,類型保護(hù)可以保證一個字符串是一個字符串,盡管它的值也可以是一個數(shù)值。類型保護(hù)與特性檢測并不是完全不同,其主要思想是嘗試檢測屬性、方法或原型,以確定如何處理值。
換句話說:類型守衛(wèi)是運(yùn)行時檢查,確保一個值在所要類型的范圍內(nèi)
目前主要有四種的方式來實現(xiàn)類型保護(hù):
(1)in 關(guān)鍵字
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 關(guān)鍵字
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)自定義類型保護(hù)的類型謂詞
function isNumber(num: any): num is number {
???return typeof num === 'number';
}
function isString(str: any): str is string{
???return typeof str=== 'string';
}
十二、接口
我們使用接口來定義對象的類型。接口是對象的狀態(tài)(屬性)和行為(方法)的抽象(描述)
簡單理解就是:為我們的代碼提供一種約定
我們使用關(guān)鍵字interface來聲明接口
interface Person {
???name: string;
???age: number;
}
let tom: Person = {
???name: 'Tom',
???age: 25
};
我們定義了一個接口 Person,接著定義了一個變量 tom,它的類型是 Person。這樣,我們就約束了 tom 的形狀必須和接口 Person 一致。
接口一般首字母大寫。(當(dāng)然挺多人也習(xí)慣 I 大寫字母開頭,用來表示這是一個接口)
設(shè)置接口可選|只讀
interface Person {
?readonly name: string;
?age?: number;
}
·???????可選屬性,我們最常見的使用情況是,不確定這個參數(shù)是否會傳,或者存在。
·???????只讀屬性用于限制只能在對象剛剛創(chuàng)建的時候修改其值。此外 TypeScript 還提供了 ReadonlyArray 類型,它與 Array 相似,只是把所有可變方法去掉了,因此可以確保數(shù)組創(chuàng)建后再也不能被修改。
索引簽名
有時候我們希望一個接口中除了包含必選和可選屬性之外,還允許有其他的任意屬性,這時我們可以使用?索引簽名?的形式來滿足上述要求。
需要注意的是,一旦定義了任意屬性,那么確定屬性和可選屬性的類型都必須是它的類型的子集
interface Person {
?name: string;
?age?: number;
?[prop: string]: any; //?prop字段必須是 string類型 or number類型。 值是any類型,也就是任意的
}
?
const p1:Person = { name: "張麻子" };
const p2:Person = { name: "樹哥", age: 28 };
const p3:Person = { name: "湯師爺", sex: 1 }
我們規(guī)定 以 string 類型的值來索引,索引到的是一個 any 類型的值
接口與類型別名的區(qū)別
實際上,在大多數(shù)的情況下使用接口類型和類型別名的效果等價,但是在某些特定的場景下這兩者還是存在很大區(qū)別。
TypeScript 的核心原則之一是對值所具有的結(jié)構(gòu)進(jìn)行類型檢查。 而接口的作用就是為這些類型命名和為你的代碼或第三方代碼定義數(shù)據(jù)模型。
type(類型別名)會給一個類型起個新名字。 type 有時和 interface 很像,但是可以作用于原始值(基本類型),聯(lián)合類型,元組以及其它任何你需要手寫的類型。起別名不會新建一個類型 - 它創(chuàng)建了一個新名字來引用那個類型。給基本類型起別名通常沒什么用,盡管可以做為文檔的一種形式使用。
接口和類型別名都可以用來描述對象或函數(shù)的類型,只是語法不同
type MyTYpe = {
?name: string;
?say(): void;
}
?
interface MyInterface {
?name: string;
?say(): void;
}
都允許擴(kuò)展
·???????interface 用?extends?來實現(xiàn)擴(kuò)展
interface MyInterface {
?name: string;
?say(): void;
}
?
interface MyInterface2 extends MyInterface {
?sex: string;
}
?
let person:MyInterface2 = {
?name:'樹哥',
?sex:'男',
?say(): void {
???console.log("hello 啊,樹哥!");
?}
}
·???????type 使用?&?實現(xiàn)擴(kuò)展
type MyType = {
?name:string;
?say(): void;
}
type MyType2 = MyType & {
?sex:string;
}
let value: MyType2 = {
?name:'樹哥',
?sex:'男',
?say(): void {
???console.log("hello 啊,樹哥!");
?}
}
不同點
·???????type可以聲明基本數(shù)據(jù)類型別名/聯(lián)合類型/元組等,而interface不行
// 基本類型別名
type UserName = string;
type UserName = string | number;
// 聯(lián)合類型
type Animal = Pig | Dog | Cat;
type List = [string, boolean, number];
·???????interface能夠合并聲明,而type不行
interface Person {
?name: string
}
interface Person {
?age: number
}
// 此時Person同時具有name和age屬性