javascript - Đặt kiểu cho các khóa động với số lượng thuộc tính tối đa trong typescript
Tôi đang thắc mắc liệu có thể trong TypeScript để xác định kiểu số lượng tối đa các thuộc tính động trong một đối tượng hay không.
Vì vậy, ví dụ cơ bản là để theo dõi các sự kiện:
events.track('SOME_EVENT', { first: 'a', other: 'b', some: 'c'})
Dữ liệu sự kiện được cho là chứa tối đa 3 thuộc tính với các giá trị tương ứng của chúng, các khóa cũng có thể là động.
Tôi đã gõ nó với một Record cơ bản, nhưng không có giới hạn nào về số lượng thuộc tính được phép:
export interface Events {
track: (name: string, params?: Record<string, string | number | unknown>) => void;
}
Điều này có khả thi không?
Câu trả lời #1
Tôi đã có một giải pháp sử dụng TuplifyUnion từ đây:
Tôi không chắc điều này "an toàn" đến mức nào (xem phần từ chối trách nhiệm). Việc sử dụng TuplifyUnion được coi là không an toàn vì thứ tự có thể thay đổi bất cứ lúc nào. Vì thứ tự không quan trọng ở đây và chỉ số lượng phần tử mới quan trọng trong trường hợp này, tôi nghĩ sử dụng nó ở đây là ổn.
Giải pháp cho phép 0-3 khóa. Nếu bạn muốn số lượng khác, chỉ cần thêm chúng vào union (ví dụ: 1 | 2 chấp nhận 1 hoặc 2 khóa).
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
type LastOf<T> =
UnionToIntersection<T extends any ? () => T : never> extends () => (infer R) ? R : never
type Push<T extends any[], V> = [...T, V];
type TuplifyUnion<T, L = LastOf<T>, N = [T] extends [never] ? true : false> =
true extends N ? [] : Push<TuplifyUnion<Exclude<T, L>>, L>
type MaxThreeProperties<T extends Record<string, any>> =
TuplifyUnion<keyof T>["length"] extends 0 | 1 | 2 | 3 ? T : never
// add all acceptable key amounts here ^ ^ ^ ^
function track<T extends Record<string, any>>(
name: string,
params: MaxThreeProperties<T>
) {}
Về cơ bản, chúng tôi đặt tất cả các khóa vào một tuple và sau đó "thủ công" kiểm tra độ dài của tuple. Điều này có thể dễ dàng mở rộng cho các số lượng thuộc tính khác mặc dù có thể trông xấu.
Một nhược điểm là thông báo lỗi tuy nhiên:
Kiểu 'string' không thể gán cho kiểu 'never'.(2322)
Điều này có thể gây nhầm lẫn cho ai đó đang sử dụng chức năng...
Đây là một số bài kiểm tra:
track('SOME_EVENT', {}) // works
track('SOME_EVENT', {a: ""}) // works
track('SOME_EVENT', {a: "", b: ""}) // works
track('SOME_EVENT', {a: "", b: "", c: ""}) // works
track('SOME_EVENT', {a: "", b: "", c: "", d: ""}) // ERROR
track('SOME_EVENT', {a: "", b: "", c: "", d: "", e: ""}) // ERROR
const a = {a: "", b: "", c: ""}
const b = {a: "", b: "", c: "", d: ""}
track('SOME_EVENT', a) // works
track('SOME_EVENT', b) // ERROR
Sân chơi
Câu trả lời số 2
Tôi không thể nghĩ ra cách khai báo một giao diện đối tượng với 1-3 (nhưng chỉ 1-3) thuộc tính có tên là chuỗi không biết trước (nhưng điều đó không có nghĩa là không có cách; tôi chỉ ở mức trung cấp với TypeScript).
Tôi nghiêng về một hợp nhất của các bộ giá trị:
type EventParam = [name: string, value: string | number | unknown];
type EventParams =
[EventParam]
| [EventParam, EventParam]
| [EventParam, EventParam, EventParam];
export interface Events {
track: (name: string, params?: EventParams) => void;
}
declare let events: Events;
// Works with 1:
events.track("something", [["first", "a"]]);
// Works with 2:
events.track("something", [["first", "a"], ["other", "b"]]);
// Works with 3:
events.track("something", [["first", "a"], ["other", "b"], ["some", "c"]]);
// Fails with 4:
events.track("something", [["first", "a"], ["other", "b"], ["some", "c"], ["fourth", 42]]);
// Error as desired −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^^^^
Liên kết sân chơi
Nhưng điều đó có thể không có tính công thái học tốt nhất.
