Nhảy tới nội dung

Variance (variance)

Trong TypeScript, khi xác định tính tương thích của các kiểu, khái niệm variance được sử dụng. Variance biểu thị mối quan hệ giữa các kiểu, và trong TypeScript, để biểu thị variance này, ta thêm in hoặc out trước type variable của generics.

Lưu ý, variance được đề cập ở đây cũng có thể thay đổi bằng cấu hình strictFunctionTypes trong tsconfig.json.
Trong phần này, trừ khi có đề cập đặc biệt, ta giả định strictFunctionTypesfalse.

📄️ strictFunctionTypes

Làm nghiêm ngặt check variance của parameter type

Covariance (Covariance)

Covariance là variance khi thêm out vào type variable của generics. Covariance có nghĩa là mối quan hệ subtype được bảo toàn.

Contravariance (Contravariance)

Contravariance là variance khi thêm in vào type variable của generics. Contravariance có nghĩa là mối quan hệ subtype bị đảo ngược.

Invariance (Invariance)

Invariance là variance khi thêm cả inout vào type variable của generics. Invariance có nghĩa là kiểu không covariant cũng không contravariant.

Bivariance (Bivariance)

Bivariance là variance khi không thêm in hoặc out vào type variable của generics.

Ở đây, ta định nghĩa BivariantFunction<I, O> (không thêm variance nên trong TypeScript giống như bivariant) là kiểu của hàm nhận một tham số I và trả về giá trị O.

ts
type BivariantFunction<I, O> = (arg: I) => O;
ts
type BivariantFunction<I, O> = (arg: I) => O;

Tiếp theo, định nghĩa CovariantFunction<in I, O> với tham số I là covariant, ContravariantFunction<I, out O> với giá trị trả về O là contravariant, và InvariantFunction<in out I, in out O> với cả tham số và giá trị trả về đều invariant. Chúng được định nghĩa như sau:

ts
type BivariantFunction<I, O> = (arg: I) => O;
type CovariantFunction<I, out O> = BivariantFunction<I, O>;
type ContravariantFunction<in I, O> = BivariantFunction<I, O>;
type InvariantFunction<in out I, in out O> = BivariantFunction<I, O>;
ts
type BivariantFunction<I, O> = (arg: I) => O;
type CovariantFunction<I, out O> = BivariantFunction<I, O>;
type ContravariantFunction<in I, O> = BivariantFunction<I, O>;
type InvariantFunction<in out I, in out O> = BivariantFunction<I, O>;

Ví dụ sử dụng quan hệ kế thừa class

Để dễ hiểu quan hệ kế thừa, định nghĩa các class A, B, C. A kế thừa B, B kế thừa C, và đã thêm các method.

ts
class A {
public a(): void {
console.log("a");
}
}
 
class B extends A {
public b(): void {
console.log("b");
}
}
 
class C extends B {
public c(): void {
console.log("c");
}
}
ts
class A {
public a(): void {
console.log("a");
}
}
 
class B extends A {
public b(): void {
console.log("b");
}
}
 
class C extends B {
public c(): void {
console.log("c");
}
}

Định nghĩa các hàm với cả IO đều là B cho mỗi loại variance.

ts
declare const biFunc: BivariantFunction<B, B>;
declare const coFunc: CovariantFunction<B, B>;
declare const contraFunc: ContravariantFunction<B, B>;
declare const inFunc: InvariantFunction<B, B>;
ts
declare const biFunc: BivariantFunction<B, B>;
declare const coFunc: CovariantFunction<B, B>;
declare const contraFunc: ContravariantFunction<B, B>;
declare const inFunc: InvariantFunction<B, B>;

Thử thay đổi generics của các hàm này.

