شرح مفصل لسلسلة الاختيارية `?.` في JavaScript: المفهوم، آلية العمل، وحالات الاستخدام الأمثل

دقائق القراءة: 8

في عالم تطوير الويب سريع التطور، تُعد لغة JavaScript حجر الزاوية لبناء تجارب مستخدم ديناميكية وتفاعلية. ومع تعقيد التطبيقات الحديثة، يزداد التعامل مع الكائنات المتداخلة (Nested Objects) التي قد تحتوي على خصائص غير متوفرة أو بقيم null أو undefined. تاريخياً، كان هذا التحدي يتطلب كتابة تعليمات برمجية مطولة للتحقق من وجود كل خاصية في السلسلة قبل محاولة الوصول إليها، مما يؤدي إلى كود أقل وضوحاً وأكثر عرضة للأخطاء.

هنا يأتي دور ميزة سلسلة الاختيارية (Optional Chaining)، التي قُدمت في إصدار ECMAScript 2020 (ES2020). تُغير هذه الميزة طريقة وصولنا إلى الخصائص من الكائنات المتداخلة بعمق، مقدمةً حلاً أنيقاً وفعالاً لمشكلة التحقق المتكرر من القيم الفارغة. تُمثل سلسلة الاختيارية بالرمز ?.، وهي تُبسط الكود وتجعله أكثر قابلية للقراءة والصيانة.

ما هي سلسلة الاختيارية ?. في JavaScript؟

تُعد سلسلة الاختيارية ?. إضافة قوية للغة JavaScript، تسمح للمطورين بقراءة قيمة خاصية تقع في عمق سلسلة من الكائنات المتصلة دون الحاجة إلى التحقق الصريح من صحة كل مرجع في السلسلة. فبدلاً من إطلاق خطأ (TypeError) إذا كان المرجع null أو undefined، تتوقف عملية التقييم وتعيد القيمة undefined.

قبل ظهور هذه الميزة، كان المطورون يواجهون سيناريوهات شائعة حيث قد تكون البيانات المستلمة من واجهة برمجة تطبيقات (API) أو من مصادر أخرى غير مكتملة. للتعامل مع ذلك، كانوا يضطرون إلى كتابة سلاسل طويلة من عمليات التحقق المنطقية && أو استخدام مكتبات مساعدة مثل Lodash.

متى تستخدم سلسلة الاختيارية ?.؟ حالات الاستخدام الشائعة

تُبرز سلسلة الاختيارية ?. قيمتها في العديد من السيناريوهات التي تتطلب التعامل مع بيانات قد تكون غير مكتملة أو غير متوفرة. إليك أبرز حالات الاستخدام:

1. الوصول إلى خصائص الكائنات التي قد تكون null أو undefined

تخيل أنك تتعامل مع كائن يمثل بيانات مستخدم، وهذا الكائن قد لا يحتوي دائماً على جميع الخصائص المتوقعة. على سبيل المثال، قد لا يكون لدى المستخدم عنوان شحن (shipping address) مسجل:

const user = {
  id: 1,
  name: "أحمد",
  // shippingAddress: { street: "شارع الأمل", city: "الرياض" }
};

// بدون سلسلة اختيارية، قد يؤدي هذا إلى خطأ TypeError إذا كانت shippingAddress غير موجودة
// const city = user.shippingAddress.city; 

// مع سلسلة اختيارية
const city = user?.shippingAddress?.city;
console.log(city); // undefined (بدلاً من خطأ)

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

2. استدعاء الدوال التي قد لا تكون موجودة

يمكن استخدام سلسلة الاختيارية لاستدعاء دالة قد تكون غير معرفة أو غير موجودة كخاصية في كائن. فبدلاً من إطلاق خطأ TypeError، سيعيد التعبير undefined:

const myObject = {
  greet: function() {
    console.log("مرحباً!");
  }
};

// استدعاء دالة موجودة
myObject.greet?.(); // يطبع "مرحباً!"

const anotherObject = {};
// استدعاء دالة غير موجودة بأمان
anotherObject.sayHello?.(); // لا يحدث شيء، ولا يوجد خطأ

3. الوصول إلى عناصر المصفوفات التي قد تكون غير موجودة

يمكن تطبيق سلسلة الاختيارية أيضاً عند محاولة الوصول إلى عناصر معينة في مصفوفة قد تكون null أو undefined، أو عند محاولة الوصول إلى عنصر خارج نطاق المصفوفة:

const users = [
  { name: "علي", email: "ali@example.com" },
  null,
  { name: "فاطمة", email: "fatima@example.com" }
];

const firstUserName = users?.[0]?.name;
const secondUserName = users?.[1]?.name; // undefined
const thirdUserName = users?.[2]?.name;
const nonExistentUser = users?.[5]?.name; // undefined

console.log(firstUserName); // علي
console.log(secondUserName); // undefined
console.log(thirdUserName); // فاطمة
console.log(nonExistentUser); // undefined

