Uncaught TypeError: Cannot read property ‘name’ of undefined TypeError: Cannot read property ‘name’ of undefined TypeError: Cannot read property ‘name’ of undefined TypeError: Cannot read property ‘name’ of undefined In normal shorthand, we get the values of the nested properties directly, and if one of the middle or even initial values is undefined, the console will report an error and the rendering template will fail. So is there a way to prevent this from happening?

The stupidest step by step

In order to avoid the above situation, some partners will directly use &&, which is the following form of code to avoid errors

if (list && list[index] && list[index].content && list[index].content.length > 0) {
    // Other code
}
Copy the code

The deeper you nest, the more code you need.

And this process, the program is indeed will not report the error, but always feel a little trouble, if I because of bad memory (in fact, is lazy) write a part of the words, sometimes not with no write the same?

Presents? The operator

Presents? The. Operator is a bit of an operation, as long as we add? To the template. The. Operator can avoid template rendering errors caused by null or undefined.

The safe navigation operator ( ? . ) and null property paths

The Angular safe navigation operator (? .). is a fluent and convenient way to guard against null and undefined values in property paths. Here it is, protecting against a view render failure if the currentHero is null.

Translation:

The safe navigation operator (? .). Angular’s safe navigation operator (? .). CurrentHero is a smooth and convenient way to prevent null and undefined values in the property path and, if currentHero is null, to protect the view from its rendering failure.

When you use? . Operator, The current hero’s name is {{currentHero? .name}}, even if the currentHero value is null or undefined, the currentHero part is displayed as blank, and no error is reported.

The current hero’s name is {{currenthero. name}}, js will raise reference Error, TypeError: Cannot read property ‘name’ of null in [null].

There are many other strengths of Angular that I won’t list here. If it weren’t for the small size of my company’s projects, I would have liked to try angular for project development.

And I have to say, this is a pretty elegant way of doing it, not too different from the usual way of writing it, just one more, right? We can solve the problem. In Vue and React, we won’t be able to use it for now, so let’s start with functions.

Simple and crude reduce

Let’s look at the initial requirement, get some value in the nested data, and since it’s nested data, we can expand it layer by layer, so we can set undefined or null as an empty object during the expansion, This way, if undefined or null, the value after fetching will not raise Error TypeError, but will only fetch a null value.

Let’s start with a brief introduction to the reduce function for arrays,

Array.prototype.reduce = function{// Accumulator, (accumulator, currentValue, array) {// Accumulator, The return value of the last call to the function. The first time for the initialValue | | arr [0] / / the value of the currentValue array function is processing. The first time the initialValue | | arr [1] / / currentIndex array function is dealing with the index of the array / / / / array function call initValue although not preach to also go, but if the type of the array and the return type is different, I recommend passing a default value to avoid errorsCopy the code

The form of the function is roughly defined as:

// @params data // @params data // @params data // @params data // @params data // @params datafunction getSafeReduce(data: any, keys: string | number): any
Copy the code

Preliminary stage

To prevent the passed value from being null, be sure to evaluate the data and keys values.

First determine whether data has a value. If not, give it a default value.

Second, check whether keys is a valid parameter, mainly check whether it is a number or a string. If not, return an empty object. We then split keys, which is a string method, and convert keys to a string in order to avoid an error when typing a number.

  1. Construct a String object directly from StringString(keys)
  2. Take advantage of the property of the + operator'' + keys
  3. Use ES6 template strings`${keys}`

Use whichever you like, but I personally prefer the latter two.

function isExist(data) {
    returndata ! == undefined && data ! == null; }function isNothing(data) {
    return! isExist(data); }function isVaild(data) {
    return typeof data === 'string' || typeof data === 'number';
}

function getSafeReduce(data, keys) {
  if (isNothing(data)) {
    return defaultValue;
  }
  return isVaild(keys) && `${keys}`.split(",").reduce((item, key,) => {
    returnitem[key] || {} }, data) || {}; } // test getSafeReduce(null,'s,1')
Copy the code

The advanced stage

This code actually does a little bit of work, but if undefined or null is present in a section, it’s going to return an empty object, and we want to return a null, or an empty string, or something like that, and it’s going to be custom, so we need to change the function definition a little bit, Add a parameter to set the return default value.

@params defaultValue: any @params defaultValue: any @params defaultValue: any @params defaultValue: anyfunction getSafeReduce(data: any, keys: string | number, defaultValue: any): any
Copy the code

The third parameter of the array reduce method is the current index index of the array, and the fourth parameter is the array itself arr. We can judge whether it is the final target value by index === arr. Length-1. If the target value does not exist, set it to the default value.

function getSafeReduce(data, keys, defaultValue,) {
  if (isNothing(data)) {
    return defaultValue;
  }
  return isVaild(keys) && `${keys}`.split(",").reduce((item, key, i, arr) => {
    let result = item[key.trim()];
    if (isNothing(result)) {
      result = (i === arr.length - 1) ? defaultValue : {};
    }
    return result;
  }, data);
}
Copy the code

Simplify the stage

The if statement of the previous step can be further simplified, i.e. the reduce function can be changed to:

let result = item[key.trim()];
result = isExist(result) ? result : ((i === arr.length - 1) ? defaultValue : {});
return result;
Copy the code

The code above can be simplified even further, and it becomes:

function getSafeReduce(data, keys, defaultValue,) {
  if (isNothing(data)) {
    return defaultValue;
  }
  return isVaild(keys) && 
  `${keys}`.split(",").reduce((item, key, i, arr) = > (isExist(item[key.trim()]) ? item[key.trim()] : (i === arr.length - 1)? defaultValue : {}), data); }Copy the code

conclusion

Simplified code is not necessarily the best, but the penultimate code is a relatively balanced code in terms of readability and maintainability.

The complete code without debugging is as follows:

function isExist(data) {
    returndata ! == undefined && data ! == null; }function isNothing(data) {
    return! isExist(data); }function isVaild(data) {
    return typeof data === 'string' || typeof data === 'number';
}

function getSafeReduce(data, keys, defaultValue,) {
  if (isNothing(data)) {
    return defaultValue;
  }
  data = isExist(data) ? data : {};
  return isVaild(keys) && 
  `${keys}`.split(",").reduce((item, key, i, arr) => {
      let result = item[key.trim()];
      result = isExist(result) ? result : ((i === arr.length - 1) ? defaultValue : {});
      return result;
  }, data);
}
Copy the code

For production, the above code is fine, but for native development debugging, we need to add a debugging option to get the template to render properly and to know what went wrong.

The complete code with debugging is as follows:

function isExist(data) {
    returndata ! == undefined && data ! == null; }function isNothing(data) {
    return! isExist(data); }function isVaild(data) {
    return typeof data === 'string' || typeof data === 'number';
}

function getSafeReduce(data, keys, defaultValue, debug) {
  if (isNothing(data)) {
    debug && (console.error('The first argument passed in is null', data));
    return defaultValue;
  }
  data = isExist(data) ? data : {};
  return isVaild(keys) && 
        `${keys}`.split(",").reduce((item, key, i, arr) => {
           let result = item[key.trim()];
           if(isNothing(result)) { result = (i === arr.length - 1) ? defaultValue : {}; Debug && (console.error(item, 'Current data does not exist key:${key}`));
           }
           return result;
        }, data);
}
Copy the code

link

Github: getSafe (debugged version)

NPM install:

npm i -S mpd-util 
import { getSafe } from 'mpd-util'
console.log(getSafe({}, 's, 1', 1))
Copy the code