ts
const f01: BivariantFunction<A, B> = biFunc;
const f02: BivariantFunction<C, B> = biFunc;
const f03: BivariantFunction<B, A> = biFunc;
const f04: BivariantFunction<B, C> = biFunc;
Type 'BivariantFunction<B, B>' is not assignable to type 'BivariantFunction<B, C>'. Property 'c' is missing in type 'B' but required in type 'C'.2322Type 'BivariantFunction<B, B>' is not assignable to type 'BivariantFunction<B, C>'. Property 'c' is missing in type 'B' but required in type 'C'.
 
const f05: CovariantFunction<B, A> = coFunc;
const f06: CovariantFunction<B, C> = coFunc;
Type 'CovariantFunction<B, B>' is not assignable to type 'CovariantFunction<B, C>'. Property 'c' is missing in type 'B' but required in type 'C'.2322Type 'CovariantFunction<B, B>' is not assignable to type 'CovariantFunction<B, C>'. Property 'c' is missing in type 'B' but required in type 'C'.
 
const f07: ContravariantFunction<A, B> = contraFunc;
Type 'ContravariantFunction<B, B>' is not assignable to type 'ContravariantFunction<A, B>'. Property 'b' is missing in type 'A' but required in type 'B'.2322Type 'ContravariantFunction<B, B>' is not assignable to type 'ContravariantFunction<A, B>'. Property 'b' is missing in type 'A' but required in type 'B'.
const f08: ContravariantFunction<C, B> = contraFunc;
 
const f09: InvariantFunction<A, B> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<A, B>'. Property 'b' is missing in type 'A' but required in type 'B'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<A, B>'. Property 'b' is missing in type 'A' but required in type 'B'.
const f10: InvariantFunction<C, B> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<C, B>'. Property 'c' is missing in type 'B' but required in type 'C'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<C, B>'. Property 'c' is missing in type 'B' but required in type 'C'.
const f11: InvariantFunction<B, A> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, A>'. Property 'b' is missing in type 'A' but required in type 'B'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, A>'. Property 'b' is missing in type 'A' but required in type 'B'.
const f12: InvariantFunction<B, C> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, C>'. Property 'c' is missing in type 'B' but required in type 'C'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, C>'. Property 'c' is missing in type 'B' but required in type 'C'.
ts
const f01: BivariantFunction<A, B> = biFunc;
const f02: BivariantFunction<C, B> = biFunc;
const f03: BivariantFunction<B, A> = biFunc;
const f04: BivariantFunction<B, C> = biFunc;
Type 'BivariantFunction<B, B>' is not assignable to type 'BivariantFunction<B, C>'. Property 'c' is missing in type 'B' but required in type 'C'.2322Type 'BivariantFunction<B, B>' is not assignable to type 'BivariantFunction<B, C>'. Property 'c' is missing in type 'B' but required in type 'C'.
 
const f05: CovariantFunction<B, A> = coFunc;
const f06: CovariantFunction<B, C> = coFunc;
Type 'CovariantFunction<B, B>' is not assignable to type 'CovariantFunction<B, C>'. Property 'c' is missing in type 'B' but required in type 'C'.2322Type 'CovariantFunction<B, B>' is not assignable to type 'CovariantFunction<B, C>'. Property 'c' is missing in type 'B' but required in type 'C'.
 
const f07: ContravariantFunction<A, B> = contraFunc;
Type 'ContravariantFunction<B, B>' is not assignable to type 'ContravariantFunction<A, B>'. Property 'b' is missing in type 'A' but required in type 'B'.2322Type 'ContravariantFunction<B, B>' is not assignable to type 'ContravariantFunction<A, B>'. Property 'b' is missing in type 'A' but required in type 'B'.
const f08: ContravariantFunction<C, B> = contraFunc;
 
