#typescript
Вопрос:
Могу ли я каким-то образом определить тип в машинописном тексте, который также применяет аритмические ограничения к реквизитам объекта, например.
type RestrictedObject = {
a: number,
b: number,
}
так что a
% b
= 0
Комментарии:
1. Ты не можешь. Единственное, что вы можете сделать, это использовать геттеры и сеттеры и выполнить проверку при установке нового значения
Ответ №1:
Нет, это потребовало бы проверки во время выполнения, что выходит за рамки типоскрипта.
Вы можете определить объект со свойствами метода доступа, чтобы принудительно применить отношение:
class Restricted {
#a: number;
#b: number;
static #valid(a: number, b: number): boolean {
return a % b === 0;
}
constructor(a: number, b:number) {
if (Restricted.#valid(a, b)) {
throw new Error(`The values of 'a' and 'b' must be such that 'a % b' is 0; ${a} and ${b} don't fit`);
}
this.#a = a;
this.#b = b;
}
get a() {
return this.#a;
}
set a(value) {
if (this.#b !== null) {
if (Restricted.#valid(value, this.#b)) {
throw new Error(`The value of 'a' cannot be ${value} when 'b' is ${this.#b}`);
}
}
this.#a = value;
}
get b() {
return this.#b;
}
set b(value) {
if (this.#a !== null) {
if (Restricted.#valid(this.#a, value)) {
throw new Error(`The value of 'b' cannot be ${value} when 'a' is ${this.#a}`);
}
}
this.#b = value;
}
}
Примечание: вышесказанное использует собственные частные поля JavaScript и частные статические методы, которые сейчас являются указанными частями языка, но вы могли private
бы вместо этого использовать TypeScript, если хотите.
Ответ №2:
Если вы хотите писать меньше кода, вы можете просто выдать ошибку в конструкторе:
class RestrictedObject {
constructor(public a: number, public b: number) {
if (a % b !== 0) {
throw new Error(`Remainder of ${a}/${b} must be zero.`);
}
}
}
console.log(new RestrictedObject(4, 3).a);
// Error: Remainder of 4/3 must be zero.
Но если вы любите приключения и действительно хотите заниматься только одними типами, вы можете этого достичь:
type Length<T extends any[]> = T extends { length: infer L } ? L : never;
type BuildTuple<L extends number, T extends any[] = []> = T extends { length: L }
? T
: BuildTuple<L, [...T, any]>;
type Subtract<A extends number, B extends number> = BuildTuple<A> extends [...infer U, ...BuildTuple<B>]
? Length<U>
: never;
type EQ<A, B> = A extends B ? (B extends A ? true : false) : false;
type AtTerminus<A extends number, B extends number> = A extends 0 ? true : B extends 0 ? true : false;
type LT<A extends number, B extends number> = AtTerminus<A, B> extends true
? EQ<A, B> extends true
? false
: A extends 0
? true
: false
: LT<Subtract<A, 1>, Subtract<B, 1>>;
type Modulo<A extends number, B extends number> = LT<A, B> extends true ? A : Modulo<Subtract<A, B>, B>;
type RestrictedObject<A extends number, B extends number, T = Modulo<A, B>> = {
a: T extends 0 ? A : never;
b: T extends 0 ? B : never;
};
const foo: RestrictedObject<4, 2> = {
a: 4,
b: 2
};
И когда ошибаешься:
const foo: RestrictedObject<4, 3> = {
a: 4, // type error: Type 'number' is not assignable to type 'never'.
b: 2 // type error: Type 'number' is not assignable to type 'never'.
};