Чи є `valueof` подібний до` keyof` в TypeScript?


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це не тип дати.

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

Відповіді:


221

ОНОВЛЕННЯ: Схоже, заголовок питання приваблює людей, які шукають об'єднання всіх можливих типів властивостей, аналогічно тому, як 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

41

Якщо хтось все ще шукає реалізації valueofбудь-яких цілей, я придумав це:

type valueof<T> = T[keyof T]

Використання:

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

Працює, як очікувалося :) Повертає союз усіх можливих типів


2
Це добре. Це майже вирішує мою іншу проблему , проте комутатор, здається, зовсім не звужує тип.
— стиле

Я хочу створити 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

2

Дякую існуючим відповідям, які вирішують проблему ідеально. Просто хотів додати lib, включив цей тип утиліти, якщо ви бажаєте імпортувати цю загальну.

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>;

2

Спробуйте це:

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...']>

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.