Why I like JavaScript Optional Chaining

The original link: dmitripavlutin.com/javascript-…

New features of JavaScript are rapidly changing our code. The new features that have had the biggest impact on my code since ES2015 include deconstruction, arrow functions, class classes, and the module system. In August 2019, a new feature called optional Chaining, which literally translates to optional chaining, arrived at Stage 3, and this new feature will be a great improvement. Optional chain calls change the way deep properties in an object are accessed. Let’s take a look at how optional chain calls can make your code simpler by removing template conditions and variables when accessing properties that may not exist in depth.

Problem 1.

Due to the dynamic nature of JavaScript, an object can have a variety of nested destructions. In general, you deal with the following types of objects:

  • Get remote JSON data
  • Using configuration Objects
  • Objects with optional properties

When we give an object enough flexibility to have a variety of nested structures, we pay the price of increasing the complexity of accessing object properties. BigObject objects have different versions of attribute deconstruction at runtime:

// a version of bigObject const bigObject = {//... prop1: { //... prop2: { //... value:"some value"}}}; // Another version of bigObject const bigObject = {//... prop1: { // Nothing here } }Copy the code

In this case, the previous approach is that you have to manually determine whether the attribute exists:

if(bigObject && bigObject.prop1 ! = null && bigObject.prop1.prop2 ! = null) {let result = bigObject.prop1.prop2.value;
}
Copy the code

It is recommended not to write this type of template code. Let’s look at how to solve this problem by using optional chain calls to reduce template conditions.

2. Easy access to deep attributes

Let’s construct an object that represents the movie’s message. This object contains a required title property, as well as optional director and Actors properties. The movieSmall object contains only the required title property, and the movieFull object contains both required and optional properties:

const movieSmall = {
    title: "Heat"
};

const movieFull = {
    title: "Blade Runner",
    director: {
        name: "Ridley Scott",
    },
    actors: [{ name: "Harrison Ford" }, { name: "Rutger Hauer"}}]Copy the code

Write a function to obtain the director property value. Note that the director property may not exist:

function getDirector (movie) {
    if(movie.director ! = null) {returnmovie.director.name; } } get Director(movieSmall); //=> undefined get Director(movieFull); / / = >'Ridley Scott'
Copy the code

if (movie.director ! = null) determines whether the director attribute is defined. This is done in case JavaScript throws a TypeError when accessing a child of an undefined attribute in an object: Cannot read property ‘name’ of undefined, such as the name property of the director property that accesses movieSmall. The following is an example of using the JS optional chain call to remove the judgment on the existence of the movie.director property. This way getDirector() looks much shorter:

function getDirector(movie) {
    returnmovie.director? .name; } get Director(movieSmall); //=> undefined get Director(movieFull); / / = >'Ridley Scott'
Copy the code

In the movie. The director? Inside the.name expression, you can find?. : this is the optional chained call. In the movieSmall example, in case movieSmall’s director property does not exist, movie.director? .name gives the value of undefined. The optional chained call prevents JS from raising TypeError: Cannot read property ‘name’ of undefined. In contrast, in the movieFull example, the director property exists. movie.director? The.name gives the normal return value Ridley Scott. In a nutshell, the use of optional chained calls looks like this:

letname = movie.director? .name;Copy the code

Is equivalent to:

let name;
if(movie.director ! = null) { name = movie.director.name; }Copy the code

? . Simplifies the getDirector() function to just two lines of code. That’s why I like optional chain calls.

2.1 Array Elements

There are more applications for optional chain calls than just that. You can use multiple optional chain operators in a single expression. You can also use it to safely access array elements. Next we write a function that returns the name of the first actor in the list of actors in a movie. Inside a Movie Object, actors may be empty or even have no such property, so the previous approach required additional judgment conditions:

function getLeadingActor (movie) {
    if (movie.actors && movie.actors.length > 0) {
        returnmovie.actors[0].name; } } getLeadingActor(movieSmall); //=> undefined getLeadingActor(movieFull); / / = >'Harrison Ford'
Copy the code

If (movie.actors && movie.actors. Length > 0) is used to ensure that the movie contains the Actors attribute and that the attribute contains at least one element. With optional chain calls, this security judgment becomes easy to resolve:

function getLeadingActor (movie) {
        returnmovie.actors? [0]? .name; } getLeadingActor(movieSmall); //=> undefined getLeadingActor(movieFull); / / = >'Harrison Ford'
Copy the code

actors? . Make sure there are actors attributes. [0]? . Ensure that at least one element exists in the list.

3. Cooperate with invalid value merge operator

