Typescript:高級類型

對象的類型

在 TypeScript 中,咱們使用接口(Interfaces)來定義對象的類型。程序員

什麼是接口

在面嚮對象語言中,接口(Interfaces)是一個很重要的概念,它是對行爲的抽象,而具體如何行動須要由類(classes)去實現(implements)。web

TypeScript 中的接口是一個很是靈活的概念,除了可用於對類的一部分行爲進行抽象之外,也經常使用於對「對象的形狀(Shape)」進行描述數組

interface Person {
  name: string;
  age: number;
}

let man: Person = {
  name: 'Xcat Liu',
  age: 25,
};

上面的例子中,咱們定義了一個接口 Person,接着定義了一個變量 man,它的類型是 Person。這樣,咱們就約束了 man的形狀必須和接口 Person 一致。函數

接口通常首字母大寫。ui

定義的變量比接口少了一些或者多了一些屬性是不容許的:rest

interface Person {
  name: string;
  age: number;
}

let xcatliu: Person = {
  name: 'Xcat Liu',
};

// index.ts(6,5): error TS2322: Type '{ name: string; }' is not assignable to type 'Person'.
//   Property 'age' is missing in type '{ name: string; }'.

可見,賦值的時候,變量的形狀必須和接口的形狀保持一致。code

可選屬性

有時咱們但願不要徹底匹配一個形狀,那麼能夠用可選屬性:對象

interface Person {
  name: string;
  age?: number;
}

let xcatliu: Person = {
  name: 'Xcat Liu',
};
interface Person {
  name: string;
  age?: number;
}

let xcatliu: Person = {
  name: 'Xcat Liu',
  age: 25,
};

可選屬性的含義是該屬性能夠不存在。接口

這時仍然不容許添加未定義的屬性:ip

interface Person {
  name: string;
  age?: number;
}

let xcatliu: Person = {
  name: 'Xcat Liu',
  age: 25,
  website: 'http://xcatliu.com',
};

// examples/playground/index.ts(9,3): error TS2322: Type '{ name: string; age: number; website: string; }' is not assignable to type 'Person'.
//   Object literal may only specify known properties, and 'website' does not exist in type 'Person'.

任意屬性

有時候咱們但願一個接口容許有任意的屬性,可使用以下方式:

interface Person {
  name: string;
  age?: number;
  [propName: string]: any;
}

let xcatliu: Person = {
  name: 'Xcat Liu',
  website: 'http://xcatliu.com',
};

使用 [propName: string] 定義了任意屬性取 string 類型的值。

須要注意的是,一旦定義了任意屬性,那麼肯定屬性和可選屬性都必須是它的子屬性:

interface Person {
  name: string;
  age?: number;
  [propName: string]: string;
}

let xcatliu: Person = {
  name: 'Xcat Liu',
  age: 25,
  website: 'http://xcatliu.com',
};

// index.ts(3,3): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; website: string; }' is not assignable to type 'Person'.
//   Index signatures are incompatible.
//     Type 'string | number' is not assignable to type 'string'.
//       Type 'number' is not assignable to type 'string'.

上例中,任意屬性的值容許是 string,可是可選屬性 age 的值倒是 number,number 不是 string 的子屬性,因此報錯了。

只讀屬性

有時候咱們但願對象中的一些字段只能在建立的時候被賦值,那麼能夠用 readonly 定義只讀屬性:

interface Person {
  readonly id: number;
  name: string;
  age?: number;
  [propName: string]: any;
}

let xcatliu: Person = {
  id: 89757,
  name: 'Xcat Liu',
  website: 'http://xcatliu.com',
};

xcatliu.id = 9527;

// index.ts(14,9): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.

上例中,使用 readonly 定義的屬性 id 初始化後,又被賦值了,因此報錯了。

只讀的約束存在於第一次給對象賦值的時候,而不是第一次給只讀屬性賦值的時候:

數組的類型

「類型 + 方括號」表示法

最簡單的方法是使用「類型 + 方括號」來表示數組:

let fibonacci: number[] = [1, 1, 2, 3, 5];

數組的項中不容許出現其餘的類型:

let fibonacci: number[] = [1, '1', 2, 3, 5];

// index.ts(1,5): error TS2322: Type '(number | string)[]' is not assignable to type 'number[]'.
//   Type 'number | string' is not assignable to type 'number'.
//     Type 'string' is not assignable to type 'number'.

數組泛型

也可使用數組泛型(Generic) Array<elemType> 來表示數組:

let fibonacci: Array<number> = [1, 1, 2, 3, 5];

any 在數組中的應用

