Table of Contents
There are cases in Flow where you'll want to create a generic function or class, but want to limit what can be passed as a type argument.
Where Generic Constraints are Useful
Let's say you have a class that wraps a Map, but makes a shallow copy before inserting:
class ShallowMap<T> {
map: Map<string, T>;
constructor() {
this.map = new Map();
}
get(key: string) {
return this.map.get(key);
}
set(key: string, value: T) {
// Make a shallow copy. Flow will give an error on this line.
const copy = {...value};
this.map.set(key, copy);
}
}
Flow won't let you do this, since, while objects can be spread, other types can't:
// This works.
const spreadEmptyObject = {...Object.create(null)};
const spreadObjectWithProperty = {...{id: 2}};
// This doesn't.
const spreadNumber = {...0};
const spreadString = {...''};
And there's nothing saying T has to be an object.
Adding Something that Says T Has to Be An Object
It's pretty simple; just add a colon and the constraining type after the generic type declaration:
// `T` has to be an object.
class ShallowMap<T: Object> {
map: Map<string, T>;
constructor() {
this.map = new Map();
}
get(key: string) {
return this.map.get(key);
}
set(key: string, value: T) {
// Flow is okay with this line now.
const copy = {...value};
this.map.set(key, copy);
}
}
More Specific Constraints
Constraints can be more specific, allowing more complex interactions with generic types, since they fit some criteria. For example, a generic map that uses a property of T to determine the key:
type Keyable = {
key: string,
};
// `T` has a string property called `key`.
class AutoKeyMap<T: {key: string}> {
map: Map<string, T>;
constructor() {
this.map = new Map();
}
get(key: string) {
return this.map.get(key);
}
set(value: T) {
// Since `T` has a string property `key`, we can access it.
const key = value.key;
this.map.set(key, value);
}
}