Есть ли в TypeScript valueof, похожий на keyof?


119

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

type JWT = { id: string, token: string, expire: Date };
const obj: JWT = { id: 'abc123', token: 'tk01', expire: new Date(2018, 2, 14) };

function print(key: keyof JWT) {
    switch (key) {
        case 'id':
        case 'token':
            console.log(obj[key].toUpperCase());
            break;
        case 'expire':
            console.log(obj[key].toISOString());
            break;
    }
}

function onChange(key: keyof JWT, value: any) {
    switch (key) {
        case 'id':
        case 'token':
            obj[key] = value + ' (assigned)';
            break;
        case 'expire':
            obj[key] = value;
            break;
    }
}

print('id');
print('expire');
onChange('id', 'def456');
onChange('expire', new Date(2018, 3, 14));
print('id');
print('expire');

onChange('expire', 1337); // should fail here at compile time
print('expire'); // actually fails here at run time

Я попытался изменить value: anyна, value: valueof JWTно это не сработало.

В идеале onChange('expire', 1337)он потерпит неудачу, потому что 1337это не тип Date.

Как я могу изменить value: anyзначение данного ключа?

Ответы:


226

ОБНОВЛЕНИЕ: похоже, что заголовок вопроса привлекает людей, которые ищут объединение всех возможных типов значений свойств, аналогично тому, как keyofобъединение всех возможных типов ключей свойств. Давайте сначала поможем этим людям. Вы можете сделать ValueOfаналогичный keyof, используя типы поиска с keyof Tключом, например:

type ValueOf<T> = T[keyof T];

что дает вам

type Foo = { a: string, b: number };
type ValueOfFoo = ValueOf<Foo>; // string | number

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

type sameAsString = Foo['a']; // lookup a in Foo
type sameAsNumber = Foo['b']; // lookup b in Foo

Чтобы убедиться, что пара ключ / значение "совпадает" должным образом в функции, вы должны использовать универсальные типы, а также типы поиска, например:

declare function onChange<K extends keyof JWT>(key: K, value: JWT[K]): void; 
onChange('id', 'def456'); // okay
onChange('expire', new Date(2018, 3, 14)); // okay
onChange('expire', 1337); // error. 1337 not assignable to Date

Идея состоит в том, что keyпараметр позволяет компилятору определять общий Kпараметр. Затем он требует, чтобы valueсовпадения JWT[K], нужный вам тип поиска.

Надеюсь, это поможет; удачи!


Я хочу создать tupleиз ценностей. Есть ли шанс это сделать?
— Morteza Tourani

Это звучит как несвязанный вопрос, поэтому вам, вероятно, следует задать его где-нибудь в другом месте, с минимально воспроизводимым примером, чтобы люди наверняка поняли ваш вариант использования. Удачи.
— jcalz

1
Возникла проблема с использованием строкового перечисления с функциями-членами. Чтобы справиться с этим хорошо, вы можете использовать type StringValueOf<T> = T[keyof T] & string;. Лучшие документы, которые я нашел в перечислениях строк, - это примечания к выпуску TypeScript 2.9
— karmakaze

42

Если кто-то все еще ищет реализацию valueofдля каких-либо целей, это то, что я придумал:

type valueof<T> = T[keyof T]

Использование:

type actions = {
  a: {
    type: 'Reset'
    data: number
  }
  b: {
    type: 'Apply'
    data: string
  }
}
type actionValues = valueof<actions>

Работает как положено :) Возвращает объединение всех возможных типов


2
Это хорошо. Это почти решает мою другую проблему , однако переключатель, похоже, совсем не сужает тип.
— Styfle

Я хочу создать tupleиз ценностей. Есть ли шанс это сделать?
— Morteza Tourani

12

Есть еще один способ извлечь тип объединения объекта:

  const myObj = { a: 1, b: 'some_string' } as const;
  type values = typeof myObj[keyof typeof myObj];

Результат: 1 | "some_string"


Это constочень ценная вещь, TypeScript фактически предоставит сами значения и удалит дубликаты; Это очень хорошо для словарей.
— Ноам

Именно то, что я искал!
— Mavrick RMX '21

3

Попробуй это:

type ValueOf<T> = T extends any[] ? T[number] : T[keyof T]

Он работает с массивом или простым объектом.

// type TEST1 = boolean | 42 | "heyhey"
type TEST1 = ValueOf<{ foo: 42, sort: 'heyhey', bool: boolean }>
// type TEST2 = 1 | 4 | 9 | "zzz..."
type TEST2 = ValueOf<[1, 4, 9, 'zzz...']>

2

Спасибо за существующие ответы, которые отлично решают проблему. Просто хотел добавить, что библиотека включает этот тип утилит, если вы предпочитаете импортировать эту общую.

https://github.com/piotrwitek/utility-types#valuestypet

import { ValuesType } from 'utility-types';

type Props = { name: string; age: number; visible: boolean };
// Expect: string | number | boolean
type PropsValues = ValuesType<Props>;

0

Один лайнер:

type ValueTypesOfPropFromMyCoolType = MyCoolType[keyof MyCoolType];

Пример универсального метода:

declare function doStuff<V extends MyCoolType[keyof MyCoolType]>(propertyName: keyof MyCoolType, value: V) => void;
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.