我希望能够将对象属性分配给一个值,给定一个键和值作为输入,但仍然能够确定值的类型。这有点难以解释,所以这段代码应该揭示了问题:
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更改为给定键的值?
你可以使用泛型的帮助来定义T,它是一个JWT的键,值的类型是JWT[T]
function onChange<T extends keyof JWT>(key: T, value: JWT[T]);
这里唯一的问题是在实现下面的obj[key] = value + ' (assigned)';将无法工作,因为它将尝试将字符串分配给string & Date。这里的修复是将索引从键更改为令牌,这样编译器就知道目标变量类型是字符串。
另一种解决这个问题的方法是使用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)';
}
使用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 />,
},
];
使用下面的函数,可以将值限制为特定键的值。
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)
你可以为你自己创建一个Generic来获取值的类型,但是,请考虑object的声明应该声明为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