Как обеспечить безопасность типов по типу объекта с учетом ключа и значения? - PullRequest
0 голосов
/ 10 июля 2019

Учитывая этот тип:

type Obj = $ReadOnly<{|
  foo: string,
  bar: number
|}>;

С функцией обновления, которая применяет частичное обновление:

const update = (u: $Shape<Obj>) => (obj: Obj) => ({...obj, ...u});

Как я могу написать функцию, которая принимает ключ и значение и применяет их к Obj безопасным для типов способом? Я попробовал:

const setKeyValue = (key: $Keys<Obj>) =>
  (value: $ElementType<Obj, typeof key>) =>
    update({[key]: value});

Но Поток жалуется , что string нельзя записать в bar, а number - foo. IOW, определение $ElementType<>, похоже, неправильно находит тип, соответствующий переданному key.

1 Ответ

2 голосов
/ 10 июля 2019

Результат $ElementType<Obj, typeof key> равен number & string. Это связано с тем, что Keys<Obj> возвращает объединение всех возможных значений ключа, а ElementType, который возвращает пересечение всех возможных значений.

type Obj = $ReadOnly<{|  foo: string, bar: number |}>;

{
  let k: 'foo' = 'foo';
  // Works because Flow knows `k` is exactly `foo` and its value can only be a string.
  let v: $ElementType<Obj, typeof k> = 'wow';
};

{
  let k: $Keys<Obj> = 'foo';
  // ERROR: Flow can't know `typeof k` is exactly `foo` and not also `bar`.
  let v: $ElementType<Obj, typeof k> = 'wow';
};

пробный поток

number & string не очень полезен:

//  Cannot assign `'wow'` to `a` because string [1] is incompatible with number [2].
let a: number & string = 'wow';
// Cannot assign `10` to `b` because number [1] is incompatible with string [2].
let b: number & string = 10;

[попытка выполнения]

Один из возможных способов получить желаемое поведение - с помощью дженериков. Тем не менее, похоже, что ElementType еще не работает должным образом с Generics

Пока вышеприведенное не будет выполнено, я думаю, что это разумный случай для использования any. Он относительно изолирован и не вытекает за пределы вашей функции.

{
  const setKeyValue = (key: $Keys<Obj>) =>
    (value: $ElementType<Obj, typeof key>) =>
      update(({[key]: value}: any));
};

Как упомянуто в билете, есть способы сделать это в безопасном месте, которые Flow поймет, но они более многословны.

{
  const setKeyValue = (key: $Keys<Obj>) => {
    return {
      foo: (value) => update(({[key]: value})),
      bar: (value) => update(({[key]: value})),
    }[key]; // using an object here is helpful in case you forget a field or if one is added later, Flow will error.
  };
};

Когда Flow не уверен в чем-то, он обычно выдает ошибку. TypeScript обычно будет молчать, если не уверен.

т. Е. Следующая ошибка не возникает в TypeScript, но должна.

interface Obj { foo: string; bar: number; }

const update = (u: Partial<Obj>) => (obj: Obj): Obj => ({...obj, ...u});
const setKeyValue = <TKey extends keyof Obj>(key: TKey) => 
    (value: Obj[TKey]) => update({[key]: {'crazy': 'crazy stuff that should error'}})


const w: Obj = {foo: 'hello', bar: 5};
setKeyValue('foo')('world')(w);

[Игровая площадка TypeScript]

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...