kiểu union - Typescript - Truyền một phần tử của UnionType vào hàm nhưng không phải cả hai
Tôi có hai giao diện và muốn tạo một hàm mà trong đó hoặc giao diện A hoặc giao diện B được bao gồm nhưng không phải cả hai cùng một lúc. Việc sử dụng một UnionType đơn giản không hoạt động vì nó cũng cho phép cả hai giao diện được bao gồm.
Ví dụ (https://stackblitz.com/edit/typescript-qgytsy)
interface A{
a: string;
}
interface B{
b: string;
}
function aOrB(modifier: A | B) {}
aOrB({ a: '' }); // works!
aOrB({ b: '' }); // works!
aOrB({ a: '', b: '' }); // also works but should not work!
Có cách nào để đạt được điều này, sao cho hai lần gọi đầu tiên tới aOrB(...) hoạt động nhưng lần gọi cuối thì không?
Trân trọng, Benedikt
Câu trả lời #1
Bạn có thể định nghĩa một kiểu mà loại trừ một số thuộc tính:
type IFoo = {
bar: string; can?: never
} | {
bar?: never; can: number
};
let val0: IFoo = { bar: "hello" } // OK only bar
let val1: IFoo = { can: 22 } // OK only can
let val2: IFoo = { bar: "hello", can: 22 } // Error foo and can
let val3: IFoo = { } // Error neither foo or can
Sau đó sử dụng loại đó cho tham số bộ điều chỉnh.
Câu trả lời #2
Hệ thống kiểu TS là cấu trúc, có nghĩa là {a: 'a', b: 'b'} là thành phần hợp lệ của A | B vì nó có thể gán cho A do nó có tất cả các thuộc tính của A và có thể gán cho B vì lý do tương tự. Không có vấn đề gì khi sử dụng đối tượng như vậy ở nơi yêu cầu A hoặc B vì nó có tất cả những gì cả hai cần về mặt cấu trúc. Xem xét đoạn mã sau:
function fOnA(modifier: A) { } // we require only A
const a = { a: '', b: '' } // we create object with additional props
fOnA(a) // no error
Chúng ta có thể thực hiện kiểm tra kiểu của kiểu { a: '', b: '' }.
type ExtendsA = { a: '', b: '' } extends A ? true : false // evaluates true
type ExtendsB = { a: '', b: '' } extends B ? true : false // evaluates true
Như bạn có thể thấy, { a: '', b: '' } là một đối tượng hợp lệ để sử dụng cho A và cho B.
Để rõ ràng, việc sử dụng cấu trúc như vậy với các thuộc tính bổ sung không có hại, vì chúng ta luôn có những gì chúng ta cần.
Trường hợp khi chúng ta muốn phân biệt xem chúng ta có A hay B là một trường hợp mà chúng ta nên tạo bộ phân biệt để có thể kiểm tra loại đối tượng nào đã đi vào hàm. Hãy xem xét:
interface A{
kind: 'A', // discriminant
a: string;
}
interface B{
kind: 'B', // discriminant
b: string;
}
function aOrB(modifier: A | B) {}
aOrB({ kind: 'A', a: '' }); // works!
aOrB({ kind: 'B', b: '' }); // works!
aOrB({ kind: 'B', a: '' }); // error as it should be
aOrB({ kind: 'B', b: '', a: '' }); // error as it should be
Tôi đã thêm kind để phân biệt cả hai phiên bản. Cách tiếp cận này có nhiều lợi ích, vì bây giờ chúng ta có thể dễ dàng kiểm tra xem chúng ta có A hay B chỉ bằng cách kiểm tra kind.
Một giải pháp khác là chặn các thuộc tính mà chúng ta không muốn bằng từ khóa never. Xem xét:
interface A{
a: string;
b?: never;
}
interface B{
b: string;
a?: never;
}
function aOrB(modifier: A | B) {}
aOrB({ a: '' }); // works!
aOrB({ b: '' }); // works!
aOrB({ a: '', b: ''}); // error as it should be
Bằng cách thêm prop?: never; tôi tránh cho phép đối tượng vượt qua các yêu cầu của A và B.
