Вы можете получить то, что вы хотите, используя сопоставленные условные типы и самодельные обобщенные типы (о которых я не могу найти хорошую документацию по TypeScript; но в Java * 1004 есть аналогичные варианты использования)* что может быть хорошо читает?).Давайте посмотрим:
type Constrained<T> = {
[K in keyof T]: T[K] extends object ? Constrained<T[K]> :
T[K] extends string | number ? T[K] : never
}
A Constrained<T>
принимает тип T
и рекурсивно проходит через него, проверяя, является ли каждое свойство либо string
, number
, либо object
это также соответствует Constrained
.Любое свойство, которое ему не нравится, заменяется на never
.
С этим вы можете создавать свои интерфейсы следующим образом:
interface Test extends Constrained<Test> {
a: string; // okay
b: number; // okay
// c: boolean; // uncommenting this causes an error
d: { foo: string; bar: string } // okay
}
Обратите внимание на самодельный универсальный шаблон, где объявлено, что Test
расширяет Constrained<Test>
.Это обеспечивает точное ограничение, которое вы хотите без подписей индекса.Если вы добавите свойство, которое не соответствует ограничению, оно выдаст вам ошибку, обычно что-то вроде Type 'XXX' is not assignable to type 'never'
.
. Чтобы сделать это с классами, это будет выглядеть примерно так:
class Test2 implements Constrained<Test2> {
public prop1: number = 1;
}
class Test1 implements Constrained<Test1> {
public prop1: string = "a";
public prop2: number = 1;
public nestedProp: Test2 = new Test2();
}
(Я добавил инициализаторы, поскольку TypeScript теперь жалуется, когда вы не инициализируете экземпляры классов).И ваша функция работает так:
function someFunction<T extends Constrained<T>>(x: T) { }
someFunction(new Test1());
Надеюсь, это поможет.Удачи!