I recently learned about an exciting upcoming change to Javascript: Records and Tuples are on their way!
When I learned about this, I didn’t actually know anything about Records or Tuples, or about the process by which Javascript standards are changed. So I dug in a little deeper and am sharing what I learned.
From what I found, Records and Tuples will bring fun gains to performance and developer happiness. I’m most excited for the gains we’ll see in writing code with these types. With Records and Tuples, we’ll rely less on helper functions and third-party libraries like lodash to perform equality checks of our data. I’m also hoping, in practice, it means fewer “gotcha” moments and weird bugs in my code.
Let’s dive into the what and the why.
What I’ll cover in this post
- What are Records and Tuples?
- What’s so cool about them?
- What should I watch out for?
- How can I play around with them?
- When can I use them for real?
-
Definitions of key terms
- immutable
- primitives
- deep equality
What are Records and Tuples?
Records and Tuples are two new data types that will soon be a part of the Javascript language.
You can think of a Record as “Object-like”, but it will be an immutable object, so the items cannot be changed.
Tuples can be thought of as “Array-like”, and are again, immutable.
Both Records and Tuples are immutable only because they are formed only of primitives, and Records and Tuples are primitives themselves.
Most excitingly, Records and Tuples offer deep equality, something not available with Objects or Arrays, which usually requires an external library to assess.
What’s so cool about them?
Because Records and Tuples are primitives and immutable, Javascript can make certain assumptions about them, and we can do certain operations at faster speeds.
Deep equality is a great example of this. Rather than checking the equality with a for-loop or library, we can check it directly using Records and Tuples.
In the example below, the two identical Objects do not have equality, but the two Records (denoted by the syntax of a preceding #
) do.
const dogA = { name: "Rufus", owners: [ { name: "Sue" }, { name: "Joe" } ] };
const dogB = { name: "Rufus", owners: [ { name: "Sue" }, { name: "Joe" } ] };
const dogC = #{ name: "Rufus", owners: #[ #{ name: "Sue" }, #{ name: "Joe" } ] };
const dogD = #{ name: "Rufus", owners: #[ #{ name: "Sue" }, #{ name: "Joe" } ] };
console.log(a === b); // false
console.log(c === d); // true
For small objects, the gains are negligible, but for large and nested objects, the speed gains of this could be significant.
I’m also excited about the implications for React. By using these new types, we can avoid unnecessary re-renders. If a prop is an object, React does not know if its contents have changed, and may re-render unnecessarily. But by using a Record, React can perform a low-cost check for equality, and avoid re-rendering if the Records are equal.
The same would be true for internal state methods, like useEffect
. Sometimes useEffect
runs unnecessarily, and additional checks inside the useEffect
are required to determine if any operations should take place. These checks may prove less necessary in the future, as useEffect
will be better able to tell if the dependent props have changed and if the useEffect
should run.
If any of this isn’t clicking, have a read through the official tutorial from the creators of the proposal. It has more examples, and explains some key concepts like I have tried to do in the glossary. Their cookbook is great too.
What should I watch out for?
The strength of Records and Tuples creates a catch. To be able to quickly assess deep equality, these types are primitives, and can only be composed of primitives. This means that to use them, you will have to convert your relevant Object and Arrays to Records and Tuples, if you intend to use them in other Records and Tuples.
Additionally, after refactoring to these new types, certain methods can no longer be used. Any methods or operations that mutate the Array or Object will not be allowed on Tuples or Records. This means that methods like .push
or .pop
cannot be used. Any method that directly changes the data is off limits. You instead must create a new variable in which you can store this data, or reassign the variable. This could require significant refactoring if you use mutating methods frequently.
How can I play around with Records and Tuples?
It’s quite easy to check out these new types for yourself. You can use the official playground to test out some of the basic usages like the ones I’ve highlighted above. Worth noting that it doesn’t seem to work on Safari yet.
You can also add Records and Tuples to your babel configuration using this babel plugin. With this, you can use Records and Tuples in an existing project, to contextualize the performance gains it could mean for your project.
When can I use them for real?
It’s not clear yet when Records and Tuples will make it into the Javascript standard. The only thing we can say with confidence is that they will eventually. The proposal has made it to stage 2, meaning “the committee expects these features to be developed and eventually included in the standard.”
You can keep an eye on the proposal status here at the tc39 github list of proposals. Records and Tuples are currently in Stage 2 (of 4). So for now, we only have the playgrounds and babel plugins to keep us entertained. But “eventually”, we’ll be able to use Records and Tuples for real. I’m looking forward to it.
Definitions of Key Terms
Immutable
If data is immutable, it cannot be changed. You cannot edit, transform, or restructure the immutable data.
Objects and Arrays in Javascript are not immutable, which is why you can use properties like push
and pop
which change the structure of the Array. This can cause problems, shown nicely in this CSS Tricks post.
MDN’s article on Primitives shows a succinct example to illustrate the differences in immutability in Javascript.
Primitives
Primitives are essentially immutable types of data. I think the MDN page on primitives explains it best.
Deep Equality
Deep equality is an equality check against the individual items in an Object or Array.
Objects and Arrays do not have deep equality. Equality checks on Objects or Arrays are done by identity (e.g. that they occupy the same spot in your computer’s memory), meaning that two identical arrays, which are separately created, would not be equal. In the example below, these technically are two separate arrays, and therefore do not equal each other. However, the identity of a
and b
are the same, and therefore equal.
[1, 2, 3] === [1, 2, 3] // false
const a = [1, 2, 3]
console.log(a === a) // true
const b = a
console.log(a === b) // true
To determine if the items in two arrays are the same, devs often use helper functions from libraries like lodash or Ramda.
Records and Tuples will have deep equality, meaning that these types are compared on contents, rather than identity.
#[1, 2, 3] === #[1, 2, 3] // true
It’s worth noting that other languages, like Ruby, have deep equality built-in for array and object types. But Javascript likes to be different.