Announcing 5 new Flow tuple type features
Tuples are a lighter weight alternative to objects when you want to group data, by using positions rather than property names to distinguish elements. However, tuple types have lacked many useful features object types provide - until now!
- Labeled tuple elements:
type T = [foo: number, bar: string] - Variance (read-only/write-only) annotations for tuple elements:
type T = [+foo: number, -bar: string] - Optional tuple elements:
type T = [foo?: number, bar?: string] - Tuple spread
type T = [number, string, ...OtherTuple] - Refinement on tuple length
Read the full docs.
Labeled tuple elements
You can now add a label to tuple elements. Labels do not affect the type of the tuple element, but are useful in self-documenting the purpose of the tuple elements, especially when multiple elements have the same type.
type Range = [x: number, y: number];Labels are also necessary to add variance annotations or optionality modifiers to elements.
Variance annotations on tuple elements and read-only tuples
You can add variance annotations (to denote read-only/write-only) on labeled tuple elements, just like on object properties:
type T = [+foo: number, -bar: string];This allows you to mark elements as read-only or write-only. For example:
function f(readOnlyTuple: [+foo: number, +bar: string]) {
const n: number = readOnlyTuple[0]; // OK to read
readOnlyTuple[1] = 1; // ERROR! Cannot write
}You can also use the $ReadOnly on tuple types as a shorthand for marking each property as read-only:
type T = $ReadOnly<[number, string]>; // Same as `[+a: number, +b: string]`Optional tuple elements
You can mark tuple elements as optional with ? after an element’s label. This allows you to omit the optional elements. Optional elements must be at the end of the tuple type, after all required elements.
type T = [foo: number, bar?: string];
([1, "s"]: T); // OK: has all elements
([1]: T); // OK: skipping optional elementYou cannot write undefined to the optional element - add | void to the element type if you want to do so:
type T = [foo?: number, bar?: number | void];
declare const x: T;
x[0] = undefined; // ERROR
([undefined]: T); // ERROR
x[1] = undefined; // OK: we've added `| void` to the element typeYou can also use the Partial and Required utility types to make all elements optional or required respectively:
type AllRequired = [number, string];
([]: Partial<AllRequired>); // OK: like `[a?: number, b?: string]` now
type AllOptional = [a?: number, b?: string];
([]: Required<AllOptional>); // ERROR: like `[a: number, b: string]` nowTuples with optional elements have an arity (length) that is a range rather than a single number. For example, [number, b?: string] has an length of 1-2.
Tuple type spread
You can spread a tuple type into another tuple type to make a longer tuple type:
type A = [number, string];
type T = [...A, boolean]; // Same as `[number, string, boolean]`
([1, "s", true]: T); // OKTuple spreads preserve variance and optionality. You cannot spread arrays into tuples, only other tuples.
Refinement on length
You can refine a union of tuples by their length:
type Union = [number, string] | [boolean];
function f(x: Union) {
if (x.length === 2) {
// `x` is `[number, string]`
const n: number = x[0]; // OK
const s: string = x[1]; // OK
} else {
// `x` is `[boolean]`
const b: boolean = x[0];
}
}Technically this is not a newly added feature, but one not previously announced.
Adoption
To use labeled tuple elements (including optional elements and variance annotations on elements) and tuple spread elements, you need to upgrade your infrastructure so that it supports the syntax:
flowandflow-parser: 0.212.0prettier: 3babelwithbabel-plugin-syntax-hermes-parser. See our Babel guide for setup instructions.eslintwithhermes-eslint. See our ESLint guide for setup instructions.
Bonus: declare const & declare let
Flow now supports declare const and declare let (in addition to the existing declare var). These allow you to declare a variable with a type, but without an implementation. They are useful for writing library definitions or creating reproductions of issues in Try Flow, while using the modern const and let instead of the legacy var. Both declare const and declare let are block-scoped, like their non-declare equivalents.
if (cond) {
declare const foo: number;
}
foo; // ERROR: cannot resolve `foo` (as it's block-scoped)