A new feature called the Nullish coalescing operator, which I translated myself as the invalid value coalescing operator, is designed to handle undefined and null, converting them to specified values. Expression variable?? DefaultValue assigns the value of defaultValue to variable if variable is undefined or null.

const noValue = undefined;
const value = 'Hello';

noValue ?? 'Nothing'; / / = >'Nothing'
value ?? 'Nothing'; / / = >'Hello'
Copy the code

The invalid value merge operator can improve optional chained calls by giving a default value when the call is deconstructed as undefined: For example, we can improve the getLeadingActor function to return an Unknown actor when the movie object has no Actors attribute:

function getLeadingActor (movie) {
        returnmovie.actors? [0]? .name ??'Unknown actor'; } getLeadingActor(movieSmall); //=> undefined getLeadingActor(movieFull); / / = >'Harrison Ford'
Copy the code

4. Three common uses of optional chain calls

There are three common ways to use optional chain calls. The first way is to use object? .property to access the static properties of the object:

const object = null; object? .property; //=> undefinedCopy the code

The second way is to use object? .[expression] to access the dynamic properties of an array or object:

const object = null;
const name = 'property'; object? .[name]; //=> undefinedCopy the code
const array = null; array? . [0]; //=> undefinedCopy the code

The third way is to use object? .([arg1, [arg2,…]]) to call the object’s methods:

const object = null; object? .method('Some value');    //=> undefined
Copy the code

Optional chained calls can be combined if necessary:

const value = object.maybeUndefinedProp? .maybeNull()? .[propName];Copy the code

5. Short circuit feature: terminate operation when undefined or NULL is encountered

The interesting thing about the optional chain-call operator is that for the expression leftHandSide? .righthandSide. If the value of leftHandSide is invalid, the rightHandSide operation will not be executed. This feature is called the short-circuit feature. Look at an example:

const noting = null;
letindex = 0; noting? .[index++]; //=> undefined; index; / / = > 0Copy the code

Noting a nonproductive value (undefined or null), the optional chain call operator skips the right side of the protein in case of a nonproductive value on the left side, so the end result is no increase in index value.

6. When to use the optional chain-call operator?

Resist the urge to use the optional chain-call operator to access any type of attribute, otherwise it will be abusive. The next section explains how to use it correctly.

6.1 Accessing properties that may have invalid values

? . Must be used when accessing properties that may have invalid values: maybeNullish? Prop. For the rest of the case, use the previous property access method:.prop or [propExpression]. Back to the movie object, in the expression movie.director? In.name, since director may be undefined, it is correct to use the optional chain-call operator when accessing the director property. Instead, use? When accessing the title property of the movie object. . Makes no sense, because the movie object is not an invalid value.

// Good
function logMovie (movie) { console.log(movie.director? .name); console.log(movie.title); } // Badfunction logMovie (Movie) {// director needs to use the optional chain call console.log(movie.director.name); // The title attribute does not need to use the optional chain to call console.log(movie? .title); }Copy the code

6.2 There are usually better options

In the following example, the function hasPadding() takes a style object as an argument that has an optional padding property. The padding property also contains optional attributes: Top, right, bottom, and left. In this function it is natural to use the optional chain call:

functionhasPadding ({ padding }) { const top = padding? .top ?? 0; const right = padding? .right ?? 0; const bottom = padding? .bottom ?? 0; const left = padding? .left ?? 0;returntop + left + right + bottom ! = = 0; } hasPadding({ color:'black'}); / / = >falsehasPadding({ padding: {left: 0} }); / / = >falsehasPadding({ padding: {right: 10} }); / / = >true
Copy the code

While the above example can correctly determine whether an element has padding, it’s still not a good idea to use an optional chaining call for every attribute. A better option is to use the expand operator to expand the padding object and give it a default value of 0:

functionhasPadding ({ padding }) { const p = { top: 0, right: 0, bottom: 0, left: 0, ... padding, };returnp.top + p.left + p.right + p.bottom ! = = 0; } hasPadding({ color:'black'}); / / = >falsehasPadding({ padding: {left: 0} }); / / = >falsehasPadding({ padding: {right: 10} }); / / = >true
Copy the code

In my opinion, using the expansion operator version of hasPadding() is much easier to understand.

7. Why do I like it?

What I like about the optional chain caller is that it is very convenient to use to access properties of deeply nested objects. Using it, you can avoid writing a string of validation criteria to verify every property on the access chain. When optional chain calls need to be used in conjunction with invalid value merge operators, there may be better options for easily handling default values. If you know of other handy examples of optional chain calls, please do so in the comments.