TS 中的相容性,主要看結構是否相容
。(核心是考慮安全性),結構化的型別系統(又稱鴨子型別檢查),如兩個型別名字不一樣但是無法區分
型別相容性是基於結構子型別的。 結構型別是一種只使用其成員來描述型別的方式。
如果
x
要相容y
,那麼y
至少具有與x
相同的屬性。
這裡要檢查y
是否能賦值給x
,編譯器檢查x
中的每個屬性,看是否能在y
中也找到對應屬性。
X 相容 Y:X(目標型別)= Y(源型別)
簡單一句話概括相容性: 重新賦值不報錯(型別自動轉化)
let temp: string | number;
let num!: number;
temp = num;
let obj: {
toString(): string;
};
let str: string = "yya";
obj = str; // 字串中具備toString()方法,所以可以進行相容
obj.toString(); // 安全, 保證使用的時候不會發生異常
介面的相容性,只要滿足介面中所需要的型別即可!(保證你要的,我都有,就行,多了也沒關係)
interface IAnimal {
name: string;
age: number;
}
interface IPerson {
name: string;
age: number;
address: string;
}
let animal: IAnimal;
let person: IPerson = {
name: "yya",
age: 18,
address: "beijing",
};
type T2 = IPerson extends IAnimal ? true : false; // true
animal = person; // 子類賦予給父類別 相容
函數的相容性主要是比較引數和返回值
引數:賦值函數的引數要少於等於被賦值的函數:也就是說,對應函數的引數來講,少的引數可以賦予給多的
,因為內部實現傳了多個可以少用或不用(忽略額外的引數在 JavaScript 裡是很常見的)
sum2
的每個引數必須能在sum1
裡找到對應型別的引數。 注意的是引數的名字相同與否無所謂,只看它們的型別
。 sum2
的每個引數在sum1
中都能找到對應的引數,所以允許賦值。
let sum1 = (a: string, b: string) => a + b;
let sum2 = (a: string) => a;
sum1 = sum2;
舉例: Array#forEach
給回撥函數傳 3 個引數:item,index 和 array。 儘管如此,傳入一個只使用第一個引數的回撥函數也是可以的
type Func<T> = (item: T, index: number, array: any[]) => void;
function forEach<T>(arr: T[], cb: Func<T>) {
for (let i = 0; i < arr.length; i++) {
cb(arr[i], i, arr);
}
}
forEach([1, 2, 3], (item) => {
console.log(item);
});
返回值:
type sum1 = () => string | number;
type sum2 = () => string;
let fn1: sum1;
let fn2!: sum2;
fn1 = fn2;
類與物件字面量和介面差不多,但有一點不同:類有靜態部分和範例部分的型別。 比較兩個類型別的物件時,只有範例的成員會被比較。 靜態成員和建構函式不在比較的範圍內。
class Animal {
feet!: number;
constructor(name: string, numFeet: number) {}
}
class Size {
feet!: number;
constructor(numFeet: number) {}
}
let a!: Animal;
let s!: Size;
a = s; // OK
s = a; // OK
只要有 private 或者 protected 關鍵字會影響相容性, 當檢查類範例的相容時,如果目標型別包含一個 private 私有成員,那麼源型別必須包含來自同一個類的這個私有成員
。 這條規則也適用於包含 protected 受保護成員範例的型別檢查。 允許子類賦值給父類別,但是不能賦值給其它有同樣型別的類。
class A {
private name!: string;
age!: number;
}
class B {
private name!: string;
age!: number;
}
// let a: A = new B(); // error
class Parent {
protected name: string = "zf";
age: number = 11;
}
class Child extends Parent {}
let child: Parent = new Child(); // ok
泛型比較的是最終的結果 比較的不是泛型傳遞的引數
例一:
interface Empty<T> {}
let x: Empty<number>;
let y!: Empty<string>;
type xx = Empty<number> extends Empty<string> ? true : false; // true
x = y; // OK 因為 y 匹配 x 的結構
在例一中,x 和 y 是相容的,因為它們的結構使用型別引數時並沒有什麼不同。 把這個例子改變一下,增加一個成員,就能看出是如何工作的了:
例二:
interface NotEmpty<T> {
data: T;
}
let x: NotEmpty<number>;
let y: NotEmpty<string>;
type xx = NotEmpty<number> extends NotEmpty<string> ? true : false; // false
x = y; // Error, 不相容
對於沒指定泛型型別的泛型引數時,會把所有泛型引數當成 any 比較。 然後用結果型別進行比較,就像例一:
let identity = function <T>(x: T): T {};
let reverse = function <U>(y: U): U {};
identity = reverse; // OK, (x: any) => any 匹配 (y: any) => any
列舉型別與數位型別相容,並且數位型別與列舉型別相容
enum Status {
Pending,
Resolved,
Rejected,
}
let current = Status.Pending;
let num = 0;
current = num;
num = current;
不同列舉型別之間是不相容的。
enum Status {
Pending,
Resolved,
Rejected,
}
enum Color {
Red,
Blue,
Green,
}
let current = Status.Pending;
let color = Color.Red;
current = color; // 不能將型別「Color.Red」分配給型別「Status」
型別分為兩種 結構化型別(structural type system) 、標稱型別(nominal type system)
標稱型別: 雖然 BTC,USDT 都是 number 型別,但還是想要用不同的型別表示,且不能互換,資料的值本身沒什麼區別,安上不同名字就是不同型別,也就是說,標稱型別系統中,兩個變數是否型別相容(可以交換賦值)取決於這兩個變數顯式宣告的型別名字是否相同。
class AddType<S> {
private _type!: S;
}
type NewType<T, S extends string> = T & AddType<S>;
type BTC = NewType<number, "btc">; // number + BTC
type USDT = NewType<number, "usdt">; // number + USDT
let btc = 100 as BTC;
let usdt = 100 as USDT;
function getCount(count: USDT) {
return count;
}
getCount(usdt);