4. التعامل مع استجابات واجهات برمجة التطبيقات (APIs)

تُعد هذه الميزة مفيدة بشكل خاص عند التعامل مع استجابات API، حيث قد تختلف بنية البيانات أو قد تكون بعض الحقول مفقودة. لنفترض أنك تتوقع كائناً بهذا الشكل:

obj = {
  prop1: {
    prop2: {
      someProp: "value"
    }
  }
};

ولكن في الواقع، قد لا تكون كل هذه الحقول متوفرة دائماً، أو قد تعود بقيم null. إليك مثال واقعي:

// الكائن المتوقع من API
const expectedObj = {
  id: 9216,
  children: [
    { id: 123, children: null },
    { id: 124, children: [{ id: 1233, children: null }] }
  ]
};

// الكائن الفعلي الذي قد يعود من API
const actualObj = {
  id: 9216,
  children: null
};

// الوصول الآمن باستخدام سلسلة الاختيارية
const firstGrandchildId = expectedObj?.children?.[1]?.children?.[0]?.id;
const actualFirstGrandchildId = actualObj?.children?.[1]?.children?.[0]?.id;

console.log(firstGrandchildId); // 1233
console.log(actualFirstGrandchildId); // undefined (بدلاً من خطأ)

هذا السيناريو شائع جداً في التطبيقات التي تعتمد على استدعاءات API، حيث كانت الحلول السابقة تتطلب الكثير من التحققات الشرطية.

5. تبسيط الكود المعقد (وداعاً للتحققات المتعددة)

في تطبيقات مثل React، غالباً ما كنت ترى كوداً يحاول حماية نفسه من هذه المشكلات باستخدام تحققات منطقية متسلسلة:

render = () => {
  const obj = {
    prop1: {
      prop2: {
        someProp: "value",
      },
    },
  };
  return (
    <div>
      {obj && obj.prop1 && obj.prop1.prop2 && obj.prop1.prop2.someProp && (
        <div>
          {obj.prop1.prop2.someProp}
        </div>
      )}
    </div>
  );
};

هذا الكود يصبح طويلاً وصعب القراءة بسرعة. باستخدام سلسلة الاختيارية، يمكن تبسيطه بشكل كبير:

render = () => {
  const obj = {
    prop1: {
      prop2: {
        someProp: "value",
      },
    },
  };
  return (
    <div>
      {obj?.prop1?.prop2?.someProp && (
        <div>
          {obj.prop1.prop2.someProp}
        </div>
      )}
    </div>
  );
};

في الماضي، غالباً ما كنا نستخدم مكتبات خارجية مثل Lodash، وتحديداً الدالة _.get()، للتعامل مع هذه المشكلة:

// باستخدام Lodash
const value = _.get(obj, 'prop1.prop2.someProp');
console.log(value); // undefined إذا كانت أي من الخصائص غير موجودة

سلسلة الاختيارية ?. تقدم نفس الوظيفة تماماً، ولكنها الآن مدمجة كجزء أساسي من اللغة، مما يقلل الاعتماد على المكتبات الخارجية ويجعل الكود أكثر حداثة وفعالية.

كيف تعمل سلسلة الاختيارية ?.؟

تُستخدم سلسلة الاختيارية ?. لتسلسل الوصول إلى الخصائص التي قد تكون null أو undefined. عندما يواجه محرك JavaScript الرمز ?. في سلسلة وصول إلى خاصية، فإنه يتحقق مما إذا كان المرجع الأيسر للرمز ?. هو null أو undefined. إذا كان كذلك، تتوقف عملية التقييم فوراً وتُعاد القيمة undefined للسلسلة بأكملها، دون محاولة الوصول إلى الخصائص اللاحقة. هذا يمنع إطلاق أخطاء TypeError.

const userProfile = {
  details: {
    address: {
      street: "طريق الملك فهد"
    }
  }
};

const streetName = userProfile?.details?.address?.street;
console.log(streetName); // طريق الملك فهد

const zipCode = userProfile?.details?.address?.zipCode;
console.log(zipCode); // undefined (لأن zipCode غير موجودة)

const country = userProfile?.location?.country;
console.log(country); // undefined (لأن location غير موجودة)

دمج سلسلة الاختيارية مع عامل دمج القيم الفارغة ??

في كثير من الأحيان، قد لا نرغب في الحصول على undefined كقيمة نهائية، بل نريد قيمة افتراضية ذات معنى إذا كانت الخاصية غير موجودة. هنا يأتي دور عامل دمج القيم الفارغة (Nullish Coalescing Operator)، المُمثل بالرمز ??، والذي قُدم أيضاً في ES2020.

يعيد عامل ?? المعامل الأيمن إذا كان المعامل الأيسر null أو undefined. بخلاف عامل || (OR) الذي يتعامل مع القيم “الكاذبة” (falsy values) مثل 0 أو '' أو false كقيم فارغة، فإن ?? يتعامل فقط مع null و undefined. هذا يجعله رفيقاً مثالياً لسلسلة الاختيارية.