const f09: InvariantFunction<A, B> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<A, B>'. Property 'b' is missing in type 'A' but required in type 'B'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<A, B>'. Property 'b' is missing in type 'A' but required in type 'B'.
const f10: InvariantFunction<C, B> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<C, B>'. Property 'c' is missing in type 'B' but required in type 'C'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<C, B>'. Property 'c' is missing in type 'B' but required in type 'C'.
const f11: InvariantFunction<B, A> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, A>'. Property 'b' is missing in type 'A' but required in type 'B'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, A>'. Property 'b' is missing in type 'A' but required in type 'B'.
const f12: InvariantFunction<B, C> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, C>'. Property 'c' is missing in type 'B' but required in type 'C'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, C>'. Property 'c' is missing in type 'B' but required in type 'C'.

Tóm tắt những trường hợp xảy ra lỗi:

  1. f04: Tham số và giá trị trả về đều bivariant nên mối quan hệ supertype và subtype được chấp nhận, nhưng B không có method c() so với giá trị trả về C nên xảy ra lỗi
  2. f06: Giá trị trả về là covariant nên việc gán supertype được chấp nhận, nhưng giá trị trả về C là subtype của B nên xảy ra lỗi
  3. f07: Tham số là contravariant nên việc gán subtype được chấp nhận, nhưng tham số A là supertype của B nên xảy ra lỗi
  4. f09: Tham số và giá trị trả về đều invariant nên mối quan hệ supertype và subtype không được chấp nhận, do đó xảy ra lỗi
  5. f10: Tham số và giá trị trả về đều invariant nên mối quan hệ supertype và subtype không được chấp nhận, do đó xảy ra lỗi
  6. f11: Tham số và giá trị trả về đều invariant nên mối quan hệ supertype và subtype không được chấp nhận, do đó xảy ra lỗi
  7. f12: Tham số và giá trị trả về đều invariant nên mối quan hệ supertype và subtype không được chấp nhận, do đó xảy ra lỗi

Ngoài ra, khi đặt strictFunctionTypestrue, ngoài các lỗi trên còn có:

  1. f01: Tham số và giá trị trả về đều bivariant nên mối quan hệ supertype và subtype được chấp nhận, nhưng tham số A không có method b() nên xảy ra lỗi

Ví dụ sử dụng union type

Hãy biểu diễn quan hệ kế thừa bằng union type.

ts
type A = null;
type B = null | undefined;
type C = null | undefined | string;
ts
type A = null;
type B = null | undefined;
type C = null | undefined | string;

Lúc này A là subtype của B, và B là subtype của C. Nói cách khác, A extends BB extends C.

ts
const f01: BivariantFunction<A, B> = biFunc;
const f02: BivariantFunction<C, B> = biFunc;
const f03: BivariantFunction<B, A> = biFunc;
Type 'BivariantFunction<B, B>' is not assignable to type 'BivariantFunction<B, null>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.2322Type 'BivariantFunction<B, B>' is not assignable to type 'BivariantFunction<B, null>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.
const f04: BivariantFunction<B, C> = biFunc;
 
const f05: CovariantFunction<B, A> = coFunc;
Type 'CovariantFunction<B, B>' is not assignable to type 'CovariantFunction<B, null>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.2322Type 'CovariantFunction<B, B>' is not assignable to type 'CovariantFunction<B, null>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.
const f06: CovariantFunction<B, C> = coFunc;
 
const f07: ContravariantFunction<A, B> = contraFunc;
const f08: ContravariantFunction<C, B> = contraFunc;
Type 'ContravariantFunction<B, B>' is not assignable to type 'ContravariantFunction<C, B>'. Type 'C' is not assignable to type 'B'. Type 'string' is not assignable to type 'B'.2322Type 'ContravariantFunction<B, B>' is not assignable to type 'ContravariantFunction<C, B>'. Type 'C' is not assignable to type 'B'. Type 'string' is not assignable to type 'B'.
 
