자바스크립트에서 선택적(null-safe)

어제 this StackOverflow 질문에 부딪혔고 자바스크립트에서 null/undefined 처리에 대해 생각하게 되었습니다.

짧은 배경


undefined는 무엇입니까? undefined는 선언된 변수, 존재하지 않는 속성 또는 함수 인수에만 제공되는 기본 값입니다.
null는 무엇입니까? null는 값의 부재를 나타내는 또 다른 원시 값입니다.

따라서 다음을 수행하면 어떻게됩니까?


let obj;

console.log(obj.someProp);



다음 오류가 발생합니다.

TypeError: Cannot read property 'someProp' of undefined


null에서도 마찬가지입니다.

널 검사



그렇다면 어떻게 피할 수 있습니까? 글쎄요, 우리는 자바스크립트에서 short-circuit 평가를 가지고 있어서 운이 좋았습니다. 즉, 이전 TypeError을 피하기 위해 다음을 작성할 수 있습니다.


let obj;

console.log(obj && obj.someProp); // Prints undefined



그러나 obj.prop1.prop2.prop3와 같이 더 깊이 들어가려면 어떻게 해야 합니까? 우리는 다음과 같은 많은 검사를 끝낼 것입니다:


console.log( obj && obj.prop1 && obj.prop1.prop2 && obj.prop1.prop2.prop3 );



역겹지 않나요?

그리고 해당 체인에 undefined 또는 null가 있는 경우 기본값을 인쇄하려면 어떻게 해야 할까요? 그러면 더 큰 표현이 됩니다.


const evaluation = obj && obj.prop1 && obj.prop1.prop2 && obj.prop1.prop2.prop3;

console.log( evaluation != null ? evaluation : "SomeDefaultValue" );



다른 언어는 어떻게 하나요?



이 문제는 자바스크립트에만 있는 것이 아니라 대부분의 프로그래밍 언어에 존재하므로 일부 언어에서 null 검사를 수행하는 방법을 살펴보겠습니다.

자바



자바에는 Optional API가 있습니다.


SomeClass object;

Optional.ofNullable(object)
    .map(obj -> obj.prop1)
    .map(obj -> obj.prop2)
    .map(obj -> obj.prop3)
    .orElse("SomeDefaultValue");



코틀린



kotlin(또 다른 JVM 언어)에는 elvis( ?: ) 및 safe-call( ?. ) 연산자가 있습니다.


val object: SomeClass?

object?.prop1?.prop2?.prop3 ?: "SomeDefaultValue";



씨#



마지막으로 C#에는 null 조건( ?. ) 및 null 병합( ?? ) 연산자도 있습니다.


SomeClass object;

object?.prop1?.prop2?.prop3 ?? "SomeDefaultValue";



JS 옵션



그래서 이걸 다 보고 나서 자바스크립트로 그렇게 많이 쓰지 않을 수 있는 방법이 없을까 하는 생각이 들어서 regex로 실험을 시작하여 객체 속성에 안전하게 액세스할 수 있는 함수를 작성하기 시작했습니다.


function optionalAccess(obj, path, def) {
  const propNames = path.replace(/\]|\)/, "").split(/\.|\[|\(/);

  return propNames.reduce((acc, prop) => acc[prop] || def, obj);
}

const obj = {
  items: [{ hello: "Hello" }]
};

console.log(optionalAccess(obj, "items[0].hello", "def")); // Prints Hello
console.log(optionalAccess(obj, "items[0].he", "def")); // Prints def



그 후 동일한 서명을 가진 lodash._get 에 대해 찾았습니다.
_.get(object, path, [defaultValue])
하지만 솔직히 말해서 저는 문자열 경로를 그다지 좋아하지 않기 때문에 문자열 경로를 피할 수 있는 방법을 찾기 시작한 다음 프록시를 사용하는 솔루션을 찾았습니다.


// Here is where the magic happens
function optional(obj, evalFunc, def) {

  // Our proxy handler
  const handler = {
    // Intercept all property access
    get: function(target, prop, receiver) {
      const res = Reflect.get(...arguments);

      // If our response is an object then wrap it in a proxy else just return
      return typeof res === "object" ? proxify(res) : res != null ? res : def;
    }
  };

  const proxify = target => {
    return new Proxy(target, handler);
  };

  // Call function with our proxified object
  return evalFunc(proxify(obj, handler));
}

const obj = {
  items: [{ hello: "Hello" }]
};

console.log(optional(obj, target => target.items[0].hello, "def")); // => Hello
console.log(optional(obj, target => target.items[0].hell, { a: 1 })); // => { a: 1 }



미래



현재 다음을 수행할 수 있는 TC39 proposal이 있습니다.


obj?.arrayProp?[0]?.someProp?.someFunc?.();



꽤 깔끔해 보이죠? 그러나 이 제안은 아직 1단계이므로 이것이 js라는 것을 확인하려면 시간이 걸릴 것입니다. 그럼에도 불구하고 해당 구문을 사용할 수 있는 babelplugin이 있습니다.

종악장


null는 한동안 존재했고 앞으로도 있을 것이며 프로그래밍에서 가장 싫어하는 개념 중 하나라고 확신합니다. 그러나 null-safety를 확보하는 방법이 있습니다. 여기에 두 센트를 게시했습니다. 어떻게 생각하는지 또는 다른 대안이 있으면 알려주세요.

p.s.: 여기 좀 예쁘다 gist

좋은 웹페이지 즐겨찾기