寫 GraphQL 不可不知道的 ECMAScript 的新語法提案 optional chaining 跟 nullish coalescing
說到 JavaScript 最常出現的錯誤,Reference Error: XXX is not defined,Cannot read property XXX of undefined,絕對在排行榜上佔很前面的位置。這樣的錯誤通常發生 api 出問題在 object 因為各種 edge case 的 error 造成缺少欄位,而程式並沒有做好相對應的檢查而直接對其做操作。
自從 destructor 跟 default value 在 ECMAScript 出現以後,我們可以用 default value 來做 defensive programing 去躲掉的問題,用 React 來舉例如下
const Item = ({ title = "defaultTitle", info = {}, coupons = [] }) => {
const { price } = info;
return (
<div>
<div>{title}</div>
<div>{price}</div>
{coupons.map((coupon, i) => (
<span key={i}>{coupon}</span>
))}
</div>
);
};
即使 title info coupons 是 undefined,頂多用 default value 取代,不會造成取值的 error
但是在引進 GraphQL 之後 GraphQL 有一個特性,如果某個欄位 resolve 不出來的時候會在這個欄位回傳 null 不是 undefined,
const query = gql`
{
product {
title
info {
price
}
coupons
}
}
`;
/*
{
product: {
title: 'title',
info: null,
coupons : null
}
}
*/
而 default value 能用來 fallback undefined 沒有辦法處理 null,可能就會造成意外的 crash。
Component 可能就要改寫如下
const Item = ({ title, info, coupons }) => {
const { price } = info || {};
return (
<div>
<div>{title || "defaultTitle"}</div>
<div>{price}</div>
{coupons && coupons.map((coupon, i) => <span key={i}>{coupon}</span>)}
</div>
);
};
如果遇到更深的 object 你可能會寫出這樣的程式碼
const value =
obj &&
obj.deep &&
obj.deep.deep &&
obj.deep.deep.deep &&
obj.deep.deep.deep.value;
用 && operator 取值還有其他的問題,JavaScript 裡頭 a && b 等價於 a ? b : a
a 可能是 0 空字串 等各種 falsy value 的可能,會造成各種意外的錯誤,譬如下面的例子,你可能就會在畫面上看到一個詭異的 0
const Products = ({ products }) => {
return (
<div>
{products.length &&
products.map((p, i) => <Product key={i} product={p} />)}
</div>
);
};
好吧我們還有 lodash.get
_.get(object, "deep.deep.depp.value");
但是 lodash 會增加不少 js 的 bundle size,你可能會花一番功夫去處理這個問題
ECMAScript 新語法 Optional Chaining #
https://github.com/tc39/proposal-optional-chaining
optional chaining 是從其他語言如 swift 借鏡過來處理取值語法,用下圖說明,?. 前面如果是 null undefined 的話就會直接 return undefined,同樣也可以用在 function 上
a?.b; // undefined if `a` is null/undefined, `a.b` otherwise.
a == null ? undefined : a.b;
a?.[x]; // undefined if `a` is null/undefined, `a[x]` otherwise.
a == null ? undefined : a[x];
a?.b(); // undefined if `a` is null/undefined
a == null ? undefined : a.b(); // throws a TypeError if `a.b` is not a function
// otherwise, evaluates to `a.b()`
a?.(); // undefined if `a` is null/undefined
a == null ? undefined : a(); // throws a TypeError if `a` is neither null/undefined, nor a function
// invokes the function `a` otherwise
讓你可以不必要再寫 object field checking code,就可以避免了對 undefined 跟 null 取值的錯誤
ECMAScript 新語法 nullish coalescing #
https://github.com/tc39/proposal-nullish-coalescing
|| operator 也是常用來處理 default value 的方法,但是我們要 fallback 成 default value 會讓任何 falsy 的值也都一起 fallback 進去包括 false 空字串,我們可能只是想要 undefined 或是 null 進行 fallback 而已
nullish coalescing 的語法很簡單就只有兩個問號 ?? ,可以來處理只要 fallback null 跟 undefined 的情境
const response = {
settings: {
nullValue: null,
height: 400,
animationDuration: 0,
headerText: "",
showSplashScreen: false,
},
};
const undefinedValue = response.settings.undefinedValue ?? "some other default"; // result: 'some other default'
const nullValue = response.settings.nullValue ?? "some other default"; // result: 'some other default'
const headerText = response.settings.headerText ?? "Hello, world!"; // result: ''
const animationDuration = response.settings.animationDuration ?? 300; // result: 0
const showSplashScreen = response.settings.showSplashScreen ?? true; // result: false
結論 #
目前 optional chaining 跟 nullish coalescing 已經進到 tc39 的 stage 3 release candidate,babel 也有 implement plugin 應該可以放心使用了,美中不足的缺點是 Typescript 現在尚未支援 (補充 Typescript 3.7 版本就開始支援這兩個語法),可能會破壞 VSCode 的 syntax highlight,然後 babel 其實也是幫你轉成 deep checking 的語法,如果沒有用中間變數暫存去對相同欄位重複使用 optional chaining 可能會造成程式碼些微的體積增加,如果引進 graphql 時為 null 的處理感到頭痛,不妨可以嚐試一下這兩個語法