const f09: InvariantFunction<A, B> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<null, B>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<null, B>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.
const f10: InvariantFunction<C, B> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<C, B>'. Type 'C' is not assignable to type 'B'. Type 'string' is not assignable to type 'B'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<C, B>'. Type 'C' is not assignable to type 'B'. Type 'string' is not assignable to type 'B'.
const f11: InvariantFunction<B, A> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, null>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, null>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.
const f12: InvariantFunction<B, C> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, C>'. Type 'C' is not assignable to type 'B'. Type 'string' is not assignable to type 'B'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, C>'. Type 'C' is not assignable to type 'B'. Type 'string' is not assignable to type 'B'.
ts
const f01: BivariantFunction<A, B> = biFunc;
const f02: BivariantFunction<C, B> = biFunc;
const f03: BivariantFunction<B, A> = biFunc;
Type 'BivariantFunction<B, B>' is not assignable to type 'BivariantFunction<B, null>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.2322Type 'BivariantFunction<B, B>' is not assignable to type 'BivariantFunction<B, null>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.
const f04: BivariantFunction<B, C> = biFunc;
 
const f05: CovariantFunction<B, A> = coFunc;
Type 'CovariantFunction<B, B>' is not assignable to type 'CovariantFunction<B, null>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.2322Type 'CovariantFunction<B, B>' is not assignable to type 'CovariantFunction<B, null>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.
const f06: CovariantFunction<B, C> = coFunc;
 
const f07: ContravariantFunction<A, B> = contraFunc;
const f08: ContravariantFunction<C, B> = contraFunc;
Type 'ContravariantFunction<B, B>' is not assignable to type 'ContravariantFunction<C, B>'. Type 'C' is not assignable to type 'B'. Type 'string' is not assignable to type 'B'.2322Type 'ContravariantFunction<B, B>' is not assignable to type 'ContravariantFunction<C, B>'. Type 'C' is not assignable to type 'B'. Type 'string' is not assignable to type 'B'.
 
const f09: InvariantFunction<A, B> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<null, B>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<null, B>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.
const f10: InvariantFunction<C, B> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<C, B>'. Type 'C' is not assignable to type 'B'. Type 'string' is not assignable to type 'B'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<C, B>'. Type 'C' is not assignable to type 'B'. Type 'string' is not assignable to type 'B'.
const f11: InvariantFunction<B, A> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, null>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, null>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.
const f12: InvariantFunction<B, C> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, C>'. Type 'C' is not assignable to type 'B'. Type 'string' is not assignable to type 'B'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, C>'. Type 'C' is not assignable to type 'B'. Type 'string' is not assignable to type 'B'.
  1. f03: Tham số và giá trị trả về đều bivariant nên mối quan hệ supertype và subtype được chấp nhận, nhưng undefined của B không thể gán cho null của giá trị trả về A nên xảy ra lỗi
  2. f05: Giá trị trả về là covariant nên việc gán supertype được chấp nhận, nhưng undefined của B không thể gán cho giá trị trả về Anull nên xảy ra lỗi
  3. f08: Tham số là contravariant nên việc gán subtype được chấp nhận, nhưng string của tham số C không thể gán cho B nên xảy ra lỗi
  4. f09: Tham số và giá trị trả về đều invariant nên mối quan hệ supertype và subtype không được chấp nhận, do đó xảy ra lỗi
  5. f10: Tham số và giá trị trả về đều invariant nên mối quan hệ supertype và subtype không được chấp nhận, do đó xảy ra lỗi
  6. f11: Tham số và giá trị trả về đều invariant nên mối quan hệ supertype và subtype không được chấp nhận, do đó xảy ra lỗi
  7. f12: Tham số và giá trị trả về đều invariant nên mối quan hệ supertype và subtype không được chấp nhận, do đó xảy ra lỗi

Khi đặt strictFunctionTypestrue, ngoài các lỗi trên còn có:

  1. f02: Tham số và giá trị trả về đều bivariant nên mối quan hệ supertype và subtype được chấp nhận, nhưng string của tham số C không thể gán cho B nên xảy ra lỗi

Kiểm tra quan hệ kế thừa

