如何在给定键和值的情况下确保对象类型的类型安全?

时间:2019-07-10 16:54:11

标签: javascript flowtype

给出此类型:

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

但是Flow complains无法将string写入bar,也不能将number写入foo。 IOW,$ElementType<>定义似乎无法正确找到与传递的key相对应的类型。

1 个答案:

答案 0 :(得分:2)

$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';
};

try flow

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;

[try flow]

获得所需行为的一种可能方法是使用泛型。也就是说,看起来像ElementType doesn't yet work as expected with 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 Playground]