在TypeScript中是否存在类似于`keyof`的`valueof`?

时间:2018-03-14 19:05:54

标签: typescript types

我希望能够将对象属性分配给给定键和值作为输入的值,但仍能够确定值的类型。这有点难以解释,所以这段代码应该揭示问题:

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更改为给定密钥的值?

8 个答案:

答案 0 :(得分:69)

更新:看起来问题标题吸引了寻找所有可能属性值类型的联合的人,类似于keyof为您提供所有可能属性键类型的并集的方式。让我们先帮助那些人。您可以ValueOfkeyof类似,使用lookup types并以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

为了确保键/值对在函数中“正确匹配”,您应该使用generics以及查找类型,如下所示:

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],即您需要的查找类型。

希望有所帮助;祝你好运!

答案 1 :(得分:27)

如果有人仍然为任何目的寻找valueof的实现,那么这就是我提出的:

type valueof<T> = T[keyof T]

用法:

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

按预期工作:)返回所有可能类型的联合

答案 2 :(得分:22)

还有另一种提取对象的联合类型的方法:

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

结果:1 | "some_string"

答案 3 :(得分:5)

使用下面的函数,您可以将值限制为该特定键的值。

function setAttribute<T extends Object, U extends keyof T>(obj: T, key: U, value: T[U]) {
    obj[key] = value;
}

示例

interface Pet {
     name: string;
     age: number;
}

const dog: Pet = { name: 'firulais', age: 8 };

setAttribute(dog, 'name', 'peluche')     <-- Works
setAttribute(dog, 'name', 100)           <-- Error (number is not string)
setAttribute(dog, 'age', 2)              <-- Works
setAttribute(dog, 'lastname', '')        <-- Error (lastname is not a property)

答案 4 :(得分: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...']>

答案 5 :(得分:1)

感谢现有的答案,可以完美解决问题。只是想添加一个包含此实用程序类型的库,如果您想导入该通用类型。

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

答案 6 :(得分:0)

单线:

type ValueTypesOfPropFromMyCoolType = MyCoolType[keyof MyCoolType];

有关通用方法的示例:

declare function doStuff<V extends MyCoolType[keyof MyCoolType]>(propertyName: keyof MyCoolType, value: V) => void;

答案 7 :(得分:0)

您可以使用泛型的帮助来定义 T,它是 JWT 的键,值的类型为 JWT[T]

function onChange<T extends keyof JWT>(key: T, value: JWT[T]);

这里唯一的问题是在实现中,跟随 obj[key] = value + ' (assigned)'; 将不起作用,因为它会尝试将 string 分配给 string & Date。此处的解决方法是将索引从 key 更改为 token,以便编译器知道目标变量类型为 string

另一种解决问题的方法是使用 Type Guard

// IF we have such a guard defined
function isId(input: string): input is 'id' {
  if(input === 'id') {
    return true;
  }

  return false;
}

// THEN we could do an assignment in "if" block
// instead of switch and compiler knows obj[key] 
// expects string value
if(isId(key)) {
  obj[key] = value + ' (assigned)';
}