Как сообщить машинописному тексту, что объект гарантированно имеет определенное (динамическое) свойство?

У меня есть следующий машинописный код (ну, более сложный, потому что он написан на Vue, поэтому некоторые части реактивны).

type A = {
  a: string;
}

type B = {
  b: string;
}

const getObj = function(useA: boolean): A|B {
  if (useA) {
    return { a: 'test'} as A;
  } else {
    return { b: 'test'} as B;
  }
}

const getKey = function(useA: boolean): 'a'|'b' {
  if (useA) {
    return 'a';
  } else {
    return 'b';
  }
}

const getValue = function(useA: boolean): string {
  return getObj(useA)[getKey(useA)];
}

В getValue есть ошибка машинописного текста, потому что нельзя гарантировать, что объект из getObj будет тем типом, для которого ключ возвращен из getKey. Есть ли способ реорганизовать это с помощью дженериков или чего-то еще, чтобы избежать этой ошибки? В качестве альтернативы, могу ли я сказать машинописному тексту игнорировать ошибку (например, комментарии, которые отключают линтинг в строке)? Или как-то использовать утверждения типа? В реальном случае есть более двух возможностей, и использование гардов было бы грязным.

Я понимаю, почему машинописному тексту это не нравится, но я надеюсь либо найти способ сделать это, не относящийся к типу, либо сказать машинописному тексту, что меня устраивает риск/гарантия, что свойство будет там.

Также обратите внимание, что я не могу поместить общую функцию в A/B, чтобы просто вернуть значение, потому что в реальном коде я не контролирую эти типы/объекты.

Перегрузки функций и утверждения типов являются идиоматическим решением в подобных случаях (и обеспечивают отличный DX ). Вы можете использовать что-то вроде это. Это отвечает на ваш вопрос? Если да, я могу написать это как ответ — если нет, то что я упускаю?
jsejcksn 15.04.2023 16:22

Если вы действительно хотите провести рефакторинг, чтобы компилятор мог проверить безопасность типов, вам, вероятно, потребуется выполнить исправление для ms/TS#30581 , как описано в ms/TS#47109 , хотя наличие ввода boolean делает это более странно и сложно. Похоже на эту версию, то есть 🙃, но она настолько близка, насколько мы можем здесь использовать проверку типов компилятора. Я, вероятно, предпочел бы просто использовать утверждения типа в этом случае, но если вы хотите, чтобы я написал ответ, объясняющий это, дайте мне знать.

jcalz 15.04.2023 16:42

Да, @jsejcksn, эта игровая площадка помогла. На самом деле, даже не требуется подробного ввода getObj и getKey... достаточно было изменить тело getValue на: return getObj(useA)[getKey(useA) as keyof typeof getObj]; Это, вероятно, не гарантирует безопасность, но это был самый простой ответ для устранения ошибки.

Dov Rosenberg 15.04.2023 23:15
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой 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 для повышения производительности приложения путем загрузки модулей только тогда, когда они...
1
3
71
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы можете сделать так, чтобы функция getObj возвращала универсальный тип T, а затем приводила возвращаемое значение, если вас устраивает риск того, что свойство может отсутствовать во время выполнения.

type A = {
  a: string;
}

type B = {
  b: string;
}

const getObj = function(useA: boolean): T {
  return useA ? { a: 'test' } : { b: 'test' };
}

далее в коде:

const aObj = getObj(true) as A;
console.info(aObj.a);
const bObj = getObj(false) as B;
console.info(bObj.b);

Обратите внимание, что типы исчезают во время компиляции, поэтому, если во время выполнения по какой-либо причине свойство a не существует в aObj или b не существует в bObj, эти значения будут неопределенными, и вы будете передавать неопределенные значения, что может привести к ошибкам.

в качестве альтернативы, если вы хотите убедиться, что у объектов есть соответствующие ключи и значения, вы можете сделать что-то вроде этого:

const aObj = getObj(true);
if (aObj.a === undefined) {
    // handle error
}

Для удобства, для функции getKey вы можете фактически ввести результат с типом, который возвращает только a или b следующим образом:

const ABKeys = ['a', 'b'] as const;
type ABKey = typeof ABKeys[number];

const getKey = function (useA: boolean): ABKey {
    return useA ? ABKeys[0] : ABKeys[1];
}

Я не думаю, что этот подход на самом деле помогает в случае желания сделать: obj = getObj(variableObjType)[variableKey], где известно, что variableObjType и variableKey совпадают, но неизвестно, что они собой представляют.

Dov Rosenberg 16.04.2023 14:26
Ответ принят как подходящий

Вы можете использовать утверждение типа, чтобы помочь компилятору понять корреляцию между возвращаемыми типами двух функций с одним и тем же входным параметром:

function getValue(useA: boolean): string {
  return getObj(useA)[getKey(useA) as keyof typeof getObj];
  //                               ^^^^^^^^^^^^^^^^^^^^^^
  //                                   type assertion
}

Вы также можете использовать сигнатуры перегрузки функций для предоставления более узких, конкретных типов возвращаемых значений, связанных с их входными параметрами — это улучшит правильность типов, а также улучшит опыт разработчиков при использовании редактора кода с интерактивной обратной связью (например, IntelliSense). в VS Code или на игровой площадке TypeScript).

Вот полный пример:

Игровая площадка ТС

type A = { a: string };
type B = { b: string };

function getObj(useA: true): A;
function getObj(useA: false): B;
function getObj(useA: boolean): A | B;
function getObj(useA: boolean) {
  return useA ? { a: "test" } : { b: "test" };
}

function getKey(useA: true): "a";
function getKey(useA: false): "b";
function getKey(useA: boolean): "a" | "b";
function getKey(useA: boolean) {
  return useA ? "a" : "b";
}

function getValue(useA: boolean): string {
  return getObj(useA)[getKey(useA) as keyof typeof getObj];
}


// Other examples:

const o1 = getObj(true);
    //^? const o1: A

const o2 = getObj(false);
    //^? const o2: B

const o3 = getObj(true as boolean);
    //^? const o3: A | B

const k1 = getKey(true);
    //^? const k1: "a"

const k2 = getKey(false);
    //^? const k2: "b"

const k3 = getKey(true as boolean);
    //^? const k3: "a" | "b"

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