一個比較常見的作法是,用 any 表示數組中容許出現任意類型:

let list: any[] = ['Xcat Liu', 25, { website: 'http://xcatliu.com' }];

函數類型

函數聲明

在 JavaScript 中,有兩種常見的定義函數的方式——函數聲明(Function Declaration)和函數表達式(Function Expression):

// 函數聲明(Function Declaration)
function sum(x, y) {
  return x + y;
}

// 函數表達式(Function Expression)
let mySum = function (x, y) {
  return x + y;
};

一個函數有輸入和輸出,要在 TypeScript 中對其進行約束,須要把輸入和輸出都考慮到,其中函數聲明的類型定義較簡單:

function sum(x: number, y: number): number {
  return x + y;
}

注意,輸入多餘的(或者少於要求的)參數,是不被容許的

函數表達式

若是要咱們如今寫一個對函數表達式(Function Expression)的定義,可能會寫成這樣:

let mySum = function (x: number, y: number): number {
  return x + y;
};

這是能夠經過編譯的,不過事實上,上面的代碼只對等號右側的匿名函數進行了類型定義,而等號左邊的 mySum,是經過賦值操做進行類型推論而推斷出來的。若是須要咱們手動給 mySum 添加類型,則應該是這樣:

let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
  return x + y;
};

在 TypeScript 的類型定義中,=> 用來表示函數的定義,左邊是輸入類型,須要用括號括起來,右邊是輸出類型。

接口中函數的定義

咱們也可使用接口的方式來定義一個函數須要符合的形狀:

interface SearchFunc {
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
  return source.search(subString) !== -1;
}

可選參數

前面提到,輸入多餘的(或者少於要求的)參數,是不容許的。那麼如何定義可選的參數呢?

與接口中的可選屬性相似,咱們用 ? 表示可選的參數:

function buildName(firstName: string, lastName?: string) {
  if (lastName) {
    return firstName + ' ' + lastName;
  } else {
    return firstName;
  }
}
let xcatliu = buildName('Xcat', 'Liu');
let xcat = buildName('Xcat');

須要注意的是,可選參數必須接在必需參數後面。換句話說,可選參數後面不容許再出現必須參數了:

function buildName(firstName?: string, lastName: string) {
  if (firstName) {
    return firstName + ' ' + lastName;
  } else {
    return lastName;
  }
}
let xcatliu = buildName('Xcat', 'Liu');
let xcat = buildName(undefined, 'Xcat');

// index.ts(1,40): error TS1016: A required parameter cannot follow an optional parameter.

參數默認值

在 ES6 中,咱們容許給函數的參數添加默認值,TypeScript 會將添加了默認值的參數識別爲可選參數:

function buildName(firstName: string, lastName: string = 'Liu') {
  return firstName + ' ' + lastName;
}
let xcatliu = buildName('Xcat', 'Liu');
let xcat = buildName('Xcat');

此時就不受「可選參數必須接在必需參數後面」的限制了:

function buildName(firstName: string = 'Xcat', lastName: string) {
  return firstName + ' ' + lastName;
}
let xcatliu = buildName('Xcat', 'Liu');
let xcat = buildName(undefined, 'Xcat');

剩餘參數

ES6 中,可使用 ...rest 的方式獲取函數中的剩餘參數(rest 參數):

function push(array, ...items) {
  items.forEach(function(item) {
    array.push(item);
  });
}

let a = [];
push(a, 1, 2, 3);

事實上,items 是一個數組。因此咱們能夠用數組的類型來定義它:

function push(array: any[], ...items: any[]) {
  items.forEach(function(item) {
    array.push(item);
  });
}

let a = [];
push(a, 1, 2, 3);

類型斷言

類型斷言(Type Assertion)能夠用來繞過編譯器的類型推斷,手動指定一個值的類型(即程序員對編譯器斷言)。

<類型>值

// 或

值 as 類型

當 TypeScript 不肯定一個聯合類型的變量究竟是哪一個類型的時候,咱們只能訪問此聯合類型的全部類型裏共有的屬性或方法:

function getLength(something: string | number): number {
  return something.length;
}

// index.ts(2,20): error TS2339: Property 'length' does not exist on type 'string | number'.
//   Property 'length' does not exist on type 'number'.

而有時候,咱們確實須要在還不肯定類型的時候就訪問其中一個類型的屬性或方法,此時可使用類型斷言,將 something 斷言成 string

function getLength(something: string | number): number {
  if ((<string>something).length) {
    return (<string>something).length;
  } else {
    return something.toString().length;
  }
}
相關文章
相關標籤/搜索