Nullish Short-Circuit Assignment in TypeScript 4.0 (beta)

On June 26th 2020, TypeScript 4.0 Beta was announced. The update brings a myriad of useful changes, but one such addition is something you'll quickly get in to the habit of using every day: Nullish Short-Circuit Assignment.

// A function that adds a post.
// If the user in the post doesn't exist, add a fake `0` user instead.
// It's valid to submit the fake `0` user too.
interface Post {
  content: string;
  user?: number;
}

// Old
function addPost(post: Post) {
  if (typeof post.user !== 'number') {
    post.user = 0;
  }
}

// New
function addPost(post: Post) {
  post.user ??= 0;
}

You've used a += b before to add to a number or extend a string without having to write a = a + b. What's frustrating, though, is having to check if a variable is nullish (null or undefined) before assigning it. TypeScript 4.0 adds three new assignment operators: &&=, ||=, and ??=. Using these alongside the Nullish Coalescing added in TypeScript 3.7, even more boilerplate code can be stripped down!

In the grand scheme it's a small improvement, but the new code is definitely shorter and more readable.

What about a simpler example? In the block above we wanted to be able to parse 0 as valid input, so we couldn't just do a falsey check. What if we just wanted to use nullish coalescing (??) without the short-circuit assignment (??=)? That's pretty succint too, right?

function parseConfig(config) {
  config.setting = config.setting ?? "set";
}

This doesn't look too bad. We have to repeat config.setting which could lead to some spelling mistakes and therefore unintended results, but it's still pretty readable and concise. What this is vulnerable to, however, is setters. If you're not familiar, a setter is a function that binds an object property to a function to be called when there's an attempt to set that property. For example, I could ensure that setting the username property on my object updates the profileUrl field appropriately:

const obj = {
  set username(name) {
    this.profileUrl = `https://example.com/@${name}`;
  }
}

obj.username = "FoobarBaggins";
// obj.profileUrl === "https://example.com/@FoobarBaggins"

Because a setter is just a function, it could potentially perform a computationally-expensive task without the developer realising. In our Nullish Coalescing examples, performing obj.foo = obj.foo ?? "bar"; will always call the foo setter, regardless of if obj.foo was nullish or not. If we use a short-circuit (obj.foo ??= "bar";), the setter will only be called if obj.foo was nullish. Nice!

obj.username = obj.username ?? "FoobarBaggins"; // calls setter every time
obj.username ??= "FoobarBaggins"; // only calls setter if obj.username nullish

I highly recommend you check out the wonderful TypeScript 4.0 Beta announcement for all the nitty-gritty of the new features. You can use the beta today with npm i -D typescript@beta, though check if it's fully released when you're reading this!

July 02, 2020