У меня есть несколько интерфейсов, которые расширяют базовый интерфейс. например:
interface Base {
sharedKey : "a" | "b"
}
interface ChildOne extends Base {
sharedKey : "a",
keyOne : "child_one"
}
interface ChildTwo extends Base {
sharedKey : "b",
keyTwo : "child_two"
}
Все расширенные интерфейсы определяют общий ключ, который сужает определение. Я пытаюсь реализовать функцию, которую я видел во многих IDE, которая указывает ключи вложенных объектов. Рассмотрим такую функцию:
function doStuff( sharedKey, privateKey ) { // Something here};
Эта функция примет ключ, который является a
или b
, и на основе этого ключа определит, разрешено ли privateKey
быть keyOne
или keyTwo
.
Например, это должно быть разрешено:
doStuff( 'a', 'keyOne' ); // Allowed
Но это не должно:
doStuff( 'b', 'keyOne' ); // Error: keyOne does not exist on ChildTwo
Это возможно? Я пытался использовать литералы шаблонов, но проблема в том, что возвращаемый тип из литералов шаблонов был строкой, и я не могу использовать его как тип.
Использование Record<Unions['sharedKey'], Unions>
(где Unions
— объединение дочерних интерфейсов) обеспечивает подсказку типов некоторый, которая помогает предотвратить ключи, которые вообще не существуют, но все же позволяет использовать ключи из других интерфейсов.
Одним из возможных решений было бы использование перегрузок функций:
interface ChildOne {
sharedKey : "a",
keyOne : "child_one"
}
interface ChildTwo {
sharedKey : "b",
keyTwo : "child_two"
}
function doStuff( sharedKey: ChildOne['sharedKey'], privateKey: keyof ChildOne ):number
function doStuff( sharedKey: ChildTwo['sharedKey'], privateKey: keyof ChildTwo ):number
function doStuff( sharedKey: string, privateKey: string) {
// Something here
return 0;
};
doStuff( 'a', 'keyOne' ); // Allowed
doStuff( 'b', 'keyOne' ); // Error: keyOne does not exist on ChildTwo
Спасибо, это достойный подход, но я стараюсь не добавлять код каждый раз, когда добавляю новый интерфейс. Хотя мне все равно нужно создать объединение всех дочерних интерфейсов. Буду иметь это в виду для других проблем.
Это можно сделать расширяемым образом с помощью Extract
:
function doStuff<
SharedKey extends Union["sharedKey"],
PrivateKey extends Exclude<keyof Extract<Union, { sharedKey: SharedKey }>, "sharedKey">
>(sharedKey: SharedKey, privateKey: PrivateKey) { ... }
где Union
- объединение всех детей.
Вы получаете используемый общий ключ, а затем используете его для извлечения дочернего элемента из союза, у которого есть этот ключ.
Как только у вас есть правильный дочерний элемент, вы получаете ключи этого дочернего элемента, исключая «sharedKey» (если вы также не хотите, чтобы sharedKey отображался в подсказке типа?).
По сути, это та же идея, что и ответ @PabloCarrilloAlvarez, выраженный в виде дженериков.
doStuff("a", "keyOne") // good
doStuff("b", "keyOne") // error
doStuff("b", "keyTwo") // good
doStuff("a", "keyTwo") // error
Вы можете попробовать этот метод с несколькими примерами вызовов ниже:
Спасибо, приятно использовать встроенные функции!
@Johansson Эй, я не хочу быть навязчивым, но если ответ поможет решить вашу проблему, вы можете пометить его как принятый.
Однако это потенциально может быстро выйти из-под контроля с несколькими ключами и дочерними элементами.