TypeScript
是JavaScript
的超集,爲JavaScript
賦予了強類型語言的特性。在項目中使用TypeScript
可以將錯誤提早暴露在開發階段,而且TypeScript
除保留JavaScript
原有特性外還增長了不少強大的特性,如:接口、泛型、類等等,爲開發帶來了極大的便利。前陣子在學習TypeScript
過程當中整理了很多筆記,在梳理過程當中就產生了該文章,同時也但願讓更多同窗瞭解TypeScript
,順便幫本身加深記憶。node
TypeScript
的數據類型,包含了JavaScript
全部的數據類型。除此以外還提供了枚舉、元組、any、void等類型,下面講述在TypeScript
中如何使用這些類型。typescript
// Boolean
let bool: boolean = true;
複製代碼
// String
let str: string = 'hello TypeScript';
複製代碼
TypeScript
還支持二進制、八進制// Number
let decLiteral: number = 10;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;
複製代碼
// 第一種,能夠在元素類型後面接上 [],表示由此類型元素組成的一個數組
let arr1: number[] = [1, 2, 3];
// 第二種方式是使用數組泛型,Array<元素類型>
let arr2: Array<number> = [1, 2, 3];
// 數組支持多種元素類型
let arr3: (number | string) = [1, '2', 3];
let arr4: Array<number | string> = [1, '2', 3];
複製代碼
// Function
let add = (x, y) => x + y;
複製代碼
// 能夠這樣定義,但訪問、修改屬性時會拋出錯誤,由於Object上不存在x、y屬性。
let obj: Object = {x: 1, y: 2};
console.log(obj.x); // 類型「Object」上不存在屬性「x」
// 完善定義
let obj1: {x: number, y: number} = {x: 1, y: 2};
console.log(obj1.x); // 1
複製代碼
// Symbol
let syb: symbol = Symbol('hello');
複製代碼
// null與undefined
let null: null = null;
let undefined: undefined = undefined;
複製代碼
上面示例中咱們在變量後跟類型的方式,被稱之爲:類型註解。使用類型註解的變量必須按照類型註解聲明的類型來賦值,不然TypeScript
會拋出錯誤。編程
void
表明沒有返回值,或返回值爲undefined
,一般用來聲明函數的返回值類型。void
類型,那它只能被賦值爲null
與undefeated
。// void
let add = (x: number, y: number): void => undefined; // 表示返回值爲undefined
let unusable: void = undefined;
unusable = 'hello'; // ts會拋出錯誤,不能將類型「"hello"」分配給類型「void」
複製代碼
any
能夠被賦值爲除never
外的任何類型。any
,可是不建議所有使用any
來定義類型,這樣使用TypeScript
就沒有任何意義了。// any
let any: any = 1;
any = '1';
any = null;
複製代碼
never
類型表示的是那些永不存在的值的類型never
的函數必須存在沒法達到的終點,好比:一個死循環的函數、一個老是拋出錯的函數。never
只能被賦值爲never
自身。// never
function loop(): never {
while(true) {}
}
function error(message: string): never {
throw new Error(message);
}
複製代碼
// 元祖 tuple
let tuple: [number, string] = [1, '2'];
tuple[2] = 2; // 不能將類型「2」分配給類型「undefined」。
複製代碼
// 數字枚舉,默認從0開始,依次遞增
enum Color {
Red,
Green,
Blue
}
console.log(Color.Red) // 0
console.log(Color.Red) // 1
console.log(Color.Red) // 2
// 自定義數字枚舉的值
enum Alphabet {
A = 8,
B,
C = 2020
}
console.log(Alphabet.A) // 8
console.log(Alphabet.B) // 9
console.log(Alphabet.C) // 2020
// 類型保護
let year = Alphabet.C;
year = 'today'; // 不能將類型「"today"」分配給類型「Alphabet」
// 數字枚舉支持雙向映射
console.log(Alphabet.A) // 8
console.log(Alphabet[8]) // A
// js實現雙向映射
var Alphabet;
(function (Alphabet) {
Alphabet[Alphabet["A"] = 8] = "A";
Alphabet[Alphabet["B"] = 9] = "B";
Alphabet[Alphabet["C"] = 2020] = "C";
})(Alphabet || (Alphabet = {}));
// 字符串枚舉
enum Message {
Fail = '失敗',
Success = '成功'
}
// 枚舉成員類型
enum Library {
// 常量枚舉
BookName,
Year = 2020,
Count = BookName + Year,
// 計算型枚舉
Number = Math.random(),
Size = '如何成爲掘金V6'.length // 計算型枚舉後的枚舉成員必需要賦值
}
複製代碼
看完上面的內容,咱們已經對TypeScript
中的數據類型已經有一個大概的瞭解了。有同窗可能會有疑問,若是咱們須要描述一個複雜的數據類型該怎麼編寫類型註解呢?這就涉及到咱們下面講的內容接口,如何使用接口來描述一個複雜的數據類型。數組
接口是TypeScript
中核心概念之一,主要用於約束對象、函數、類的結構。dom
x?: string
,來約定參數是否可選readonly x: number
,來約定參數是否只讀/** * 對象類型接口 */
// 可選屬性與只讀屬性
interface Person {
name: string;
readonly age: number;
sex: 'boy' | 'girl',
hobby?: string[]
}
let Tom: Person = {
name: 'Tom',
age: 3,
sex: 'boy'
};
Tom.age = 1; // Cannot assign to 'age' because it is a read-only property.
let Jerry: Person = {
name: 'Jerry',
age: 3,
sex: 'boy',
hobby: ['和湯姆一塊兒快樂的玩耍']
}
/** * 鴨式辯型法舉例 * renderList傳入的參數只要知足Result接口的約定便可,多餘的code、msg不會影響 * 這也是咱們提到的:一隻鳥若是走起來像鴨子,叫起來像鴨子,遊起來像鴨子,那麼這隻鳥就能被當成一隻鴨子。 */
interface ListItem {
id: number;
name: string
}
interface Result {
list: ListItem[]
}
function renderList(result: Result) {
result.list.forEach((item: ListItem) => {
console.log(item.id, item.name)
})
}
const data = {
list: [{
id: 1,
name: '小紅'
}, {
id: 2,
name: '小白'
}],
code: 1,
msg: ''
};
renderList(data);
/** * 函數類型接口 * 函數的參數名不須要與接口裏定義的名字相匹配,但要求對應位置上的類型是兼容的 */
let add: (x: number, y: number) => number;
interface Add {
(x: number, y: number): number;
}
// 上面這兩種定義式等價的
// 函數的參數名不須要與接口裏定義的名字相匹配
let add1: Add = (j: number, k: number) => j + k;
// 但要求對應位置上的類型是兼容的
let add2: Add = (j: string, k: number) => j + k; // 不能將類型「(j: string, k: number) => string」分配給類型「Add」。參數「j」和「x」 的類型不兼容。
/** * 索引類型接口 * 索引類型具備一個 索引簽名,它描述了對象索引的類型,還有相應的索引返回值類型。 * 當不肯定接口中屬性個數時可使用索引類型接口 * 索引類型分字符串索引和數字索引 * 字符串與數字索引能夠同時使用,但數字索引必須是字符串索引返回的子類型 */
// 數字索引
interface StringArray {
[index: number]: string;
}
const myArra1: StringArray = ['1', 2]; // 不能將類型「number」分配給類型「string」。
const myArray: StringArray = ['1', '2'];
// 字符串索引
interface ListItem {
id: number;
name: string;
[x: string]: any;
}
// 這裏咱們使用string去索引any
let list: ListItem[] = [{
id: 1,
name: 'aa',
year: '2019'
}]
// 字符串與數字索引能夠同時使用,但數字索引必須是字符串索引返回的子類型
interface ArrayItem {
[x: string]: string;
[y: number]: number; // 數字索引類型「number」不能賦給字符串索引類型「string」
}
interface ArrayItem1 {
[x: string]: string;
[y: number]: string;
}
/** * 混合類型接口 * 混合類型接口是函數類型接口與對象類型接口的集合 */
interface Lib {
(): void;
version: string;
doSomething(): void;
}
let lib: Lib = (() => {}) as Lib; lib.version = '1.0.0'; lib.doSomething = () => {};
複製代碼
TypeScript
更清楚數據的類型,這時候就能使用類型斷言,表示你清楚該數據的類型格式。
<類型>
的方式來聲明類型斷言。as
進行類型斷言。// 使用接口聲明一個函數類型接口
interface Add {
(x: number, y: number): number
}
// 使用類型別名聲明一個函數類型接口
type Add = (x: number, y: number) => number;
// 上面兩種聲明方式等價
/** * 類型別名的一些小技巧 * 在React中初始化state,設置類型比較麻煩 * 若是使用類型別名就很方便了 */
const initialState = {
page: 1,
pageCount: 15,
list: [{
id: 1,
name: 'Tom'
}]
};
type State = typeof initialState;
type State1 = {
page: number;
pageCount: number;
list: {
id: number;
name: string;
}[];
}
// State與State1二者等價
/** * 類型斷言 * 類型斷言有兩種聲明方式:1. 與JSX通常聲明,2. 使用as關鍵字聲明 * 在React中請使用第二種方式,同時也推薦使用第二種方式 */
interface Obj {
x: number;
y: number;
}
let obj = {};
obj.x = 1; // 類型「{}」上不存在屬性「x」
// 1. 如JSX通常聲明
let obj1 = <Obj>{};
obj1.x = 1;
// 2. 使用as關鍵字聲明
let obj2 = {} as Obj;
obj2.x = 1;
複製代碼
TypeScript
中的函數行爲基本與JavaScript
保持一致,不一樣的是TypeScript
支持如下功能:函數
TypeScript
規定必須在類型最寬泛的版本中實現重載,你能夠理解爲在類型最爲寬泛的版本中須要支持以前版本的全部類型。// 約束參數類型、數量
function add(x: number, y: number) {
return x + y;
};
add(1, 2); // 3
add(1, '2'); // 類型「"2"」的參數不能賦給類型「number」的參數
add(1, 2, 3); // 應有 2 個參數,但得到 3 個。
/** * 可選參數與聯合類型 * 可選參數除參數約定類型外,還會默認支持undefined * 可選參數都是聯合類型 * 聯合類型是指使用「|」設置支持多種類型,後續內容會詳細講解 */
function add1(x: number, y?: number) {
return y ? x + y : x;
}
add1(1); // 1
add1(1, 2); // 3
// 默認參數
function add2(x: number, y: number = 2020) {}
// 剩餘參數
function add3(x: number, ...rest: number[]){
return rest.reduce((pre: number, cur: number) => pre + cur, x);
}
add2(1, 2, 3, 4, 5, 6); // 21
/** * 函數重載 * TypeScript規定必須在類型最寬泛的版本中實現重載,你能夠理解爲在類型最爲寬泛的版本中須要支持以前版本的全部類型。 */
function add4(...rest: number[]): number;
function add4(...rest: string[]): string;
function add4(...rest: any) {
let first = rest[0]
if (typeof first === 'string') {
return rest.join(' ')
}
if (typeof first === 'number') {
return rest.reduce((pre: number, cur: number) => pre + cur)
}
}
add4(2020, 1, 17) // 2038
add4('hello','TypeScript') // hello TypeScript
add4({x: 1, y: 2}) // 類型「{ x: number; y: number; }」的參數不能賦給類型「string」的參數
複製代碼
JavaScript
自己是沒有類概念,在ECMAScript 6以前都是使用函數與基於原型的繼承建立可複用組件。 JavaScript
在ECMAScript 6引入了class關鍵字,可用於基於類的面向對象編程。可是class的本質仍是函數+原型,此處很少作講解。TypeScript
的類包含JavaScript
的類,除此以外還新增類一些新特性如:成員修飾符、抽象類、多態等等。oop
TypeScript
有哪些成員修飾符以及特性?
TypeScript
類的屬性必須具備初始值。TypeScript
類屬性默認爲public,公共成員/** * 繼承與成員修飾符 */
interface PersonBase {
name: string;
age: number;
sex: 'boy' | 'girl';
}
class Person {
name: string;
age: number;
readonly sex: string; // 只讀屬性,不可被更改
constructor({ name, age, sex }: PersonBase) {
this.name = name;
this.age = age;
this.sex = sex;
}
}
class Man extends Person {
private wife: string; // 私有成員不能被外部調用
protected propertyList?: string[]; // 受保護成員,只能在類及其子類中訪問。
static weight: number = 120; // 只能經過類名訪問,不能經過子類訪問
constructor(personBase: PersonBase) {
super(personBase);
this.wife = 'Jilly';
this.propertyList = ['house', 'car'];
}
private getWife() {
return this.wife;
}
getWeight() {
return this.weight; // Property 'weight' is a static member of type 'Man'
}
}
class Son extends Man {
constructor(personBase: PersonBase) {
super(personBase);
}
}
const Tom = new Man({
name: 'Tom',
age: 18,
sex: 'boy'
});
const Child = new Son({
name: 'child',
age: 3,
sex: 'boy'
});
Child.sex = 'girl'; // Cannot assign to 'sex' because it is a read-only property.
console.log(Tom.propertyList); // 屬性「propertyList」受保護,只能在類「Man」及其子類中訪問。
console.log(Child.getWife()); // 屬性「getWife」爲私有屬性,只能在類「Man」中訪問。
console.log(Man.weight); // 只能經過類名訪問,不能經過子類訪問
複製代碼
abstract
關鍵字定義抽象類與抽象方法。/** * 抽象類與多態 * 抽象類只能被繼承,不能被實例化。 * 抽象類中的抽象方法不包含具體實現而且必須在派生類中實現 */
// 實現一個抽象類
abstract class Animal {
eat() {
console.log('eat');
}
// 實現一個抽象方法,抽象方法不包含具體實現而且必須在派生類中實現
abstract sleep(): void;
}
const pig = new Animal(); // 沒法建立抽象類的實例
class Dog extends Animal {
name: string;
constructor(name: string){
super();
this.name = name;
}
sleep() {
console.log('dog sleep')
}
}
let dog = new Dog('wang wang');
dog.sleep()
// 非抽象類「Cat」不會實現繼承自「Animal」類的抽象成員「sleep」
class Cat extends Animal {
}
複製代碼
/** * 類與接口 * 接口與接口之間可相互繼承,使用extends關鍵字繼承。 * 繼承多個接口須要用「,」分割 * 類使用接口時,須要使用implements關鍵字 * 接口與類可相互繼承 */
interface Animal {
eat(): void;
}
// 接口繼承接口
interface Felidae extends Animal {
run(): void;
}
interface Pets {
cute: boolean;
}
// 同時繼承多個接口
interface Cat extends Felidae, Pets {}
// 類使用接口
class Tom implements Cat {
cute = true;
eat(){};
run(){};
}
// 接口繼承類
interface Jack extends Tom {}
複製代碼
/** * 泛型 * 不預先確認數據格式,具體的類型格式在使用時才能確認。 * 泛型不能用於類的靜態成員 */
// 一個簡單的泛型函數
function log<T>(msg: T): T {
return msg;
}
// 實現一個泛型接口
interface Log {
<T>(msg: T): T
}
// 上面兩種實現方式等價
// 兩種使用方式
log<string>('hello'); // 第一種,告訴泛型函數傳入的參數是什麼類型
log('hello'); // 第二種,經過TypeScript類型推斷來肯定類型
let myLog: Log = <T>(msg: T) => msg;
myLog<string>('hello');
myLog('hello');
class Cat<T> {
static eat(name: T) {} // 靜態成員不能引用類類型參數
}
複製代碼
類型保護機制,可以保證TypeScript
變量在特定的區塊內屬於特定的類型,所以,在類型保護區塊中,能夠放心的引用對應類型的屬性和方法。學習
/** * 類型保護機制 * 類型保護機制能肯定在指定區塊內,可使用對應類型的方法和屬性 * 可以使用instanceof、typeof、in、hasOwnProperty或組合判斷等方式來肯定區塊 */
class Pig {
edible: boolean = true;
}
class Cat {
cute: boolean = true;
}
function getAnimal(animal: Pig | Cat) {
const Animal = animal instanceof Pig ? new Pig() : new Cat();
if (Animal instanceof Pig) {
console.log(Animal.edible); // true
} else {
console.log(Animal.cute); // true
}
}
複製代碼
&
將多個類型合併爲一個類型。|
來設置多個類型, 賦值時能夠根據設置的類型來賦值。/** * 交叉類型與聯合類型 * 交叉類型會將多個類型合併爲一個類型,能夠認爲是多個類型的集合。 * 交叉類型使用&將多個類型合併爲一個類型。 * 聯合類型會將變量設置多種類型,賦值時能夠根據設置的類型來賦值。 * 聯合類型使用|來設置多個類型, 賦值時能夠根據設置的類型來賦值。 */
interface Dog {
run: () => void;
}
interface Cat {
jump: () => void;
}
// 交叉類型
let pet: Dog & Cat = {
run: () => {},
jump: () => {}
};
// 聯合類型
let Sex: 'boy' | 'girl';
let Status: 'success' | 'fail' | 'loading'
複製代碼
keyof T
爲索引類型查詢操做符,T你能夠理解爲任何類型與值。T[K]
爲索引訪問操做符,與獲取對象類似,存在返回值的類型,不存在會拋出錯誤。/** * 索引類型 */
let obj = {
a: 1,
b: 2,
c: 3
};
// keyof T
interface Obj {
a: number;
b: string;
}
let key: keyof Obj; // "a" | "b"
// T[K]
let value: Obj['a']; // number
/** * T extends U * 這個函數的意思是,從K[]中找到T包含的索引,返回類型爲T[K] * 簡單的來講就是,從keys中找到obj中包含的值,keys若是傳入obj中不存在的值就會拋出錯誤 */
function getValue<T, K extends keyof T>(obj: T, keys: K[]): T[K][] {
return keys.map(key => obj[key]);
}
console.log(getValue(obj, ['a', 'b'])); // [1, 2]
console.log(getValue(obj, ['a', 'f'])); // 不能將類型「string」分配給類型「"a" | "b" | "c"」 [1, undefined]
複製代碼
/** * 映射類型 * 從舊類型中建立新類型的一種方式。新的類型以相同的形式去轉換舊的類型,好比能夠令每一個屬性成爲readonly或者可選的。 * TypeScript自身提供了不少種映射類型,如:Readonly、Partial、Pick、Record等等 * 有興趣的同窗能夠查看 node_modules/typescript/lib/lib.es5.d.ts 文件 */
interface Obj {
a: number;
b: string;
c: boolean;
}
// 只讀
type ReadonlyObj = Readonly<Obj>
// 可選
type PartialObj = Partial<Obj>
// 抽離指定屬性
type PickObj = Pick<Obj, 'a' | 'b'>
type RecordObj = Record<'x' | 'y', Obj>
複製代碼
lib.d.ts
裏增長了一些預約義的有條件類型:
Exclude<T, U>
-- 從T
中剔除能夠賦值給U
的類型。Extract<T, U>
-- 提取T
中能夠賦值給U
的類型。NonNullable<T>
-- 從T
中剔除null
和undefined
。ReturnType<T>
-- 獲取函數返回值類型。InstanceType<T>
-- 獲取構造函數類型的實例類型。/** * 條件類型 */
// T extends U ? X : Y;
type TypeName<T> =
T extends string ? "string" :
T extends number ? 'number' :
T extends boolean ? 'boolean' :
T extends undefined ? 'undefined' :
T extends Function ? 'function' :
'object';
type T1 = TypeName<number>; // number
type T2 = TypeName<string[]> // object
// (A | B) extends U ? X : Y;
type T3 = TypeName<number | string[]>; // "number" | "object"
// Diff的意思是從T中找出U中不存在的值,存在返回never,不存在返回該值
type Diff<T, U> = T extends U ? never : T;
type T4 = Diff<'a' | 'b' | 'c', 'a' | 'e'>
// Diff <'a', 'a', 'e'> | Diff <'b', 'a', 'e'> | Diff <'c', 'a', 'e'>
// never | 'b' | 'c'
// 'b' | 'c'
type NotNull<T> = Diff<T, undefined | null>;
type T5 = NotNull<string | number | undefined | null> // string | number
// Exclude<T, U>
// NotNullable<T>
// Extract<T, U>
type T6 = Extract<'a' | 'b' | 'c', 'a' | 'e'>; // a
// RenturnType<T>
type T7 = ReturnType<() => string> // string
複製代碼
到此TypeScript三步曲之基礎篇就結束了,感謝你們的閱讀,若有錯誤歡迎斧正。優化
同時2020年的第一個flag也實現了,在2020年1月20日回家前輸出該文章,雖然花了不少時間和精力,但也讓我更深刻的瞭解了TypeScript
而且加深了印象。後續的TypeScript三步曲之配置篇與TypeScript三步曲之工程篇預計3-4月輸出。ui
明天就回家過年了,在此提早祝各位掘友:新年快樂、身體健康、事事如意。