?子類型:給定兩個類型A和B,假設B是A的子類型,那么在需要A的地方都可以放心使用B。計作 A <: B (A是B的子類型)。
超類型正好與子類型相反。A >: B (A是B的超類型)。
1 TS 類型
可賦值性指在判斷需要B類型的地方是否可以使用A類型。當A <: B 時,是滿足可賦值性的,不過TS有兩處例外的地方:
1)當A是any時,可賦值給B類型。
function testFun(b:boolean) {console.log(b); // 2
}
let a:any = 2;
testFun(a);
TS 這樣做是為了方便與JS代碼互操作。
2)B 是枚舉類型,且至少有一個成員是number類型,而且A是數字。
這是安全性的一大隱患,盡量不要這么操作。
1.1 型變
如果想要保證A可賦值給B對象,那么A對象的每個屬性都必須<: B 對象對應的屬性。此時,我們說TS對結構(對象和類)的屬性類型進行了協變。
type User = {name: string,money?: number
}
type VipUser = {name: string,money: number
}let user: User = {name: "牛",
}
let vipUser:VipUser = {name: "張",money: 1000
}user = vipUser;
vipUser = user; //ERROR Type User is not assignable to type VipUser
不變 | 只能是T | 逆變 | 可以是 >: T |
協變 | 可以是 <: T | 雙變 | 可以是 <: T或 >: T |
表 型變的四種方式
在TS中,每個復雜類型的成員都會進行協變,包括函數的返回類型。不過有個例外:函數的參數類型進行逆變。
1.1.1 函數的型變
如果函數A的參數數量小等于函數B的參數數量,且同時滿足下述條件,那么函數A是函數B的子類型:
1)函數A的this類型未指定,或者>:函數B的this類型。
2)函數A的各個參數的類型 >: 函數B對應參數。
3)函數A的返回類型<: 函數B的返回類型。
class Person {constructor(public sex: number) {}
}
class Man extends Person{constructor(public name: string,sex: number) {super(sex);}
}type FunA = (this: Person) => void; // 子類型
type FunB = (this: Man) => void; // 父類型let person:Person = new Person(1);
let man:Man = new Man("李",1);let funA:FunA = function () {console.log(this);}
let funB:FunB = function () {console.log(this);}funA.bind(person)();
//funB.bind(person); // ERRORfunA.bind(man)();
funB.bind(man)();funB = funA;
// funA = funB; // ERROR
函數的協變似乎有些特殊,在我們常規認知中,子類型的作用范圍窄于父類型。但是在上面例子中,作為子類型的A函數類型,比父類B可綁定的對象類型更廣。子類的唯一定義是:在需要父類的地方可以放心使用子類。上面代碼在使用B的地方,可以放心使用A,而使用A的地方不能放心使用B。
type FunA = (this:any) => void; // 子類
type FunB = (this:Man) => void; // 父類let funA:FunA = function () {console.log(this);};
let funB:FunB = function () {console.log(this);};funA.bind(man)();
funB.bind(man)();// funB.bind(person)(); // ERROR
funA.bind(person)();let fun1: FunA = funB;
let fun2: FunB = funA;
上面代碼展示了一個特殊情況:當this為any類型(或未指定時)。這里符合第一個條件。
type FunA = (person: Person) => void; // 子類
type FunB = (man: Man) => void; // 父類let funA:FunA = function(person:Person) {console.log("funA:" + person.sex);}
let funB:FunB = function (man:Man) {console.log("funB:" + man.name);}function testFunA(fun: (person: Person) => void) {}
function testFunB(fun: (man: Man) => void) {let man = new Man("張",1);fun(man);
}testFunA(funA);
// testFun(funB); // ERROR
testFunB(funA);
testFunB(funB);
1.2 類型擴展
一般來說,TS在推導類型時會放寬要求,故意推導出一個更寬泛的類型。聲明變量時如果允許以后修改變量的值,變量的類型將拓展,從字面量擴大到包含該字面量的基類型:
let a = “x”; // string
let b = 1; // number
let c = {x: 1}; // {x: number}
聲明為不可變的變量時:
const a = “x”; // “x”
可以通過顯示注解類型,防止類型被擴展:
let a: “x” = “x”; // “x”
如果使用let或var 重新為不可變的變量賦值,將自動擴展:
const a = “x”; // “x”
let b = a; // string
const 會禁止類型拓寬,還遞歸把成員設為readonly:
let x = {a: “t”,b: {x: 12}} as const; // {readonly a: “t”,readonly b: {readonly x:12}}
1.2.1 多余屬性檢查
TS嘗試把新鮮對象字面量類型T賦值給另一個類型U,如果T有U不存在的屬性,則報錯。
新鮮對象:TS直接從字面量推導出的類型。如果把對象字面量賦值給變量或者對象字面量有斷言,則其不是新鮮對象,而是常規對象。
type ObjType = {name: string
}let obj1:ObjType = {name: ""
}
let obj2:ObjType = {name: "",type: 1 // 報錯
} // 為新鮮對象
let obj3:ObjType = {name: "",type: 1
} as ObjType // true 有類型斷言function fun(obj: ObjType) {}
let obj4 = {name: "",type: 1
}
fun(obj4); // true 已被賦值給變量,為常規對象
fun({name: "",type: 1 // 報錯
}) // 為新鮮變量
2 對象類型進階
2.1 類型運算符
我們可以像獲取對象屬性值一樣獲取類型的某個屬性類型 (必須用方括號,不能用點號):
type ObjType = {name: string,user: {type: number,name: string}
};
type UserType = ObjType["user"]; // {type: number,name:string}
keyof 運算符獲取對象所有鍵的類型,合并為一個字符串字面量類型,以上面的ObjType為例:
type ObjTypeKeysType = keyof ObjType; // "name" | "user"
type UserTypeKeysType = keyof UserType; // "type" | "name"
2.2 映射類型
TS中,我們可以為類型定義索引簽名[key:T] : U,該類型的對象可能有更多的鍵,鍵的類型為T,值的類型為U。其中T必須為number或string。
而映射類型則在索引簽名的基礎上,為key做了限制。這讓類型更安全:
type IndexType = {[K: string]: string
} // 只限定了key 為 string類型type MappedType = {[K in "key1" | "key2"]: string
} // 限定了屬性必須有且只有“key1”和“key2”let indexType: IndexType = {"a": "","b": ""
}; //
let mappedType: MappedType = {"key1": "","key2": ""
}
let mappedType2:MappedType = {"key1": ""
} // 報錯 Property key2 is missing in type { key1: string; } but required in type MappedType
2.2.1 Record類型
TS內置的Record類型用于描述有映射關系的對象。是使用映射類型實現的。
type RecordType = Record<"a"|"b",1 | 2 | 3>let r1: RecordType = {"a": 1,"b": 1,
}
let r2: RecordType = {"a": 1,"b": 5
} // 報錯 Type 5 is not assignable to type 1 | 2 | 3
let r3: RecordType = {"a": 1
} // 報錯 Property b is missing in type { a: 1; } but required in type RecordType
2.3 伴生對象模式
TS的類型和值分別在不同的命名空間。在同一作用域中,我們可以有同名的類型和值。在語義上歸屬同一個名稱的類型和值放在一起,其次,使用方可以一次性導入二者。
type User = {name: string
}
let User = {createUser(name:string):User {console.log("這是值創建的");return {name: name}}
}
let user:User = User.createUser("hmf");