TypeScript中有类似于keyof的valueof吗?

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

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

164532 次浏览

更新:看起来问题标题吸引人们寻找所有可能的属性值类型的并集,类似于keyof给你所有可能的属性键类型的并集。让我们先帮助那些人。通过使用索引访问类型keyof T作为键,可以使ValueOf类似于keyof,如下所示:

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

这让你

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

对于上述问题,您可以使用比keyof T更窄的单个键来提取您关心的值类型:

type sameAsString = Foo['a']; // look up a in Foo
type sameAsNumber = Foo['b']; // look up 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]

如果有人还在为任何目的寻找valueof的实现,这是我想出的一个:

type valueof<T> = T[keyof T]

用法:

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

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

感谢现有的答案,完美地解决了这个问题。只是想添加一个库已经包括这个实用程序类型,如果你更喜欢导入这个常见的。

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

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

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

结果:1 | "some_string"

试试这个:

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

一行程序:

type ValueTypesOfPropFromMyCoolType = MyCoolType[keyof MyCoolType];

泛型方法示例:

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

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

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)

你可以使用泛型的帮助来定义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)';
}

你可以为自己创建一个Generic来获取值的类型,,请考虑对象的声明应该声明为as const,像这样:

export const APP_ENTITIES = {
person: 'PERSON',
page: 'PAGE',
} as const; <--- this `as const` I meant

然后下面的泛型将正常工作:

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

现在像下面这样使用它:

const entity: ValueOf<typeof APP_ENTITIES> = 'P...'; // ... means typing


// it refers 'PAGE' and 'PERSON' to you

使用type-fest lib,你可以用ValueOf这样做:

import type { ValueOf } from 'type-fest';


export const PATH_NAMES = {
home: '/',
users: '/users',
login: '/login',
signup: '/signup',
};


interface IMenu {
id: ValueOf<typeof PATH_NAMES>;
label: string;
onClick: () => void;
icon: ReactNode;
}


const menus: IMenu[] = [
{
id: PATH_NAMES.home,
label: t('common:home'),
onClick: () => dispatch(showHome()),
icon: <GroupIcon />,
},
{
id: PATH_NAMES.users,
label: t('user:users'),
onClick: () => dispatch(showUsers()),
icon: <GroupIcon />,
},
];