[Toc]javascript
弱類型
的,動態類型檢查
的語言。這兩個特性在帶來靈活與便捷的同時,也天生有一些缺陷。const someFunc = (string) => {
return string.split('');
}
someFunc(3);
// Uncaught SyntaxError: string.split is not a function
複製代碼
const isTrue: bollean: true;
複製代碼
let num: number = 2333;
num = 'abc'; // Error: 不能將類型「"abc"」分配給類型「number」
複製代碼
let str: string = '嘿嘿嘿';
str = 0; // Error: 不能將類型「0」分配給類型「string」。
複製代碼
在非嚴格空檢查模式下,null 和 undefined 是全部類型的子類型,能夠做爲任何類型變量的值;
在嚴格空檢查模式(strictNullChecks)下,其是獨立的類型。java
非嚴格空檢查模式下:如下三種狀況都不會報錯:
嚴格空檢查模式下:如下三種狀況都會報錯:git
let str: string = undefined;
let obj: object = undefined;
let num: number = 2;
num = null;
複製代碼
void 表示空類型,void 類型只能賦值爲 null || undefined。也能夠在函數中表示沒有返回值。github
let v: void = null;
let func = (): void => {
alert('沒有返回值');
}
複製代碼
never 表示其有沒法達到的重點,never 是任何類型的子類型,但沒有任何類型是 never 的子類型;typescript
const error = (message: string): never => {
throw new Error(message);
}
// 雖然這個函數規定必須有 string 類型的返回值,可是因爲 never 是任何類型的子類型,因此這裏不會報錯
const error = (message: string): string => {
throw new Error(message);
}
複製代碼
any 表示該值是任意類型,編輯時將跳過對他的類型檢查。應在代碼中儘可能避免 any 類型的出現,由於這會失去 ts 的大部分做用 -- 類型檢查。其使用的場景在於接受的數據時動態不肯定的類型,或者用來在第三方庫的
module.d.ts
聲明文件文件裏跳過對第三方庫的編譯。後端
// 例如在 React 的聲明文件裏,由於不肯定傳入的nextProps 與 nextContext 是什麼類型,因此使用了any
componentWillReceiveProps?(nextProps: Readonly<P>, nextContext: any): void;
複製代碼
object表示非原始類型,也就是除number,string,boolean,symbol,null或undefined以外的類型。其意義在於,在不知道傳入的對象長什麼樣子的狀況下,更容易表示Obiect的api,例如
hasOwnProperty
api
const func = (arg: object): void => {
console.log(arg.hasOwnProperty('name')); // => true
}
func({name: 'liuneng'})
複製代碼
1.9 數組與元組數組
有兩種定義數組類型的方式,一種是直接在類型後面加上[], 表示元素爲該類型的數組bash
let arr: number[] = [];
arr.push(1);
arr.push('2'); // Error: 類型「"2"」的參數不能賦給類型「number」的參數
複製代碼
第二種是使用數組泛型, 這種方式能夠在不想在外邊聲明類型時候使用dom
let list: Array<1 | 2> = [];
list.push(3); // Error: 類型「3」的參數不能賦給類型「1 | 2」的參數
複製代碼
let tuple: [string, number];
tuple = [1, 'a']; // Error: 不能將類型「[number, string]」分配給類型「[string, number]」
tuple = ['a', 1];
tuple.push(true); // 類型"true"的參數不能賦給類型「string | number」的參數。
複製代碼
2.0 類型斷言
其表示在不肯定該變量類型時,指定其類型,表示明確知道他的類型,不用去檢查了
// 雖然 param 是any,但「我」保證傳入的必定是個 string 類型的參數
const func = (param: any) => {
return (param as string).length;
};
複製代碼
使用枚舉類型能夠爲一組數值賦予有意義的名字
const param = {
filterType: 0,
};
fetch('/getfilterList', param)
.then((res: any[]) => {
console.log(res);
});
複製代碼
上面的這行代碼,用眼睛看根本不知道請求的是什麼類型的列表,只能經過註釋 與 文檔來判斷它的意義
enum filterMap {
All = 0,
Men = 1,
Women = 2,
}
const param = {
filterType: filterMap.Men,
};
fetch('/getfilterList', param)
.then((res: any[]) => {
console.log(res);
});
複製代碼
上面這段代碼,用枚舉列出了全部過濾條件的選項,使用時直接像使用對象同樣枚舉,從語義上很容易理解這段代碼想要獲取的是男性列表,代碼便是文檔。尤爲是當作常量使用更加統一與方便理解。
enum AbcMap {
A,
B = 1,
C,
D = 2,
E = 2,
}
console.log(AbcMap.A); // => 0 由於後面的 B 是1,因此自動 -1
console.log(AbcMap.C); // => 2 由於前面的 B 是1,因此自動 +1
console.log(AbcMap.[2]); // => E 有三個2,取最後一個的 key
複製代碼
interface 是對對象形狀的描述,其規定這個類型的對象應該長什麼樣子,編譯的時候回去檢查以他爲描述的對象符不符合其結構
interface IPerson {
name: string;
readonly isMan: boolean; // 只讀屬性,建立後不能夠改寫
age?: number; // 可選屬性,實現的時候能夠沒有這個屬性
}
const xiaohong: IPerson = {
name: '小紅',
isMan: false,
};
xiaohong.isMan = true; // Error: isMan 是常數或只讀屬性。
xiaohong.love = '周杰倫'; // Error: 「love」不在類型「IPerson」中。
複製代碼
上面給小紅添加 love 屬性的時候報錯了,由於 IPerson 中沒有規定這個屬性
可是有時候咱們不肯定在 interface 外有沒有別的屬性,這時候可使用索引簽名。可是此時已肯定的類型必須是他的子類型
interface IPerson {
[key: string]: string;
}
const xiaoming: IPerson = {
name: '小紅',
love: '周杰倫'
};
複製代碼
ts 能夠給函數的參數 與 返回值指定類型。使用時候不能使用多餘參數
如今定義一個加法的函數表達式
const sum: (x: number, y: number) => number = (x: number, y: number): number => x + y;
sum(1, 2); // => 3
複製代碼
上面的代碼看起來可能有點很差理解,左邊是給 sum 定義類型,右半部分是 一個具體函數,這是 ts 函數完整的寫法。經過 ts 類型推論的特性,能夠把左半部分省略掉;也能夠給變量定義類型而省略右邊
const sum = (x: number, y: number): number => x + y;
複製代碼
上面的代碼看起來就比較好理解了,可是若是咱們有一個乘法的方法,還有減法的方法等等等等,其輸入類型和輸出的類型都是 number,這個時候若是感受在每一個方法上都去定義參數與返回值的類型會以爲有點麻煩。此時,能夠單獨抽出一種函數類型,在函數表達式中使用。
type INumFunc = (x: number, y: number) => number ;
const sum: INumFunc = (x, y) => x + y;
const sub: INumFunc = (x, y) => x - y;
const multip: INumFunc = (x, y) => x * y;
複製代碼
上面的代碼定義了一個函數類型,要求輸入輸出都爲 number;此時 ts 會自動給右邊的函數體肯定函數類型。若是右邊函數體與左邊類型聲明不一致就會報錯。
// 可選參數
const sub = (x: number, y: number = 5, y?: number): number => {
if (y) {
return x - y - z;
} else {
return 0;
}
};
sub(10, 1, 1) // -> 8
sub(10, 1) // -> 0
// 默認參數
const sum = (x: number, y: number = 5): number => x + y;
sum(1, 2); // -> 3
sum(1); // -> 6
sum(1, null); // -> 6
複製代碼
js 裏有 arguments 的存在,因此咱們能夠給一個函數傳任意個參數。在 ts 裏,不肯定參數的個數的話,可使用剩餘參數,將多出的參數放入一個數組, 其和 ES6 的剩餘參數使用方法一致
const sum = (x: number, ...y: number[]): number => {
console.log(y);
let sum = x;
if (y && y.length > 0) {
for (let index = 0; index < y.length; index++) {
sum = sum + y[index];
}
}
return sub;
};
sum(1, 2, 3); // res -> 6 , log -> [2, 3]
複製代碼
ts 的類 與 ES6 中的類大致相同,不過 class 中的屬性能夠添加修飾符
static 靜態屬性,其是這個類的屬性,而不是實例的屬性 public: 訪問該成員的時候沒有限制;
protected: 在派生類中能夠訪問該屬性,可是不能再外部訪問;
private: 私有成員,只能本身訪問
readonly: 只讀屬性
abstract: 用於修飾抽象類或屬性,必須在派生類中方實現它,本身不能實現。
class Person {
static desc() {
console.log('It's a class of "person");
}
protected name: string;
private age: number = '8';
readonly sex: string = 'boy';
constructor (theName: string) {
this.name = theName;
}
public like() {
console.log('footbal');
}
abstract eat(): void; // 必須在派生類中實現它
}
class kids extends Person {
constructor(name) {
super(name);
}
sayName() {
console.log(this.name);
}
eat() {
console.log('麪包');
}
}
const xiaohong = new kids('小紅');
Person.desc(); // 靜態成員直接使用 class 訪問,不用實例
xiaohong.like(); // -> 'footbal' public 屬性訪問沒限制
console.log(xiaohong.name); // Error: 小紅是 protected 屬性,只能在基類與派生類裏面訪問
xiaohong.sayName(); // -> '小紅' 小紅的內部方法裏能夠訪問 protected 屬性
console.log(xiaohong.age) // age 是 私有屬性,不能在外部訪問
console.log(xiaohong.sex); // -> boy
xiaohong.sex = 'girl'; // Error: sex 是隻讀屬性,不能修改
複製代碼
TypeScript 會在沒有明確的指定類型的時候推測出一個類型,這就是類型推論。
let str = 'string';
str = 1;
// Error: Type 'number' is not assignable to type 'string'.
// str 在聲明的時候並無指定類型,可是 ts 自動推斷爲 string, 因此在給它賦值爲 number 的時候報錯了
let someVar;
someVar = 'string';
someVar = 3;
// 若是在聲明一個變量的時候並無給它賦值,ts 自動給它推斷爲 any 類型,因此這裏跳過了類型檢查,沒有報錯。
複製代碼
const sum = (x: number, y: number) => x + y;
sum(1, 2);
// 上面函數,沒有並無給其指定 return 的類型,但這是被容許的,由於 ts 能夠自動推斷出其返回值的類型。
複製代碼
const obj = {
a: 1,
b: 2,
};
obj.a = 'str'; // Error: 不能將類型「"str"」分配給類型「number」
// 雖然 obj 在聲明的時候並無指定類型,可是 ts 自動將其推斷爲 {a: number, b: number} 因此報錯
// 解構也是同樣的
let { a } = obj;
a = 'str'; // Error: 不能將類型「"str"」分配給類型「number」
複製代碼
下面的代碼將 arr 推斷爲了
Array<string | number>
const arr = ['a', 'b', 1];
arr[0] = true; // Error: 不能將類型「true」分配給類型「string | number」
複製代碼
ts 甚至能根據某些代碼特徵進行推斷出正確的類型範圍。
下面的 if 代碼塊中,param 被推斷爲類型 string
const func = (arg: number | string) => {
if (typeof arg === 'string') {
console.log(arg.split('')); // OK
}
console.log(arg.split('')); // Error: 類型「number」上不存在屬性「split」。
}
複製代碼
下面的代碼能夠根據 instanceof 推斷出其參數類型,甚至能夠自動推斷出 else 代碼塊中的類型
class A {
public name: string = 'hehe';
}
class B {
public age: number = 8;
}
const func = (arg: A | B) => {
if (arg instanceof A) {
console.log(arg.name); // OK
console.log(arg.age); // Error: 類型「A」上不存在屬性「age」。
} else {
console.log(arg.name); // Error: 類型「B」上不存在屬性「name」。
console.log(arg.age); // OK
}
}
複製代碼
有時候,當時用一個組件的時候,並不能肯定其數據類型是什麼樣子,或者說爲了達到複用組件的目的,可使用泛型來建立可重用的組件。
例如,如今須要一個函數,其要求能夠輸出任意類型的參數,可是輸入與輸出必須是同一類型。若是不使用泛型的話,只能使用聯合類型,或者 any 來實現。使用泛型能夠這樣作:
function identity<T>(arg: T): T {
return arg;
}
identity<string>('str'); // -> 'str'
複製代碼
function objToArr<T>(arg: T): T[] {
return [arg];
}
objToArr({a: 1}); // -> [{a: 1}]
複製代碼
上面的代碼表示輸入 T 類型的參數時,返回一個 T 類型成員的數組
// 建立一個接口,其屬性 list 的類型在使用前並不肯定
interface IData<T> {
list: T[];
status: number;
}
const numItemData: IData<number> = {
list: [1, 2, 3],
status: 1,
};
const strItemData: IData<number> = {
list: ['a', 'b', 'c'], // Error: 不能將類型「string[]」分配給類型「number[]」。
status: 1,
};
複製代碼
上面的例子建立了接口 IData
,其在使用的時候,傳入類型約束, 這樣能夠最大程度的複用 IData
接口。由於 strItemData 的賦值與泛型傳入的類型不一致因此報錯
// 咱們與後端約定, response 的格式以下,但 data 部分依具體使用場景而定, 泛型能夠給個默認值 - any
interface IResponse<T = any> {
status: number;
message: string;
data: T;
}
複製代碼
// 咱們封裝了一個 fetch API,裏面對請求進行了處理,例如header、toaster
import fetchData from 'XXX/fetchData';
// 引入上面定義的通用的 response 接口
import { IResponse } from 'XXX/response';
export const getUser<T> = (param: IInput): Promise<IResponse<T>> => {
return fetchData('xxx/getData').then((res: IResponse<T>) => {
return res;
});
};
複製代碼
使用的時候:
import getUser from 'XXX/getUser';
// 定義 response 中 data 的類型
interface IData {
name: string;
age: number;
}
// 將 data 的類型約束傳入泛型
const userInfo = getUser<IData>();
userInfo.data.name = '小剛'; // Right
userInfo.data.name = 666; // Error
// ts 推斷出 data.name 是 string 類型,因此在賦值爲 666 的時候報錯了
複製代碼
class Person<TName, TAge> {
name: TName;
age: TAge;
}
let xiaoming = new Person<string, number>();
xiaoming.name = '小明'; // Right
xiaoming.age = '8'; // Error: [ts] 不能將類型「8」分配給類型「number」。
複製代碼
上面代碼由於在建立 xiaoming 的時候規定了 age 類型必須爲 number,因此報錯了
interface IData {
a: number;
}
function objToArr<T extends IData>(arg: T): T[] {
console.log(arg.a);
return [arg];
}
objToArr({a: 1, b: 2}); // -> [{a: 1, b: 2}]
objToArr({b: 2}); // Error: 類型「{ b: number; }」的參數不能賦給類型「IData」的參數。
複製代碼
TS 爲咱們的 javascript 的內置對象提供了類型,而且在使用內置對象的時候自動爲咱們進行類型檢測 例如:
let body: HTMLElement = document.body;
let div: HTMLDivElement = document.createElement('div');
document.addEventListener('click', function(e: MouseEvent) {
console.log('MouseEvent');
});
Math.round('3.3'); // 類型「"3.3"」的參數不能賦給類型「number」的參數
// 由於 Math 對象 round 須要接受一個 number 類型的參數因此報錯了,
// 下面是TS核心庫定義文件中對 Math 對象的定義
/**
interface Math {
pow(x: number, y: number): number;
random(): number;
round(x: number): number;
sin(x: number): number;
// ......
}
declare const Math: Math;
**/
複製代碼
TS 也定義了
Number,String,Boolean, Object
, 可是並不推薦區用這些類型,而是應該使用number, string, bollean, obiect
let str: String; // Don't use 'String' as a type. Avoid using the `String` type. Did you mean `string`
複製代碼
能夠給類型起個名字
type str = 'a';
type num = 1;
const ab: str = 'ab'; // Error: 不能將類型「"ab"」分配給類型「"a"」。
const someNum: num = 2; // Error: 不能將類型「2」分配給類型「1」。
複製代碼
雖然使用方式相似,可是類型別名並不能被繼承、導出等操做。只能做爲
type Person = {
name: string;
age: number;
};
const xiaoming: Person = {
name: '小明',
age: 18,
};
複製代碼
type Person<T> = {
name: string;
like: <T>[];
};
const xiaohong: Person<string> = {
name: '小紅',
like: ['dance', 'football'],
};
複製代碼
當咱們想要比那裏一個對象的時候,須要指定每一項元素的 key 的索引簽名,不然會報錯,好比像下面這樣
const obj = {a: 1, b: 2};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(obj[key]);
}
}
// Error: 元素隱式具備 "any" 類型,由於類型「{ a: number; b: number; }」 沒有索引簽名。
複製代碼
可使用 類型別名 + 索引類型來避免該問題
const obj = {a: 1, b: 2};
type ObjKey = keyof typeof obj; // => 'a' | 'b'
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(obj[key as ObjKey]);
}
}
複製代碼
聲明文件是對某個庫的環境聲明,文件內對庫向外暴露的 API 進行類型註解,使其在使用的時候能夠享受到 TS 類型系統帶來的檢查
// 聲明變量
declare var foo: number;
// 聲明函數
declare function add(x: number, y: number): number; 複製代碼
// 直接將 interface 導出就行
複製代碼
// 1. 使用命名空間的方式
declare namespace person {
let name: string;
let age: number;
}
// 2. 使用 interface
interface IPerson {
name: string;
age: number;
}
declare const person: IPerson;
複製代碼
declare class Person {
constructor(name) {
this.name = name;
}
sayHi() {
console.log(`I'm ${this.name}`);
}
}
複製代碼