Возможно ли для TypeScript выводить ключи из динамического объекта? - PullRequest
0 голосов
/ 26 октября 2018

Здесь я пытаюсь достичь intellisense / autocomplete для объекта, сгенерированного из массива - что-то вроде Action Creator для Redux, массива строк (string[]), который можно преобразовать в объект с помощьюshape { [string]: string }.

Например:

const a = ['ONE', 'TWO', 'THREE'];

const b = a.reduce((acc, type) => ({ ...acc, [type]: type }), {});

console.log(b);
// Output: b = { ONE: 'ONE', TWO: 'TWO', THREE: 'THREE' };

Мне удалось заставить TypeScript понять это, используя приведенное ниже.TypeScript понимает, что ключи являются строками, но не знает, что это такое.

interface ITypesReturnObject { [s: string]: string }

Кто-нибудь разработал способ сообщить TypeScript, что ключи на объекте равны строкам в массиве?

Любая помощь будет принята с благодарностью.

Ответы [ 3 ]

0 голосов
/ 26 октября 2018

(при условии, что вы используете TS3.0 или более позднюю версию)

TypeScript поддерживает концепцию строковых литеральных типов , а также типов кортежей , поэтомувозможно получить ваше значение a для типа ['ONE', 'TWO', 'THREE'], а также значение ['ONE', 'TWO', 'THREE'], например:

const a: ['ONE', 'TWO', 'THREE'] = ['ONE', 'TWO', 'THREE'];

(Менее избыточный способ добиться этого случится позже):

Тогда вы можете представить предполагаемый тип b как отображение ключей на значения, которые точно соответствуют ключу, используя mappedвведите :

type SameValueAsKeys<KS extends string[]> = { [K in KS[number]]: K };

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

const b: SameValueAsKeys<typeof a> = a.reduce((acc, type) => ({ ...acc, [type]: type }), {} as any);
b.ONE; // property type is "ONE"
b.TWO; // property type is "TWO"
b.THREE; // property type is "THREE"

Обратите внимание, как компилятор знает, что b имеет три ключа, "ONE", "TWO"и "THREE", и значения совпадают с ключами.


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

Во-первых, для a ... компилятор не выводит типы кортежей, а также имеет тенденцию расширять строковые литералы до типа string, за исключением определенных экземпляров .Давайте введем вспомогательную функцию с именем stringTuple():

function stringTuple<T extends string[]>(...args: T) { return args; }

Это выводит тип кортежа из аргументов остальных .Теперь мы можем использовать его для создания a без ввода избыточных строковых значений:

const a = stringTuple("ONE", "TWO", "THREE");

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

function keyArrayToSameValueAsKeys<T extends string[]>(keys: T): SameValueAsKeys<T>;
function keyArrayToSameValueAsKeys(keys: string[]): { [k: string]: string } {
  return keys.reduce((acc, type) => ({ ...acc, [type]: type }), {});
}

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

const b = keyArrayToSameValueAsKeys(a);
b.ONE; // property type is "ONE"
b.TWO; // property type is "TWO"
b.THREE; // property type is "THREE"

Если вы поместите stringTuple() и keyArrayToSameValueAsKeys() в библиотеку, пользователь сможет использовать их без особых проблем:

const otherObject = keyArrayToSameValueAsKeys(stringTuple("x", "y", "z"));
// const otherObject: {X: "X", Y: "Y", Z: "Z"}

Или вы можете объединить их вместе следующим образом:

function keysToSameValueAsKeys<T extends string[]>(...keys: T): { [K in T[number]]: K };
function keysToSameValueAsKeys(keys: string[]): { [k: string]: string } {
  return keys.reduce((acc, type) => ({ ...acc, [type]: type }), {});
}

И затем получить результат за один вызов, например так:

const lastOne = keysToSameValueAsKeys("tic", "tac", "toe");
// const lastOne: {tic: "tic", tac: "tac", toe: "toe"};

Хорошо, надеюсь, что этонекоторая помощь.Удачи!

0 голосов
/ 05 июля 2019

Пришел сюда в поисках лучшего ответа.Вот то, что я придумал, и это прекрасно работает:

const makeStyles = <T extends string = string>(styles: Record<T, string>): Record<T, string> => {
  return styles;
}

const classes = makeStyles({
  hello: 'hello world',
  another: 'something else'
})

/** 
 * now typescript will infer the keynames that appear in the object passed 
 * to makeStyles
 */
console.log(classes)


0 голосов
/ 26 октября 2018

Если вы заранее знаете, что будет в массиве, вы можете сделать что-то вроде этого:

type myType = 'ONE' | 'TWO' | 'THREE';
const a: myType[] = ['ONE', 'TWO', 'THREE'];

const b: { [key in myType]: myType } = a.reduce<{ [key in myType]: myType }>((acc, type) => ({ ...acc, [type]: type }), <any>{});

// correct intellisense
b.ONE === 'ONE'
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...