let familyTree = {
  us: {
    children: {}
  }
};

// باستخدام _.get من Lodash مع قيمة افتراضية
// const grandChildrenLodash = _.get(familyTree, 'us.children.theirChildren', 'لا يوجد أحفاد');
// console.log(grandChildrenLodash); // لا يوجد أحفاد

// باستخدام سلسلة الاختيارية وعامل دمج القيم الفارغة
const grandChildren = familyTree?.us?.children?.theirChildren ?? 'لا يوجد أحفاد';
console.log(grandChildren); // لا يوجد أحفاد

// مثال آخر: لو كانت القيمة 0
const product = {
  details: {
    stock: 0
  }
};
const availableStock = product?.details?.stock ?? 'غير متوفر';
console.log(availableStock); // 0 (لأن 0 ليست null أو undefined)

const price = product?.details?.price ?? 'غير محدد';
console.log(price); // غير محدد

هذا المزيج يوفر طريقة قوية وآمنة للوصول إلى الخصائص وتوفير قيم افتراضية عند الضرورة.

كيفية استخدام سلسلة الاختيارية ?. في مشاريعك

بما أن سلسلة الاختيارية هي ميزة حديثة نسبياً في ECMAScript 2020، فإن دعمها يختلف بين البيئات المختلفة. إليك كيفية التأكد من إمكانية استخدامها:

1. في متصفحات الويب الحديثة

معظم المتصفحات الحديثة مثل Chrome، Firefox، Edge، و Safari تدعم سلسلة الاختيارية بشكل كامل. يمكنك تجربة الأمثلة مباشرة في وحدة تحكم المطور (Developer Console) في متصفحك. إذا كنت تستخدم إصداراً قديماً جداً من المتصفح، فقد لا تعمل الميزة بشكل مباشر. في بعض الحالات، قد تحتاج إلى تفعيل الميزات التجريبية لـ JavaScript (على سبيل المثال، بزيارة chrome://flags/ وتمكين “Experimental JavaScript” في Chrome، ولكن هذا نادراً ما يكون ضرورياً الآن).

2. في تطبيقات Node.js

إصدارات Node.js الحديثة (مثل Node.js 14 والإصدارات الأحدث) تدعم سلسلة الاختيارية بشكل افتراضي. إذا كنت تستخدم إصداراً أقدم من Node.js، أو إذا كنت ترغب في ضمان التوافقية مع بيئات قديمة، يمكنك استخدام Babel.

Babel هو محول JavaScript يسمح لك بكتابة كود JavaScript حديث واستخدام ميزات ECMAScript الجديدة، ثم يقوم بتحويلها إلى إصدار متوافق مع البيئات القديمة. لإضافة دعم سلسلة الاختيارية عبر Babel، يمكنك تثبيت الإضافة (plugin) المناسبة وتكوين ملف .babelrc الخاص بك كما يلي:

{
  "plugins": [
    "@babel/plugin-proposal-optional-chaining",
    "@babel/plugin-proposal-nullish-coalescing-operator" // يفضل إضافتها أيضاً
  ]
}

هذا يضمن أن الكود الخاص بك سيعمل بشكل صحيح حتى في البيئات التي لا تدعم الميزة بشكل أصلي.

الخلاصة التقنية

تُعد سلسلة الاختيارية ?. وعامل دمج القيم الفارغة ?? من الإضافات الجوهرية والمفيدة للغاية للغة JavaScript في إصدار ES2020. لقد غيرت هذه الميزات بشكل جذري طريقة تعاملنا مع الكائنات المتداخلة والبيانات غير المتوقعة، مقدمةً حلاً أنيقاً وفعالاً لمشكلة كانت تتطلب في السابق كوداً مطولاً ومعقداً أو الاعتماد على مكتبات خارجية.

باستخدام ?.، يمكن للمطورين الآن الوصول بأمان إلى الخصائص المتعمقة واستدعاء الدوال التي قد تكون غير موجودة، مما يقلل من احتمالية حدوث أخطاء TypeError ويجعل الكود أكثر نظافة وقابلية للقراءة. وعند دمجها مع ??، يمكننا توفير قيم افتراضية منطقية للخصائص المفقودة، مما يعزز من مرونة التطبيق وقدرته على التعامل مع سيناريوهات البيانات المختلفة.

إن تبني هذه الميزات الحديثة لا يقتصر فقط على تحسين جودة الكود وتقليل حجمه، بل يعكس أيضاً التزام المطورين بكتابة كود JavaScript عصري وفعال، يواكب أحدث التطورات في اللغة. ننصح بشدة بدمج سلسلة الاختيارية في مشاريعك لتبسيط منطق الوصول إلى البيانات وتحسين تجربة التطوير.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *