Web/HTML, CSS, JavaScript

Javascript - Optional chaining [ES2020] (Cannot read properties of undefined를 해결하기 위한 다양한 방법!)

JaeHoney 2022. 4. 26. 22:27
반응형

Nested object

Nested object를 설명하기 위해 아래와 같은 클래스를 만들었다.

class Dto {
    filter: Filter;
    page: Page;
    sort: Sort;
}

interface Filter {
    [key: string]: {
        operator: string;
        value: string;
    }
}

const dto = new Dto();

비즈니스 로직에서 dto.filter.search.value의 존재 유무에 따라 쿼리가 달라진다고 가정하자.

 

그럼 dto.filter.search.value의 존재 유무를 알 수 있는 방법은 뭘까?

if(dto.filter.search.value) {
    // ... 생략
} else {
    // ... 생략
}

이렇게 하면 처리가 될까? 안된다!

 

VM119:1 Uncaught TypeError: Cannot read properties of undefined (reading 'search')
    at <anonymous>:1:12

 

dto.filter.search.value가 없더라도 로직을 계속 진행시키고 싶은데 런타임 에러가 터진다. 왜냐하면 중간에 이미 dto.filter가 가진 search라는 필드가 undefined인데 속성을 달라고 하니까 예외가 터지는 것이다.

 

이런식으로 객체 안에서 또 프로퍼티를 가진 객체가 나오는 구조를 Nested object라고 부른다.

 

그렇다면 어떻게 해결할까?!

 

프로퍼티 꺼내는 방법!

고전적인 방법은 깊어지는 속성들을 하나씩 검사하는 방법이다.

if(dto && dto.filter && dto.filter.search && dto.filter.search.value) {
    // ... 생략
} else {
    // ... 생략
}

checkNested() 함수를 만들어서 조금 더 간편하게 사용하는 방법도 있다. checkNested()는 액세스하려는 프로퍼티가 null 또는 undefined면 즉시 반환한다.

function checkNested(obj) {
  var args = Array.prototype.slice.call(arguments, 1);

  for (var i = 0; i < args.length; i++) {
    if (!obj || !obj.hasOwnProperty(args[i])) {
      return false;
    }
    obj = obj[args[i]];
  }
  return true;
}

var test = {level1:{level2:{level3:'level3'}} };

checkNested(test, 'level1', 'level2', 'level3');
checkNested(test, 'level1', 'level2', 'foo');

ES6 버전부터는 ...args 문법과 reduce를 사용해서 한 줄짜리 간단한 함수로 만들 수도 있다.

function getNested(obj, ...args) {
    return args.reduce((obj, level) => obj && obj[level], obj)
}

const test = { level1:{ level2:{ level3:'level3'} } };
console.log(getNested(test, 'level1', 'level2', 'level3'))

 

그외에도 Lodash를 사용해서 _.get(object, 'filter[search].value'); 와 같은 문법으로 사용하는 방법 등 여러가지가 있지만, 굳이 다루지 않겠다.

 

옵셔널 체이닝(Optional chaining)

ES2020에 추가된 옵셔널 체이닝(Optional chaining)은 이러한 문제를 현재로써 가장 나은 방법으로 해결한다.

const result = dto?.filter?.search?.value;

 

선택적 연결 연산자인 '?.'을 사용하여 프로퍼티에 액세스하면 해당 프로퍼티가 null 또는 undefined면 즉시 반환한다.

 

즉, 처음에 설명한 예제의 경우 별도의 함수 수행 없이 아래와 같이 코드를 작성할 수 있다.

if(dto?.filter?.search?.value) {
    // ... 생략
else {
    // ... 생략
}

만약 Typescript를 사용하고 있다면 위의 문장이 에러가 날 텐데 ES6의 객체 리터럴을 사용해서 그렇다.

interface Filter {
    [key: string]: {
        operator: string;
        value: string;
    }
}

그때는 Object property . 대신 []을 사용해서 dto?.filter?.["search"]?.value; 로 작성하면 된다.

 

 

 


 

Reference

반응형