TypeScript真香系列的內容將參考中文文檔,可是文中的例子基本不會和文檔中的例子重複,對於一些地方也會深刻研究。另外,文中一些例子的結果都是在代碼沒有錯誤後編譯爲JavaScript獲得的。若是想實際看看TypeScript編譯爲JavaScript的代碼,能夠訪問TypeScript的在線編譯地址,動手操做,印象更加深入。javascript
交叉類型是將多個類型合併爲一個類型,至關於一種並的操做。java
interface IDog {
name: string,
age: number,
}
interface ICat {
name: string,
color: string
}
let animal: IDog & ICat;
animal = {
name: "哈士奇",
age: 1,
color: "white",
}
animal.name; // "哈士奇"
animal.age; // 1
複製代碼
上面animal中的屬性一個都不能少,若是少了屬性的話,就會出現下面的錯誤: igit
nterface IDog {
name: string,
age: number,
}
interface ICat {
name: string,
color: string
}
let animal: IDog & ICat;
animal = { //錯誤,color屬性在ICat中是必須的
name: "哈士奇",
age: 1,
// color: "white",
}
複製代碼
聯合類型能夠說是和交叉類型相反,聲明的類型不肯定,能夠是多個類型中的一個或幾個。github
let a: number | string;
a = 1;
a = "s";
a = false; // 錯誤,類型false不能分配給類型 number | string
複製代碼
看一個和交叉類型相對應的例子:typescript
interface IDog {
name: string,
age: number,
}
interface ICat {
name: string,
color: string
}
let animal: IDog | ICat; // 這裏咱們把&改爲了|
animal = { //沒有報錯
name: "哈士奇",
age: 1,
// color: "white",
}
animal.name;
animal.age;
複製代碼
再看一個例子:函數
interface IDog {
name: string,
age: number,
}
interface ICat {
name: string,
color: string
}
let animal: IDog | ICat;
animal = {
name: "哈士奇",
age: 1,
color: "white",
}
animal.name;
animal.age; //錯誤,age不存在於ICat. age不存在於IDog | ICat
複製代碼
咱們能夠看見上面的例子出現了錯誤,這是由於TypeScript編譯器age不知道是IDog仍是ICat,因此只能訪問公共的name屬性。若是咱們想要訪問這個屬性的話,該怎麼辦?咱們可使用類型斷言:ui
interface IDog {
name: string,
age: number,
}
interface ICat {
name: string,
color: string
}
let animal: IDog | ICat;
animal = {
name: "哈士奇",
age: 1,
color: "white",
}
animal.name;
(<IDog>animal).age; // 1 複製代碼
這下就能訪問age屬性了。spa
有時候咱們會遇到相似於下面這種場景:prototype
interface IDog {
name: string,
age: number,
}
interface ICat {
name: string,
color: string
}
function animal(arg: IDog | ICat): any {
if (arg.color) { //錯誤
return arg.color //錯誤
}
}
複製代碼
可是上面的代碼會出現錯誤。若是想要上面這段代碼正常工做,能夠和聯合類型中的例子同樣,使用類型斷言:code
interface IDog {
name: string,
age: number,
}
interface ICat {
name: string,
color: string
}
function animal(arg: IDog | ICat):any {
if ((<ICat>arg).color) {
return (<ICat>arg).color;
}
}
複製代碼
除了類型斷言,咱們還能夠利用類型保護來進行判斷,經常使用的類型保護有三種:typeof類型保護,instanceof類型保護和自定義類型保護。
function animal(arg: number | string): any {
if (typeof arg === "string") {
return arg + "貓";
}
}
複製代碼
typeof類型保護只有兩種形式能被識別: typeof v === "typename"
和 typeof v !== "typename"
。"typename"必須是 "number", "string", "boolean"或 "symbol"。
class Dog {
name: string;
age: number;
constructor() {
};
}
class Cat {
name: string;
color: string;
constructor() {
};
}
let animal: Dog | Cat = new Dog();
if (animal instanceof Dog) {
animal.name = "dog";
animal.age = 6;
}
if (animal instanceof Cat) {
animal.name = "cat";
animal.color = "white";
}
console.log(animal); //Dog {name: "dog", age: 6}
複製代碼
instanceof的右側要求是一個構造函數,TypeScript將細化爲:
對於一些複雜的狀況,咱們能夠自定義來進行類型保護:
interface IDog {
name: string,
age: number,
}
interface ICat {
name: string,
color: string
}
let animal: IDog | ICat;
animal = {
name: "哈士奇",
age: 6,
}
function isDog(arg: IDog | ICat): arg is IDog {
return arg !== undefined;
}
if (isDog(animal)) {
console.log(animal.age); //6
}
複製代碼
類型別名能夠給類型取一個別名。類型別名和接口相似,但又有不一樣。
type Name = number;
type Types = number | string;
type NAndT = Name & Types;
type MyFunc = () => number;
function animal(arg: Types) {
return arg;
}
animal("哈士奇"); //"哈士奇"
複製代碼
類型別名能夠做用於原始類型、聯合類型、泛型等等。
type Dog<T> = { value: T };
function dog(arg: Dog<string>) {
return arg;
}
dog({ value: "哈士奇" }); //{value: "哈士奇"}
dog({ value: 1}); //錯誤,類型number不能分配給string
dog("哈士奇"); //錯誤,參數「哈士奇」不能分配給類型 Dog<string>
複製代碼
區別一:接口能夠建立新的名字,並且能夠在其它任何地方使用;類型別名不建立新的名字,而是起一個別名。 區別二:類型別名能夠進行聯合,交叉等操做。 區別三:接口能夠被extends和implements以及聲明合併等,而類型別名不能夠。
這裏介紹一下聲明合併:
「聲明合併」是指編譯器將針對同一個名字的兩個獨立聲明合併爲單一聲明。 合併後的聲明同時擁有原先兩個聲明的特性。 任何數量的聲明均可被合併;不侷限於兩個聲明。
舉個例子:
interface IDog {
name: string;
setName(arg:string): string;
}
interface IDog {
age: number;
}
interface IDog {
color: string;
}
let dog: IDog;
dog = {
color: "black",
age: 6,
name: "哈士奇",
setName: (arg) => {
return arg
}
}
複製代碼
合併以後:
interface IDog {
color: string;
age: number;
name: string;
setName(arg:string): string;
}
複製代碼
咱們能夠看出,後面的接口在合併後出如今了靠前的位置。
字符串字面量容許咱們指定字符串爲必須的固定值。
type Dog = "哈士奇" | "泰迪" | "中華田園犬" | "薩摩耶";
function dog(arg: Dog):any {
switch (arg) {
case "哈士奇":
return "傻狗";
case "泰迪":
return "精力旺盛";
case "中華田園犬":
return "忠誠";
case "薩摩耶":
return "微笑天使";
}
}
dog("哈士奇"); //"傻狗"
dog("柯基"); //錯誤,參數"柯基"不能分配給類型Dog
複製代碼
數字字面量同理。
type Num = 1 | 2 | 3 | 4 | 5 | 6 | 7;
function week(arg: Num):any {
switch (arg) {
case 1:
return "星期一";
case 2:
return "星期二";
case 3:
return "星期三";
case 4:
return "星期四";
case 5:
return "星期五";
case 6:
return "星期六";
case 7:
return "星期日";
}
}
week(6); //"星期六"
week(8); //錯誤
複製代碼
咱們能夠合併單例類型、聯合類型、類型保護和類型別名來建立一個叫作可辨識聯合的高級模式。它具備三個要素:
能夠看看下面這個例子就能理解了:
//咱們首先聲明瞭將要聯合的接口,目前各個接口之間是沒有聯繫的,
//只是都有一個kind的屬性(能夠稱爲可辨識特徵或標籤)可是有不一樣的字符串字面量類型
interface IColor {
kind: "color";
value: string;
}
interface ISize {
kind: "size";
height: number;
width: number;
}
//而後咱們利用類型別名和聯合類型把兩個接口聯合到一塊兒
type MyType = IColor | ISize;
//最後使用可辨識聯合
function types(arg: MyType) :any{
switch (arg.kind) {
case "color":
return arg.value;
case "size":
return arg.height * arg.width;
}
}
types({ kind: "color", value: "blue" }); //"blue"
types({ kind: "size", height: 10, width: 20 }); //200
複製代碼
使用索引類型,編譯器就可以檢查使用動態屬性名的代碼。咱們首先要知道兩個操做符。
keyof T
,意爲:對於任何類型T
,keyof T
的結果爲T
上已知公共屬性的聯合;T[K]
。interface IDog{
a: string,
b: number,
c: boolean,
}
let dog: keyof IDog; //let dog: "a" | "b" | "c"
let arg: IDog["a"]; //let arg: string
複製代碼
再看一個較爲複雜的例子:
interface IDog{
name: string,
age: number,
value: string,
}
let dog: IDog;
dog = {
name: "二哈",
age: 6,
value: "奧裏給"
}
function myDog<T, K extends keyof T>(x: T, args: K[]): T[K][] {
return args.map(i => x[i]);
}
myDog(dog, ['name']); //["二哈"]
myDog(dog, ['name', 'value']); //["二哈", "奧裏給"]
myDog(dog, ['key']); //錯誤,類型不匹配
複製代碼
有時候咱們能夠會遇到這種狀況,把每一個成員都變爲可選或者只讀:
interface Person{
name: string;
agent: number;
}
interface PersonPartial {
name?: string;
age?: number;
}
interface PersonReadonly {
readonly name: string;
readonly age: number;
}
複製代碼
這在JavaScript裏常常出現,TypeScript提供了從舊類型中建立新類型的一種方式 — 映射類型。 在映射類型裏,新類型以相同的形式去轉換舊類型裏每一個屬性。
interface IPerson{
name: string;
agent: number;
}
type OPartial<T> = {
[P in keyof T]?: T[P];
}
type OReadonly<T> = {
readonly [P in keyof T]: T[P];
}
//使用方式
type PersonPartial = OPartial<IPerson>;
type ReadonlyPerson = OReadonly<IPerson>;
複製代碼
文中有些地方可能會加入一些本身的理解,如有不許確或錯誤的地方,歡迎指出~