شرح Nullish Coalescing أو ?? في تايب سكريبت

تعتبر Nullish Coalescing أو ?? Operator من المزايا التي أضيفت لإصدار ES2020 من JavaScript، وقد كان TypeScript سباقا إلى دعمها منذ الإصدار 3.7.

فماهي إذن هذه الميزة ؟ وما الحالات التي قد نحتاجها فيها ؟

سنجيب عل هذه الأسئلة في هذه التدوينة الجديدة.

نريد التحقق من أن القيمة مُعَرَّفَة

في كثير من الأحيان نكون بحاجة للتأكد من أن قيمة معينة مُعَرَّفَة (Defined). وعندما أقول بأنها معرفة فإنني أعني بذلك أن تلك القيمة تخالف undefined وتخالف null.

هذا هو المقصود بالقيمة المعرفة في جافا سكريبت.

لذلك عند قراءة الشفرة المصدرية (Source code) لعدد من المشاريع ـ مثل Vue.js ـ نجدها تستعين بدالة مساعدة (Helper function) تسميها في العادة isDefined أو isDef وتستخدمها كلما أرادت التأكد من قيمة معينة تخالف القيمتين undefined و null بدون أن تضطر لكتابة الشرط if(value !== undefined && value !== null) في كل مرة.

مثال

لنقل بأننا نريد إنشاء دالة تقوم بإضافة وسم script إلى صفحتنا ديناميكيا وتقبل بعد الخيارات لإضافتها إلى الوسم على شكل attributes مثل defer و async.

function isDef(v) {
  return v !== undefined && v !== null;
}

function loadScriptDynamically(src, options = {}) {
  const script = document.createElement('script');
  script.src = src;
  script.defer = isDef(options.defer) ? options.defer : true;
  script.async = isDef(options.async) ? options.async : true;
  document.body.append(script);
}

loadScriptDynamically('my-script.js');
// <script src="my-script.js" defer async>

تلاحظون أنني استعنت بال isDef التي تكلمت عليها سابقا للتأكد من أن كل خيار يخالف undefined و null قبل أن نضيفه للوسم، وإلا فإنني أضيف قيمة بدئية قررتُ أنها true في هذا المثال. يعني أن كل وسم <script> يتم توليد دينامكيا بواسطة هذه الدالة سيكون defer و async إلا إذا طلبنا منه عكس ذلك كما في المثال أدناه:

loadScriptDynamically('my-script.js', {
  defer: false,
});
// <script src="my-script.js" async>

هنا يفيدنا Nullish Coalescing

بما أن غرضنا هو إعطاء قيمة بديلة في حال كان القيمة الأساسية غير مُعَرَّفة فإن الرمز ?? الذي يعبر عن ميزة Nullish Coalescing هو ما نحتاج هنا.

function loadScriptDynamically(src, options = {}) {
  const script = document.createElement('script');
  script.src = src;
  script.defer = options.defer ?? true;
  script.async = options.async ?? true;
  document.body.append(script);
}

loadScriptDynamically('my-script.js');
// <script src="my-script.js" defer async>

لاحظوا بأننا تخلصنا بفضل هذه الميزة من isDef وكذلك من Ternary condition.

الكود أصبح أبسط وأوضح.

مالفرق بين ?? و ||

قد يسأل أحدكم لماذا لا نستخدم || الموجود في لغة البرمجة جافا سكريبت منذ زمن طويل ؟

السؤال هذا قد يكون مبررا إذا علمنا أن الرمزين يتقاطعان في عدة مناحي، ولكن الفرق الأساسي أن || يستخدم للتحوط من ال Falsy values، بينما ?? يستخدم لأخذ الإحتياط من undefined و null فقط وليس كل Falsy values.

وعندما نتحدث عن Falsy values في جافا سكريبت فإنها لا تخرج عما يلي:

  • false
  • ""
  • 0
  • -0
  • NaN
  • null
  • undefined
  • 0n

نلاحظ إذن بأن undefined و null هما فعلا من بين ال Falsy values في جافا سكريبت، وبالتالي فعند استخدامهما مع كل من ?? و || سنحصل على نفس النتائج.

null ?? 'fallback'; // fallback
null || 'fallback'; // fallback

undefined ?? 'fallback'; // fallback
undefined || 'fallback'; // fallback

أما عند استخدام باقي ال Falsy values فسيظهر الفرق جليا بين الرمزين:

0 ?? 'fallback'; // 0
0 || 'fallback'; // fallback

false ?? 'fallback'; // false
false || 'fallback'; // fallback

'' ?? 'fallback'; // ""
'' || 'fallback'; // fallback

الرمز ?? يأخذ القيمة الأولى حتى ولو كانت Falsy مادامت تخالف undefined و null فقط.

هذا يعني أنه لو استعملنا || في مثالنا السابق فسنحصل على نتائج غير متوقعة عندما نمرر خيارات (options) من فصيلة ال Falsy values:

function loadScriptDynamically(src, options = {}) {
  const script = document.createElement('script');
  script.src = src;
  script.defer = options.defer || true;
  script.async = options.async || true;
  document.body.append(script);
}

loadScriptDynamically('my-script.js', {
  defer: false,
});
// <script src="my-script.js" defer async> !!!

لماذا أضيف defer إلى الوسم <script> رغم أننا قلنا للدالة عبر ال Options أننا لا نريده ؟

الجواب حتما واضح الآن! لأن الرمز || يلجأ للقيمة الإحتياطية (fallback) عندما يجد أن القيمة الأولى في اليسار هي واحدة من ال falsy values الثمانية التي استعرضناها أعلاه، وبالتالي أضاف defer هنا لأن ال fallback هي true.

لذلك رمز ?? الذي يعبر عن Nullish Coalescing هو ما نحتاجه في مثل هذه الحالات.

آمل أن الموضوع واضح كفاية من خلال المثال الذي أخدناه.

صندوق التعليقات متاح إذا كانت لديك أي ملاحظات أو أسئلة.

يمكنك دعمنا عبر منصة باتريون لكي نستطيع تسخير وقت أكبر في نشر وتجديد المحتوى.
الدعم سيساعدنا كذلك على تطوير ذلك المحتوى وتحسينه.

ادعمنا على باتريون