Как ввести ключи вложенного объекта в TypeScript?

У меня есть несколько интерфейсов, которые расширяют базовый интерфейс. например:

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 — объединение дочерних интерфейсов) обеспечивает подсказку типов некоторый, которая помогает предотвратить ключи, которые вообще не существуют, но все же позволяет использовать ключи из других интерфейсов.

Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
4
0
40
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Одним из возможных решений было бы использование перегрузок функций:

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

Однако это потенциально может быстро выйти из-под контроля с несколькими ключами и дочерними элементами.

catgirlkelly 05.05.2022 15:51

Спасибо, это достойный подход, но я стараюсь не добавлять код каждый раз, когда добавляю новый интерфейс. Хотя мне все равно нужно создать объединение всех дочерних интерфейсов. Буду иметь это в виду для других проблем.

Johansson 05.05.2022 20:15
Ответ принят как подходящий

Это можно сделать расширяемым образом с помощью 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 05.05.2022 20:14

@Johansson Эй, я не хочу быть навязчивым, но если ответ поможет решить вашу проблему, вы можете пометить его как принятый.

user16912306 05.05.2022 21:19

Другие вопросы по теме