Typescript ограничивает общий тип строкового литерала для использования в свойстве вычисляемого объекта

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

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

Это компилируется и делает то, что я хочу, при условии, что K является строковым литералом. Обратите внимание, что в typescript 3.4 утверждение типа не требовалось, но требуется в 3.5.

function makeObject<K extends string>(key: K): { [P in K]: string } {
  return { [key]: "Hello, World!" } as { [P in K]: string };
}

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

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

  • ограничить K только строковыми литералами
  • указать, что тип возвращаемого значения будет объектом с одним полем, имя которого является значением в K (менее удовлетворительно, но, по крайней мере, тип функции будет честным)

Может ли система типов машинописного текста выражать любой из них?

Если я удаляю утверждение типа в typescript 3.5, я получаю сообщение об ошибке:

a.ts:2:3 - error TS2322: Type '{ [x: string]: string; }' is not assignable to type '{ [P in K]: string; }'.

2   return { [key]: "Hello, World!" };

Хотя не уверен, каким должен быть результат. что должно makeObject("a" as "a" | "b"); вернуть? { a?: string; b?: string; } может? мы можем получить триггер ошибки, если K не является однострочным литералом (т.е. не объединением), это то, что вы хотите?

Titian Cernicova-Dragomir 30.05.2019 09:48

В идеале ваш пример приведет к ошибке типа, потому что аргумент представляет собой объединение строковых литералов, а не строковый литерал.

stevebob 30.05.2019 11:02
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой 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 для повышения производительности приложения путем загрузки модулей только тогда, когда они...
13
2
6 986
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Нет никаких ограничений на то, чтобы что-то было однострочным литералом. Если вы укажете extends string, компилятор выведет типы строковых литералов для K, но он также по определению разрешит объединения типов строковых литералов (ведь набор объединения типов строковых литералов включен в набор всех строк)

Мы можем создать пользовательскую ошибку, которая заставляет as call находиться в состоянии ошибки, если обнаруживает объединение типов строковых литералов. Такую проверку можно сделать с помощью условных типов, убедившись, что K совпадает с UnionToIntersection<K>. Если это так, то K не является союзом, так как 'a' extends 'a' но 'a' | 'b' не распространяется 'a' & 'b'

type UnionToIntersection<U> =
    (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

type CheckForUnion<T, TErr, TOk> = [T] extends [UnionToIntersection<T>] ? TOk : TErr

function makeObject<K extends string>(key: K & CheckForUnion<K, never, {}>): { [P in K]: string } {
    return { [key]: "Hello, World!" } as { [P in K]: string };
}

makeObject("a")
makeObject("a" as "a" | "b") // Argument of type '"a" | "b"' is not assignable to parameter of type 'never'

Какова цель написания типов кортежей [T] extends [UnionToIntersection<T>]? Это просто для предотвращения распространения условия по объединению в случае, когда T является типом объединения?

stevebob 31.05.2019 00:34

Кроме того, для объяснения того, почему UnionToIntersection работает, см. stackoverflow.com/questions/50374908/….

stevebob 31.05.2019 00:38

Обновление для TypeScript 4.2

Следующие работы:

type StringLiteral<T> = T extends string ? string extends T ? never : T : never;

(Больше не работает): трюк с буквальным типом шаблона TypeScript 4.1

Обновлено: приведенное ниже фактически сломалось в 4.2. Обсуждение здесь

type StringLiteral<T> = T extends `${string & T}` ? T : never;

В TS 4.1 представлен литеральные типы шаблонов, который позволяет преобразовывать строковые литералы в другие строковые литералы. Вы можете преобразовать строковый литерал в себя. Поскольку шаблонами могут быть только литералы, а не общие строки, вы просто проверяете условно, что строковый литерал наследуется от самого себя.

Полный пример:

type StringLiteral<T> = T extends `${string & T}` ? T : never;

type CheckLiteral = StringLiteral<'foo'>;  // type is 'foo'
type CheckString = StringLiteral<string>;  // type is never

function makeObject<K>(key: StringLiteral<K>) {
    return { [key]: 'Hello, World!' } as { [P in StringLiteral<K>]: string };
}

const resultWithLiteral = makeObject('hello');  // type is {hello: string;}
let someString = 'prop';
const resultWithString = makeObject(someString); // compiler error.

Я не думаю, что объединение для K больше не является проблемой, потому что нет необходимости сужать тип для ключа свойства в подписи makeObject. Во всяком случае, это становится более гибким.

Можно ли превратить универсальный строковый литерал в фактическое строковое значение?

Douglas Gaskell 22.05.2021 00:26

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