Flowtype:通用Id <t>类型,具有与传入的类型参数类似的约束

时间:2016-07-30 23:54:23

标签: javascript flowtype

我有一个通用Id<T: HasId>类型,无论传递给string的类型参数如何,它在结构上总是只是T。我希望将Id<T>类型作为T传递给不同类型的不同类型。

例如,我希望以下代码中的代码段const i :Id<Car> = p.id导致Flow错误:

declare interface HasId {
  id: string,
};

type Id<T: HasId> = string;

type Person = {
  id: Id<Person>,
  name: string,
};

type Car = {
  id: Id<Car>,
  make: string,
  model: string,
};

const p :Person = { id: '1234', name: 'me' }

const c :Car = p; // Causes a Flow error, good!

const c :Id<Car> = p.id; // I want this to cause a Flow error,
                         //   but currently it doesn't.

此外,如果这可以继续与联合类型很好地工作,那将是很好的:

type Vehicle =
  | Car
  | Motorcycle
;

const t :Car = { id: '5678', make: 'Toyota', model: 'Prius' };

const v :Id<Vehicle> = c.id; // Currently does not cause Flow
                             //   error; I want to keep it that way.

2 个答案:

答案 0 :(得分:1)

听起来你想要的是不透明的类型,Flow还没有。如果您有类型别名type MyString = string,则可以互换使用stringMyString。但是,如果您有一个opaque类型别名opaquetype MyNumber = number,则无法互换使用numberMyNumber

对不透明类型on this GitHub issue有更长的解释。

答案 1 :(得分:1)

非常好但又有点笨拙的解决方案

我做了一些实验,并根据this GitHub issue comment and the one following it中显示的系统找到了一种方法来完成我在问题中指定的内容。您可以使用一个类(具有泛型类型参数T),其中Flow会处理opaque类型,并使用强制转换为any以便在字符串和ID之间进行转换。

以下是一些启用此功能的实用程序:

// @flow

import { v4 } from 'node-uuid';

// Performs a "type-cast" from string to Id<T> as far as Flow is concerned,
//   but this is a no-op function
export function stringToId<T>(s :string):Id<T> {
  return (s :any);
}

// Use this when you want to treat the ID as a string without a Flow error
export function idToString(i :Id<*>):string {
  return (i :any);
}
export function createId<T>():Id<T> {
  return stringToId('1234');
}

// Even though all IDs are strings, this type distinguishes between IDs that
//   can point to different objects.
export class Id<T> {};

使用这些实用程序,以下代码(类似于我的问题中的原始代码)将导致Flow错误,就像我想要的那样。

// @flow
const p :Id<Person> = createId<Person>();
// note: Even though p is Id<Person> in Flow, its actual runtime type is string.

const c :Id<Car> = p; // this causes Flow errors. Yay!

// Also works without an explicit annotation for `p`:
const pp = createId<Person>();
const cc :Id<Car> = pp; // also causes Flow errors. Yay!

缺点

不幸的是,Flow输出非常冗长,因为像这样的类型错误会触发多个Flow错误。即使输出不理想,至少它的行为正确,因为发生错误会导致Flow报告错误。

另一个问题是,使用此解决方案时,必须在对象/地图键中显式地将ID从ID转换为字符串,其中Flow不期望Id<*>,如下例所示:

// @flow
type People = { [key :string]: Person };

const people :People = {};

const p :Id<Person> = createId<Person>();

people[p] = createPerson(); // causes Flow error
// Unfortunately you always have to do this:
people[idToString(p)] = createPerson(); // no Flow error

这些类型转换函数在运行时只是无操作,因为所有Flow类型都被删除了,因此如果你调用它们可能会有性能损失。请参阅我在此答案中链接的GitHub问题,以进行更多讨论。

注意:我正在使用Flow v0.30.0。