我有一个通用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.
答案 0 :(得分:1)
听起来你想要的是不透明的类型,Flow还没有。如果您有类型别名type MyString = string
,则可以互换使用string
和MyString
。但是,如果您有一个opaque类型别名opaquetype MyNumber = number
,则无法互换使用number
和MyNumber
。
对不透明类型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。