Typescript 的最大優(yōu)點,就是 類型檢查 和 強(qiáng)制約束。它可以讓我們在編譯運行代碼之前,就避免一些類型使用不當(dāng)、或者沒有按照規(guī)定書寫代碼而引起的各種錯誤。
類型檢查器(類型注解 Flow)
記住,TypeScript 的類型檢查器,一般設(shè)置于你要進(jìn)行檢查的事物的冒號后面。冒號后面,則是你要定義的檢查器格式。
原始類型
在 ES6 以后,Javascript 的原始類型一共有 6 種。在 Typescript 中,原始類型也是 6 種,它們分別是:
// 字符串類型
const a: string = "hello world";
// 數(shù)字類型
const b: number = 123;
// 布爾類型
const c: boolean = true;
// 空類型
const d: null = null;
// undefined 類型
const e: void = undefined;
// symbol 類型
const f: symbol = Symbol();
引用類型
數(shù)組類型
在 Typescript 中,定義數(shù)組我們有 3 種方式。分別是:
// Array<類型>,尖括號單一類型定義
const arr1: Array<number> = [1, 2, 3];
// 類型[],中括號單一類型定義
const arr2: number[] = [1, 2, 3];
// [類型,類型,類型],中括號多類型定義
const arr3: [string, number, number] = ["1", 2, 3];
對象類型
在 Typescript 中,定義對象的方式有多種,例如:
// object 類型檢查器(支持所有“對象”)
const person: object = function(){}
// 大括號 類型檢查器(支持特定類型)
const person: {
name: string;
age?: number // ? 代表該字段為可選,在對象中不一定要包含此屬性
} = {
name: "steve",
age: 20
};
如果我們不想使用這么繁瑣的 對象類型 定義方式,我們還可以使用 Type 來定義一個類型檢查器:
// 定義類型檢查器 Person
type Person = {
name: string;
age?: number; // ? 代表該字段為可選,在對象中不一定要包含此屬性
};
// 在常量名 冒號 后面使用類型檢查器 Person
const person:Person= {
name: "steve", // 該字段必須定義
// age: 23, // 該字段可以不定義
};
函數(shù)類型檢查器
- 定義 回調(diào)函數(shù) 類型檢查器
const say = (callback: (a: string, b: number) => void) => {
callback("1", 2);
};
say((str, number) => {
console.log(str, number);
});
- 定義 普通函數(shù) 類型檢查器
// 參數(shù)
function square(n: number) {
return n + 1;
}
// 返回值
function square(n: number): number {
return n + 1;
}
// 無返回值
function say(): void {
console.log('hello world')
}
// 這里的 rest 可以使用任意名稱,目的為函數(shù)增添任意參數(shù)
const fn = (a: string, b: string, ...rest: number[]): string => {
let str = a + b + rest;
console.log(rest);
console.log(str);
return str;
};
fn("hello", "world", 1, 2, 3, 4, 5);
或類型檢查器
const str: "hello" | "world" | "balala" = "balala";
type StrType = number | string | Array<any>;
斷言 asserts
const nums = [1, 2, 3, 4, 5];
const res = nums.find((item) => item > 3);
// 寫法一:
const num1 = res as number
// 寫法二:
const num2 = <number>res // jsx 下不能這么使用,會沖突
斷言的作用是啥呢?
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function isFish(animal: Cat | Fish) {
// 如果不寫 animal as Fish,那么我們傳 Cat 類型的實例到 isFish,就會報錯
if (typeof (animal as Fish).swim === 'function') {
console.log(true);
}
console.log(false);
}
類型斷言只能夠欺騙 TS 編譯器,無法避免運行時的錯誤,反而濫用類型斷言可能會導(dǎo)致運行時錯誤。例如:
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function swim(animal: Cat | Fish) {
(animal as Fish).swim();
}
const tom: Cat = {
name: 'Tom',
run() { console.log('run') }
};
swim(tom);
編譯時不會報錯,但在運行時會報錯 Uncaught TypeError: animal.swim is not a function`,所以,不要輕易使用斷言。
接口類型檢查器(interface)
- 普通 接口類型檢查器 定義
interface Person {
name: string;
age: number;
sex?: string; // 可選
readonly summary: string; // 關(guān)鍵字 readonly,可以使該屬性成為只讀屬性
}
const person: Person = { name: "steve", age: 20, summary: "balala" };
function printPerson(person: Person) {
console.log(person.name);
console.log(person.age);
console.log(person.sex);
// 報錯:Cannot assign to 'summary' because it is a read-only property.
person.summary = "lalala"; // summary 為只讀屬性,
}
- 動態(tài) 接口類型檢查器 定義
interface State {
page: number; // 頁碼
total: number; // 總數(shù)
// 這里的 string,代表這個鍵可以定義的類型
[key: string]: number; // 每頁條數(shù)
}
const state:State = {
page: 1,
total: 100,
pageSize: 30
}
類 class
- 聲明一個類
// 定義 Person 類
class Person {
name: string; // this.name
// private,無法使用實例直接訪問
private age: number;
// protected,只能在本 class 和子類 class 中才能訪問
// readonly,在實例只能被讀取,無法被賦值
protected readonly gender: boolean;
// 構(gòu)造器,為 實例屬性 進(jìn)行賦值
constructor(name: string, age: number) {
this.name = name;
this.age = age;
this.gender = true
}
// 定義實例方法
sayHi(msg: string): void {
console.log(`I am ${this.name}, ${msg}`);
}
}
// 定義 Student 類,使用 extends 進(jìn)行繼承
class Student extends Person {
private constructor(name: string, age: number) {
// 繼承父類的 實例屬性
super(name, age);
console.log(this.gender);
}
// 定義 靜態(tài)方法,直接使用 類名 進(jìn)行調(diào)用
static create(name: string, age: number) {
return new Student(name, age);
}
}
const tom = new Person("tom", 18);
console.log(tom.name); // √ name 默認(rèn)為公有屬性(也可以添加 public 修飾符),可以被訪問
console.log(tom.age); // X 報錯,age 為私有屬性,除了類中,無法被外界訪問
console.log(tom.gender); // X 報錯,gender 為保護(hù)屬性,只能在本類和子類中訪問,無法被外界訪問
// X 報錯,因為 Student 的 constructor 為 private,故不能被創(chuàng)建
const jack = new Student("steve", 25);
const jack = Student.create("jack", 18);
- class 的接口(interface)
定義類的接口,可以確定在類中需要定義哪些屬性和方法。
interface EatAndRun {
eat(food: string): void;
run(distance: number): void;
}
// 使用 EatAndRun 實現(xiàn)了 Person 類
class Person implements EatAndRun {
// 該類中定義了 eat 方法
eat(food: string): void {
console.log(`吃東西:${food}`);
}
// 該類中定義了 run 方法
run(distance: number) {
console.log(`直立行走:${distance}`);
}
}
// 使用 EatAndRun 實現(xiàn)了 Animal 類
class Animal implements EatAndRun {
// 該類中定義了 eat 方法
eat(food: string): void {
console.log(`進(jìn)食:${food}`);
}
// 該類中定義了 run 方法
run(distance: number) {
console.log(`爬行:${distance}`);
return 1;
}
}
- 抽象類
使用 abstract 修飾詞修飾的類,被叫做抽象類。使用 abstract 修飾詞修飾的方法,叫做抽象方法。
抽象類不能直接使用(無法 new 出實例),一定要被繼承,然后使用繼承類。
// 定義一個動物抽象類
abstract class Animal {
eat(food: string): void {
console.log(`狗狗吃了一塊:${food}`);
}
// 抽象方法在繼承的時候一定要被重寫
abstract run(distance: number): void;
}
// 我們定義一個 Dog 類,繼承自 Animal 抽象類
class Dog extends Animal {
// 抽象方法,一定要被重寫
run(distance: number): void {
console.log(`狗狗跑了${distance}公里`)
}
}
const dog = new Dog();
dog.eat('骨頭'); // "狗狗吃了一塊:骨頭"
dog.run(1); // "狗狗跑了1公里"
interface 和 type 的區(qū)別
一般來說,interface(接口) 和 type(類型別名) 可以混用,但是也存在一些差異。
1. 對象/函數(shù)
兩者都可以用來描述對象的形狀或函數(shù)簽名,但語法不同。
interface,一般來說 name 后面直接寫 花括號:
// 約束對象
interface Point {
x: number;
y: number;
}
// 約束函數(shù)
interface SetPoint {
(x: number, y: number): void;
}
type,一般來說 name 后面都是接 等號:
// 約束對象
type Point = {
x: number;
y: number;
};
// 約束函數(shù)
type SetPoint = (x: number, y: number) => void;
2. 其他類型
與 interface 不同,type 也可用于其他類型,例如 原始類型、聯(lián)合類型和數(shù)組類型。
// 原始類型
type Name = string;
// 對象類型
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };
// union類型
type PartialPoint = PartialPointX | PartialPointY;
// 數(shù)組類型
type Data = [number, string];
3. 擴(kuò)展
兩者都可以進(jìn)行擴(kuò)展,但同樣語法不同。此外,請注意 interface 和 type 不是相互排斥的。interface 可以擴(kuò)展 type,反之亦然。
// 接口擴(kuò)展接口
interface PartialPointX { x: number; }
interface Point extends PartialPointX { y: number; }
//類型別名擴(kuò)展類型別名
type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };
// 接口擴(kuò)展類型別名
type PartialPointX = { x: number; };
interface Point extends PartialPointX { y: number; }
// 類型別名擴(kuò)展接口
interface PartialPointX { x: number; }
type Point = PartialPointX & { y: number; };
4. 聲明合并
與類型別名不同,接口可以定義多次,并將被視為單個接口(所有聲明的成員都被合并)。
// These two declarations become:
// interface Point { x: number; y: number; }
interface Point { x: number; }
interface Point { y: number; }
const point: Point = { x: 1, y: 2 };
泛型
什么是泛型?
當(dāng)我們在使用函數(shù)之前,不知道函數(shù)內(nèi)部具體需要什么類型的 數(shù)據(jù) 的時候,可以在 使用的時候 動態(tài)設(shè)置。泛型,就是這樣的一種寫法。
// 在使用之前不知道函數(shù)內(nèi)部需要什么類型,可以在使用的時候動態(tài)設(shè)置
function createArray<T>(length: number, value: T): T[] {
const arr = Array<T>(length).fill(value);
return arr;
}
// 我們在這里進(jìn)行函數(shù)調(diào)用,調(diào)用的時候就對傳參類型進(jìn)行了設(shè)置
const res = createArray<string>(10, 'hello');
console.log(res);
枚舉類型
枚舉可以實現(xiàn) 數(shù)字類型 的 自增長
定義枚舉類型:
enum En {
a = 3, // 3
b, // 4
c, // 5
}
// 獲取枚舉值
En.a // 3
En['a'] // 3
// 根據(jù)枚舉值,獲取枚舉鍵
En[3] // a,值為 3 的 enum 元素 name
在枚舉中,除開數(shù)字之外的其他任何類型都無法實現(xiàn) 自增長,一定要全部定義,不然 就會報錯。
enum En{
a = 'say', // say
b = 'hello', // hello
c = 'world', // world,這里一定要定義
}
En.a // say
En['a'] // say
En['say'] // undefined