TypeScript 快速入門

[Toc]javascript

typescript 的產生意義

  • javascript 的特性:
    • javascript 是一門弱類型的,動態類型檢查的語言。這兩個特性在帶來靈活與便捷的同時,也天生有一些缺陷。
    • 弱類型
      弱類型指的是,早聲明一個變量的時候,不須要指定類型,在給這個變量從新賦值的時候也不須要是固定的類型。不像 java 等強類型的語言,聲明一個變量的時候須要指定類型,且不能被賦予非指定類型的值。
    • 動態類型檢查
      靜態類型語言會在編譯階段就會拋出類型錯誤,避免了在線上出類型問題。而js 的類型檢查不是在編譯階段進行,而是在執行階段進行的。當產生類型檢查的錯誤的時候,只有在執行的的時候纔會顯現出來。 例如:下面有一個分割字符串的函數,可是若是不當心在調用的時候傳入其餘類型的數據做爲參數的話,在書寫和編譯的時候不會拋出錯誤,可是會在執行時拋出錯誤。這種錯誤每每會引發整個程序的崩潰。下面的代碼由於不當心給函數 someFunc 傳了個字符串類型的參數,因此運行時報錯了......
      const someFunc = (string) => {
          return string.split('');
      } 
      someFunc(3);
      // Uncaught SyntaxError: string.split is not a function
      複製代碼
  1. typescript的做用:
  • 編譯時的類型檢查,在編譯時對代碼錯誤進行檢查並拋出錯誤
  • ide的加強,代碼智能提示,interface的提示等
  • 提高代碼可讀性,穩定性、可重構性。

類型

1. 基礎數據類型

  • 1.1 boolean
    const isTrue: bollean: true;
    複製代碼
  • 1.2 number
    let num: number = 2333;
    num = 'abc';  // Error: 不能將類型「"abc"」分配給類型「number」
    複製代碼
  • 1.3 string
    let str: string = '嘿嘿嘿';
    str = 0;  // Error: 不能將類型「0」分配給類型「string」。
    複製代碼
  • 1.4 null 和 undefined

在非嚴格空檢查模式下,null 和 undefined 是全部類型的子類型,能夠做爲任何類型變量的值;
在嚴格空檢查模式(strictNullChecks)下,其是獨立的類型。java

非嚴格空檢查模式下:如下三種狀況都不會報錯:
嚴格空檢查模式下:如下三種狀況都會報錯:git

let str: string = undefined;

let obj: object = undefined;

let num: number = 2;
num = null;
複製代碼
  • 1.5 void

void 表示空類型,void 類型只能賦值爲 null || undefined。也能夠在函數中表示沒有返回值。github

let v: void = null;

let func = (): void => {
    alert('沒有返回值');
}
複製代碼
  • 1.6 never

never 表示其有沒法達到的重點,never 是任何類型的子類型,但沒有任何類型是 never 的子類型;typescript

const error = (message: string): never => {
    throw new Error(message);
}
// 雖然這個函數規定必須有 string 類型的返回值,可是因爲 never 是任何類型的子類型,因此這裏不會報錯
const error = (message: string): string => {
    throw new Error(message);
}
複製代碼
  • 1.7 any

any 表示該值是任意類型,編輯時將跳過對他的類型檢查。應在代碼中儘可能避免 any 類型的出現,由於這會失去 ts 的大部分做用 -- 類型檢查。其使用的場景在於接受的數據時動態不肯定的類型,或者用來在第三方庫的 module.d.ts 聲明文件文件裏跳過對第三方庫的編譯。後端

// 例如在 React 的聲明文件裏,由於不肯定傳入的nextProps 與 nextContext 是什麼類型,因此使用了any
componentWillReceiveProps?(nextProps: Readonly<P>, nextContext: any): void;

複製代碼
  • 1.8 object

object表示非原始類型,也就是除number,string,boolean,symbol,null或undefined以外的類型。其意義在於,在不知道傳入的對象長什麼樣子的狀況下,更容易表示Obiect的api,例如hasOwnPropertyapi

const func = (arg: object): void => {
    console.log(arg.hasOwnProperty('name')); // => true
}
func({name: 'liuneng'})
複製代碼
  • 1.9 數組與元組數組

    1. 數組

    有兩種定義數組類型的方式,一種是直接在類型後面加上[], 表示元素爲該類型的數組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」的參數
    複製代碼
    1. 元組用來表示已知元素數量與類型的數組,在賦值時內部元素的類型必須一一對應,訪問時也會獲得正確類型。當給元組添加元素或者訪問未知索引元素的時候,會使用他們的聯合類型
    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;
};
複製代碼

2.枚舉 (enum)

使用枚舉類型能夠爲一組數值賦予有意義的名字

  • 例如說,如今有一個接口用來過濾一個列表,其接受一個參數,0表示不過濾,1表示過濾男性, 2表示過濾女性
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 的值
    聲明 enum 類型的時候,能夠指定 value 也能夠不指定 value。
    不指定 value 的話他會從零後續依次遞增 1。
    當經過 value 訪問 key 的時候,若是有相同的 value,取最後一個
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
複製代碼

3.接口 (interface)

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: '周杰倫'
};
複製代碼

4.函數

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 會自動給右邊的函數體肯定函數類型。若是右邊函數體與左邊類型聲明不一致就會報錯。

  • 參數:
    ts 當不肯定函數的參數的時候,能夠定義可選參數,其他 interface 的可選屬性使用方法相似,是一個問號。也可使用默認參數,其與 ES6 的默認參數一致
// 可選參數
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]
複製代碼

5. 類(class)

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 是隻讀屬性,不能修改
複製代碼

6. 類型推論

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 甚至能根據某些代碼特徵進行推斷出正確的類型範圍。

    • typeof

    下面的 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

    下面的代碼能夠根據 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
          }
      }
    複製代碼

7. 泛型

有時候,當時用一個組件的時候,並不能肯定其數據類型是什麼樣子,或者說爲了達到複用組件的目的,可使用泛型來建立可重用的組件。

例如,如今須要一個函數,其要求能夠輸出任意類型的參數,可是輸入與輸出必須是同一類型。若是不使用泛型的話,只能使用聯合類型,或者 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 的賦值與泛型傳入的類型不一致因此報錯

  • 配合 fetch 使用。 大部分狀況下,咱們在處理 feth 請求的時候,與後端約定,後端返回的數據格式是固定的,只不過 data 部分可能並不肯定。
// 咱們與後端約定, 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」。
複製代碼
  • 能夠像 interface 同樣使用

雖然使用方式相似,可是類型別名並不能被繼承、導出等操做。只能做爲

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
// 直接將 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}`);
    }
}

複製代碼
  • 在安裝 TypeScript 的時候,會自動安裝 lib.d.ts 等聲明文件。其內部包含了 JavaScript 內置對象及 DOM 中存在各類常見的環境聲明。例如: es5.d.ts
相關文章
相關標籤/搜索