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

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


当前回答

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

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)

其他回答

我知道这有点离题了,每次我都在寻找解决方法。我被派到这个岗位。对于那些正在寻找字符串文字类型生成器的人,这里。

这将从对象类型创建一个字符串文字列表。

export type StringLiteralList<T, K extends keyof T> = T[keyof Pick<T, K>];

type DogNameType = { name: "Bob", breed: "Boxer" } | { name: "Pepper", breed: "Spaniel" } | { name: "Polly", breed: "Spaniel" };

export type DogNames = StringLiteralList<DogNameType, "name">;

// type DogNames = "Bob" | "Pepper" | "Polly";

使用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 />,
    },
  ];

试试这个:

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

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

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

结果:1 | "some_string"

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