Flowtype:泛型 Id<T> 类型,具有与传入的类型参数类似的约束
Flowtype: generic Id<T> type with similar constraints to the type argument passed in
我有一个通用的 Id<T: HasId>
类型,它在结构上始终只是一个 string
,无论作为 T
传入的类型参数如何。我希望 Id<T>
类型与作为 T
传递的不同类型表现得像不同类型。
例如,我希望以下代码中的代码段 const i :Id<Car> = p.id
导致流错误:
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.
听起来你想要的是不透明类型,而 Flow 还没有。如果您有类型别名 type MyString = string
,则可以互换使用 string
和 MyString
。但是,如果您有一个不透明的类型别名 opaquetype MyNumber = number
,则不能互换使用 number
和 MyNumber
。
不透明类型有更长的解释 on this GitHub issue。
相当不错但有点老套的解决方案
我做了一些实验,并找到了一种方法来根据 this GitHub issue comment and the one following it 中所示的系统来完成我在问题中指定的内容。您可以使用 Flow 处理不透明类型的 class(带有通用类型参数 T
),并使用转换为 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 报告错误。
另一个问题是,使用此解决方案,在 Flow 不期望 Id<*>
的 object/map 键等情况下,您必须显式地将 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。
我有一个通用的 Id<T: HasId>
类型,它在结构上始终只是一个 string
,无论作为 T
传入的类型参数如何。我希望 Id<T>
类型与作为 T
传递的不同类型表现得像不同类型。
例如,我希望以下代码中的代码段 const i :Id<Car> = p.id
导致流错误:
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.
听起来你想要的是不透明类型,而 Flow 还没有。如果您有类型别名 type MyString = string
,则可以互换使用 string
和 MyString
。但是,如果您有一个不透明的类型别名 opaquetype MyNumber = number
,则不能互换使用 number
和 MyNumber
。
不透明类型有更长的解释 on this GitHub issue。
相当不错但有点老套的解决方案
我做了一些实验,并找到了一种方法来根据 this GitHub issue comment and the one following it 中所示的系统来完成我在问题中指定的内容。您可以使用 Flow 处理不透明类型的 class(带有通用类型参数 T
),并使用转换为 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 报告错误。
另一个问题是,使用此解决方案,在 Flow 不期望 Id<*>
的 object/map 键等情况下,您必须显式地将 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。