Как определить тип значения на основе ключа в итерации объекта TypeScript?

Я пытаюсь перебрать объект в TypeScript, где тип значения зависит от ключа. Я хочу, чтобы TypeScript сузил тип значения на основе ключа внутри цикла. Вот мой первоначальный код:

interface MyInterface {
  name: string;
  age: number;
  hobbies?: string[];
}

const obj: MyInterface = {
  name: 'John',
  age: 23,
};

for (const key in obj) {
  const value = obj[key];
  // TypeScript treats key as string, and value as any
  // I want it to treat value as string when key is 'name', number when key is 'age', etc.
}

В цикле for...in TypeScript рассматривает key как string, а value как any. Однако я хочу, чтобы он обрабатывал value как string, когда key есть 'name', number когда key есть 'age' и string[] когда key есть 'hobbies'.

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

function* iterateObjectTyped<T extends MyInterface, K extends keyof T>(
  obj: T
): Generator<[K, T[K]]> {
  for (const key in obj) {
    const value = obj[key as unknown as K];
    yield [key as unknown as K, value];
  }
}

for (const [key, value] of iterateObjectTyped(obj)) {
  // now TypeScript treats key as "name" | "age" | "hobbies"
  // and value as string | number | string[]
  // however:
  if (key === 'age') {
    value;
    // it still treats value as just string | number | string[]
    // even though I expected the above check to narrow down the predictions
  }
}

Но это не работает так, как ожидалось. Несмотря на то, что TypeScript теперь рассматривает key как "name" | "age" | "hobbies", а value как string | number | string[] внутри цикла, он не сужает тип value на основе key в проверке if.

Есть ли способ добиться этого в TypeScript? Я использую TypeScript 5.4.3. Любая помощь будет оценена по достоинству.

Вы не можете знать, что ключи только те, что в keyof T, смотрите этот вопрос . Если вы хотите дать своему «неудобно типизированному» генератору что-то, что ваш код будет понимать, вы можете сделать это таким образом. Это полностью решает вопрос? Если да, то я напишу ответ с объяснением; если нет, то что мне не хватает?

jcalz 07.04.2024 00:09

@jcalz Да, это то, что я искал.

chocojunkie 07.04.2024 00:38
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой 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
2
155
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Типы объектов в TypeScript являются открытыми/расширяемыми/"неточными", а не закрытыми/запечатанными/"точными" (где "неточный"/"точный" — это терминология из Flow). Это означает, что объекту разрешено содержать больше свойств, чем знает TypeScript:

const obj2 = { name: "John", age: 23, a: true, b: null };
const obj3: MyInterface = obj2; // okay
for (const key in obj3) {
  const value = obj[key];
        // ^? string
}

Здесь obj3 имеет свойства a и b, хотя он относится к типу MyInterface. Таким образом, все, что TypeScript действительно может сказать о key, это то, что он относится к типу string и, следовательно, не является известным ключом MyInterface, и, следовательно, value относится к типу any.

См. TypeScript: Object.keys возвращает строку[].

Если хотите, вы можете утверждать, что объект имеет только те ключи, о которых знает TypeScript, это нормально. Просто имейте в виду, что если вы ошиблись и что-то сломалось, вы несете за это ответственность.


В любом случае, если вы хотите дать своему генератору iterateObjectTyped сигнатуру вызова, чтобы каждый элемент возвращаемого вывода представлял собой запись пар [K, T[K]] для некоторых K в keyof T, вы можете написать это следующим образом:

function* iterateObjectTyped<T extends object>(
  obj: T
): Generator<{ [K in keyof T]-?: [K, T[K]] }[keyof T]> {
  for (const key in obj) {
    const value = obj[key];
    yield [key, value];
  }
}

Тип { [K in keyof T]-?: [K, T[K]] }[keyof T] — это так называемый тип распространяемого объекта, как он был придуман в microsoft/TypeScript#47109 . Он перебирает каждый ключ K из T, создает [K, T[K]], а затем приводит к их объединению.

Итак, если мы позвоним iterateObjectTyped(obj), вы увидите:

// function iterateObjectTyped<MyInterface>(obj: MyInterface): Generator<
//   ["name", string] | ["age", number] | ["hobbies", string[] | undefined]
// >

Это объединение пар, и поскольку первый элемент каждой пары представляет собой литеральный тип , он действует как дискриминантное свойство дискриминируемого объединения:

for (const [key, value] of iterateObjectTyped(obj)) {
  if (key === 'age') {
    value; // const value: number
  }
}

Это работает так, как и ожидалось, поскольку поддерживает деструктуризацию размеченных объединений на отдельные переменные. Таким образом, проверка key === 'age' сузит value до number.

Детская площадка, ссылка на код

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