2017-02-23 36 views
2

我想定义一个实体映射,使得键必须始终是某个指定类型的其他对象的ID。期望使用会是这样的:使用FlowType键入特定ID

export type Id<T> = { 
    id: string 
}; 

export type Foo = Id<Foo> & { 
    val: number 
}; 

// type Foo is now {id: Id<Foo>, val: number} 

export type Bar = Id<Bar> & { 
    val: number 
}; 

// type Bar is now {id: Id<Bar>, val: number} 

// FooMap should only be able to store objects of type foo, referenced by 
// IDs of type Id<Foo> 
export type FooMap = { 
    [key: Id<Foo>]: Foo 
}; 

const foo1: Foo = { id: "foo1", val: 1 }; 
const foo2: Foo = { id: "foo2", val: 2 }; 
const bar1: Bar = { id: "bar1", val: 3 }; 

// This would pass type checking: 
const fooMap: FooMap = { 
    [foo1.id]: foo1, 
    [foo2.id]: foo2 
}; 

// But this would fail type checking: 
const badMap: FooMap = { 
    [bar1.id]: foo1 
}; 

我使用的情况是,我想有通过ID的管理关系的规范化对象结构。但是由于这些ID都是相同的底层类型(例如,stringnumber),所以可能会意外编写类似上面的badMap示例的代码。我希望FlowType可以帮助防止这种情况,只要我以编程方式构造我的地图时,我只会引用正确的ID类型。

回答

1

从概念上讲,我认为你想要一个标记为单个案例的联合类型来表示你正在使用的各种ID--下面是F#中的一个例子:Single Case Discriminated Unions

如果我的理解正确,您希望创建地图Map<FooId, Foo>,其中fooMap[someFooId]只能包含Foo对象。但由于FooId可能与BarId类型相同,因此地图中可能有someBarId: Foo。您希望Flow对可能发生的情况进行类型检查。

不幸的是,我不相信现在有一种简单的方法可以在Flow中执行此操作,因为它缺少单个标记的工会。在F#你可以做这样的事情:

type FooId = FooId of string 
type BarId = BarId of string 

type Id<'T> = Id of 'T // you'd pass in FooId as the type 

type Foo = { 
    id: Id<FooId>; 
    value: string; 
} 

type Bar = { 
    id: Id<BarId>; 
    value: string; 
} 

let foo: Foo = { id = Id (FooId "12345"); value = "fooVal" } 
let bar: Bar = { id = Id (BarId "12345"); value = "barVal" } 
let fooMap = 
    Map.empty<Id<FooId>, Foo> 
    |> Map.add (Id (BarId "12345")) foo 

我能得到的最接近的东西就是这样的JavaScript。问题在于类型错误发生在FooId/BarId的定义处。我们得到:

string literal `FooId`. Expected string literal `BarId`, got `FooId` instead 
string literal `BarId`. Expected string literal `FooId`, got `BarId` instead 

中的JavaScript:

type FooId = "FooId"; 
type BarId = "BarId"; 
type Id<T> = { 
    id: string; 
    type: T 
}; 

type Foo = { 
    id: Id<FooId>, 
    val: any 
}; 

type Bar = { 
    id: Id<BarId>, // get type error here 
    val: any 
}; 

type FooMap = { 
    [key: Id<FooId>]: Foo // get type error here 
}; 

const fooIdBuilder = (id): Id<FooId> => ({ id, type: "FooId" }); 
const barIdBuilder = (id): Id<BarId> => ({ id, type: "BarId" }); 

const foo1: Foo = { 
    id: fooIdBuilder("12345"), 
    val: "fooval" 
}; 

const foo2: Foo = { 
    id: fooIdBuilder("23456"), 
    val: "fooval" 
}; 

const bar1: Bar = { 
    id: barIdBuilder("23456"), 
    val: "barval" 
}; 

const fooMap: FooMap = {}; 
fooMap[foo1.id] = foo1; 
fooMap[bar1.id] = foo2; 

您可以复制并粘贴到这个Try Flow

+0

是的,你的答案是只是我在我的思想结束了在那里。我同意我的原始用例目前在Flow中似乎不可行,并且进一步同意在声明站点而不是使用站点的错误报告具有误导性。 :)感谢您花时间将示例代码放在一起! – Palpatim