Hãy sử dụng Conditional Types để kiểm tra quan hệ kế thừa. Định nghĩa kiểu IsSubType<T, U> để xác định xem kiểu T có phải là subtype của U hay không.

ts
type IsSubType<T, U> = T extends U ? true : false;
ts
type IsSubType<T, U> = T extends U ? true : false;

Áp dụng IsSubType<T, U> cho các class A, B, C trước đó.

ts
class A {
public a(): void {
console.log("a");
}
}
 
class B extends A {
public b(): void {
console.log("b");
}
}
 
class C extends B {
public c(): void {
console.log("c");
}
}
 
declare const t1: IsSubType<A, A>;
const t1: true
declare const t2: IsSubType<B, B>;
const t2: true
declare const t3: IsSubType<C, C>;
const t3: true
 
declare const t4: IsSubType<A, B>;
const t4: false
declare const t5: IsSubType<B, C>;
const t5: false
declare const t6: IsSubType<A, C>;
const t6: false
 
declare const t7: IsSubType<B, A>;
const t7: true
declare const t8: IsSubType<C, B>;
const t8: true
declare const t9: IsSubType<C, A>;
const t9: true
ts
class A {
public a(): void {
console.log("a");
}
}
 
class B extends A {
public b(): void {
console.log("b");
}
}
 
class C extends B {
public c(): void {
console.log("c");
}
}
 
declare const t1: IsSubType<A, A>;
const t1: true
declare const t2: IsSubType<B, B>;
const t2: true
declare const t3: IsSubType<C, C>;
const t3: true
 
declare const t4: IsSubType<A, B>;
const t4: false
declare const t5: IsSubType<B, C>;
const t5: false
declare const t6: IsSubType<A, C>;
const t6: false
 
declare const t7: IsSubType<B, A>;
const t7: true
declare const t8: IsSubType<C, B>;
const t8: true
declare const t9: IsSubType<C, A>;
const t9: true

Có thể thấy chỉ trả về true khi là chính class đó hoặc subclass.

Hãy xem union type. Lưu ý, vì đây là union type nên sử dụng Distributive Conditional Type.

📄️ Distributive Conditional Types

Distributive Conditional Types (có thể được gọi là kiểu điều kiện phân phối, phân phối union type, v.v.) chỉ tính chất khi Conditional Types được áp dụng cho generic type và type argument là union type, thì điều kiện sẽ được áp dụng riêng lẻ (= phân phối) cho từng thành viên cấu thành union type đó.

ts
type IsSubType<T, U> = [T] extends [U] ? true : false;
ts
type IsSubType<T, U> = [T] extends [U] ? true : false;
ts
type A = null;
type B = null | undefined;
type C = null | undefined | string;
 
declare const t1: IsSubType<A, A>;
const t1: true
declare const t2: IsSubType<B, B>;
const t2: true
declare const t3: IsSubType<C, C>;
const t3: true
 
declare const t4: IsSubType<A, B>;
const t4: true
declare const t5: IsSubType<B, C>;
const t5: true
declare const t6: IsSubType<A, C>;
const t6: true
 
declare const t7: IsSubType<B, A>;
const t7: false
declare const t8: IsSubType<C, B>;
const t8: false
declare const t9: IsSubType<C, A>;
const t9: false
ts
type A = null;
type B = null | undefined;
type C = null | undefined | string;
 
declare const t1: IsSubType<A, A>;
const t1: true
declare const t2: IsSubType<B, B>;
const t2: true
declare const t3: IsSubType<C, C>;
const t3: true
 
declare const t4: IsSubType<A, B>;
const t4: true
declare const t5: IsSubType<B, C>;
const t5: true
declare const t6: IsSubType<A, C>;
const t6: true
 
declare const t7: IsSubType<B, A>;
const t7: false
declare const t8: IsSubType<C, B>;
const t8: false
declare const t9: IsSubType<C, A>;
const t9: false

Có thể thấy t4, t5, t6 trả về trueA là subtype của B, và B là subtype của C.