OOZOU
تواصلوا معنا
العودة إلى المدونة

6 أنماط خاطئة في React ينبغي تجنّبها

تجنّبوا هذه الأخطاء الشائعة لبناء تطبيق عالي الأداء

14 أبريل 2022
8 دقائق للقراءة
6 أنماط خاطئة في React ينبغي تجنّبها

تبدو React.js مكتبة بسيطة وأنيقة. يكفي أن تكتبوا دالة JavaScript تمثّل مكوّنًا (component) يُعاد عرضه فورًا كلما تغيّرت بياناتكم. الفكرة بسيطة، لكنها مضلِّلة أيضًا. ففي الواقع، React مكتبة واجهات مستخدم شديدة التعقيد، تراكم عليها قدر كبير من الإرث التقني في السنوات الأخيرة.

تحاول أطر عمل حديثة كثيرة أن تؤدي الأمور بكفاءة أكبر، لكن React لا تزال في الصدارة، وإذا أردتم أن تعملوا في تطوير الويب عام 2022 فهي مهارة تستحق الاكتساب. المشكلة أن React لا تفرض طريقة محددة للعمل، وتتيح في الوقت نفسه طرقًا عديدة لحل المشكلة ذاتها، مما يترك للمطوّرين مساحة واسعة لإفساد الأمور بأفكارهم السيئة.

نستعرض في مقال اليوم أكثر الأنماط الخاطئة (anti-patterns) شيوعًا في React، إلى جانب حيل ونصائح لتحسين قاعدة الكود.

1) المكوّنات المتداخلة

يمكن تسميتها أيضًا المكوّنات المحصورة في النطاق (scoped components). فعند كتابة المكوّنات قد نميل أحيانًا إلى وضع بعضها داخل بعض على هذا النحو:

import { useState } from 'react';

export const ParentComponent = () => {
    const [count, setCount] = useState(0);

    const ChildComponent = () => (
      <div>Hello World</div>
    );

    return (
     <div>
        <ChildComponent />
        <button onClick={() => setCount(count + 1)}>Count me</button>
     </div>
    );
};

يبدو ذلك منطقيًا في النهاية، فالمكوّن الابن لن يُستخدم خارج MainComponent، ويمكنه حاليًا استخدام متغير الحالة name دون أن يستقبله كـ prop. لكن هذا التداخل بين الدوال يسبب في الواقع مشكلة صغيرة في الأداء.

فكلما استُدعي المكوّن الأب، سيُعاد إنشاء تعريف المكوّن الابن. أي أن الدالة نفسها ستُعرَّف مرة بعد مرة مع كل تنفيذ. وإذا كنتم تكتبون بهذه الطريقة في كل مكان، فقد تكون مشكلة صغيرة تؤثر في الأداء دون أن تلاحظوها. الأفضل أن تُنشئوا المكوّنات الأبناء بشكل منفصل.

import { useState } from 'react';

const ChildComponent = () => (
   <div>Hello World</div>
);

export const ParentComponent = () => {
    const [count, setCount] = useState(0);

    return (
     <div>
        <ChildComponent />
        <button onClick={() => setCount(count + 1)}>Count me</button>
     </div>
    );
};

2) تمرير الخصائص عبر طبقات متعددة (Props Drilling)

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

يُعرف هذا بـ prop drilling، أي "حفر" الخصائص عبر الطبقات، وهو يجعلكم تتمنون لو تحفرون ثقبًا في رؤوسكم. أحد الحلول هو الاستعانة بمكتبة لإدارة الحالة مثل Redux، لكنه نهج ثقيل. أما حين تكون لديكم بيانات عامة تُستخدم في كل مكان، مثل المستخدم المسجّل دخوله، فيمكنكم مشاركتها في كل مكان عبر context API. إذ توفّرون البيانات في نقطة معينة من شجرة المكوّنات، فتصبح متاحة لأي مكوّن متداخل تحتها مهما كان عمقه. يبدو إتاحة الوصول إلى البيانات بهذه الطريقة أمرًا رائعًا، لكن له ثمن: ينبغي استخدام context API باعتدال، لأنه يجعل إعادة استخدام المكوّنات مستحيلة ما لم يكن الـ provider موجودًا كأب لها.

ومعظم التطبيقات لا تضم سوى عدد قليل من القيم العامة فعلًا. هذه القيم مكانها الـ context، أما كل ما عداها فينبغي أن يبقى محليًا قدر الإمكان.

3) استخدام Context للنطاق العام

في بعض الحالات قد يصبح الخط الفاصل بين الحالة (state) والسياق (context) ضبابيًا. لكن من الأفضل أن تتذكروا أنهما أمران مختلفان تمامًا، صُمّم كل منهما لحالات استخدام مختلفة.

يتيح لكم context مشاركة الخصائص بين المكوّنات المترابطة، فيجنّبكم مشكلة "prop drill down" المزعجة، حيث تضطرون إلى تمرير الخاصية نفسها عبر 4 مستويات مختلفة لمجرد إيصالها إلى المكوّن الذي يحتاجها.

أما الحالة العامة (global state) فمهمتها إدارة القيم التي تسري على التطبيق بأكمله.

وإذا أردتم استخدام context في هذه الحالة، فستحتاجون إلى إنشاء context provider يحيط بالتطبيق كله، وهذا يتعارض مع الغرض الأساسي من هذه البنية.

وكما في بقية الأمثلة في هذه القائمة، يمكنكم فعلًا استخدام context لإدارة الحالة العامة. لكن ما إن تبدأوا بذلك في تطبيق يكبر باستمرار حتى تخرج الأمور عن السيطرة.

أنصح باستخدام Redux لتخزين الحالة العامة، واستخدام Context API لأجزاء محددة من التطبيق ضمن سياق واضح الحدود.

4) شجرة مكوّنات ضخمة داخل مكوّن واحد

حين أبدأ بناء تطبيق React، أبدأ عادةً بإنشاء مكوّن واحد ضخم، غالبًا داخل مكوّن app الذي تولّده أداة CLI مثل create-react-app. وأفعل ذلك عن قصد، لأنني لا أعرف في البداية كيف أريد تنظيم الكود، ولا أريد إضاعة وقتي في التنظيم قبل أن أفهم ما أريد فعله أصلًا.

لكن ذلك يقود إلى نمط خاطئ: مكوّن واحد كبير جدًا وشديد التداخل. ومع مكوّن كهذا يصعب الفهم وإعادة الهيكلة (refactoring) والاختبار. يمكننا تحسين هذا الكود بإعادة هيكلته إلى مكوّنات قابلة لإعادة الاستخدام تعبّر بشكل أوضح عمّا يفعله. قد يبدو ذلك عملًا كثيرًا، لكن لحسن الحظ هناك إضافة لـ VSCode اسمها Glean تستفيد من بيئة التطوير لتنجز هذا العمل نيابةً عنا. إنها توفّر وقتًا هائلًا، ولا أستطيع الاستغناء عنها.

5) استخدام الفهرس (index) كمفتاح في الحلقة

مررنا جميعًا بهذا الموقف: نحتاج إلى عرض قائمة من العناصر، فنلجأ بسرعة إلى الصياغة العملية التالية:

<ul>
    {items.map((item, index) => <li key={index}>{item.name}</li>)}
</ul>

المثال بسيط، لكن لاحظوا أننا نستخدم المتغير index الذي توفّره الدالة map. علينا أن نمرّر key لعناصرنا، فلماذا لا نستخدم هذا المتغير، أليس كذلك؟

لا! أرجوكم لا تستخدموه...

والسبب الرئيسي أنكم نادرًا ما تعرضون قائمة بهذه البساطة. ففي الواقع، تعرضون عادةً قائمة قد تتغير، أو يُعاد ترتيبها، أو تكون عناصرها أعقد من مجرد نص. وماذا يحدث حينها؟ تستخدم React قيمة key لتعرف متى أُضيف عنصر أو تغيّر أو حُذف. فالمفتاح وُضع ليكون معرّفًا فريدًا للعنصر، لا مجرد رقم ترتيبه في القائمة.

قد تظنون أن الفهرس في مثالنا يؤدي دور المعرّف فعلًا، لكن تخيّلوا ما يحدث لو أضفنا عناصر جديدة في منتصف قائمة المستخدمين. ستشير قيم الفهرس الجديدة إلى مستخدمين مختلفين، وبالتالي إلى مكوّن مختلف (عنصر li مختلف). فتلتبس الأمور على React وتنتج سلوكًا غير متوقع.

خذوا وقتكم لإنشاء معرّف فريد لكل عناصركم، فسيوفّر عليكم ذلك كثيرًا من المتاعب. وبعد أن يصبح لديكم المعرّف الفريد يمكنكم إعادة كتابة الكود على هذا النحو:

<ul>
    {items.map(item => <li key={item.ID}>{item.name}</li>)}
</ul>

6) عدم حفظ نتيجة العمليات الحسابية المعقدة (Memoization)

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

import { useState } from 'react';

export const ParentComponent = () => {
    const [count, setCount] = useState(0);

    const bigData = reallyHeavyCalculation();

    return (
     <div>
        <button onClick={() => setCount(count + 1)}>Count me</button>
     </div>
    );
};

المشكلة في التنفيذ الحالي أنه كلما تغيّرت الحالة في هذا المكوّن، ستُعاد العملية الحسابية المكلفة، رغم أنها لا تعتمد إلا على قيمة count وحدها. قد لا يكون الأمر واضحًا هنا، لكنه عنق زجاجة محتمل في الأداء. وفي موقف كهذا ستحتاجون إلى استخدام الـ hook المسمى useMemo. فهو يحفظ آخر قيمة، ولا ينفّذ الدالة الفعلية إلا حين تتغير البيانات التي تعتمد عليها.

import { useState, useMemo } from 'react';

export const ParentComponent = () => {
    const [count, setCount] = useState(0);

    const bigData = useMemo(() => reallyHeavyCalculation(), []);

    return (
     <div>
        <button onClick={() => setCount(count + 1)}>Count me</button>
     </div>
    );
};

وبالمثل، قد تواجهون الموقف نفسه مع الدوال، ولهذا توفّر React أيضًا الـ hook المسمى useCallback.

الخلاصة

مع أن React قد تسمح لنا بحل المشكلة بطرق متعددة، تجنّبوا هذه الأنماط الخاطئة لتبنوا تطبيقات عالية الأداء دائمًا.

فأحيانًا تبدأ هذه الأنماط الخاطئة ككرة ثلج صغيرة، ثم تتراكم لتصبح لاحقًا انهيارًا جليديًا من المشكلات.

Related Articles

كيف أصمّم برمجيات؟ دليل المبتدئين لأصحاب الأعمال

كيف أصمّم برمجيات؟ دليل المبتدئين لأصحاب الأعمال

تصميم البرمجيات عملية ممتعة لكنها معقدة، تتطلب الإبداع والتفكير المنطقي ومهارات حل المشكلات.

اقرأوا المزيد
مستقبل الحوسبة الطرفية: ما تحتاج الشركات إلى معرفته

مستقبل الحوسبة الطرفية: ما تحتاج الشركات إلى معرفته

يتطور المشهد الرقمي بسرعة، وعلى الشركات أن تتكيّف لتواكب الطلب المتزايد على السرعة والكفاءة والمعالجة في الوقت الفعلي.

اقرأوا المزيد
مستقبل قطاع التجزئة: الذكاء الاصطناعي والواقع المعزّز واتجاهات التحوّل الرقمي

مستقبل قطاع التجزئة: الذكاء الاصطناعي والواقع المعزّز واتجاهات التحوّل الرقمي

يتطور قطاع التجزئة بوتيرة غير مسبوقة بفضل الذكاء الاصطناعي (AI) والواقع المعزّز (AR) وتقنيات التحوّل الرقمي. فلم تعد المتاجر التقليدية مجرد أماكن لبيع المنتجات، بل أصبحت متاجر ذكية وتفاعلية.

اقرأوا المزيد

Have a Project in Mind?

Let's discuss how we can help bring your ideas to life with our expertise in web and mobile development.

ابدأوا المحادثةhello@oozou.com
OOZOU Logo

بانكوك · سنغافورة · هونغ كونغ

X
Facebook
Instagram
LinkedIn
OOZOU Co., Ltd.
548 One City Centre
Ploenchit Road
Bangkok 10330
Thailand
+66 2544 0000
hello@oozou.com

الخدمات

  • تطوير الويب
  • وكلاء الذكاء الاصطناعي والذكاء الاصطناعي التوليدي
  • تطوير تطبيقات الجوال
  • تحليلات البيانات وهندستها
  • تصميم تجربة المستخدم وواجهته (UX/UI) وتصميم المنتجات
  • التحوّل الرقمي

الشركة

  • من نحن
  • الوظائف
  • المدونة
  • دراسات الحالة
  • عروض حية
  • تعلّمت اليوم
  • تواصل معنا

المزيد

  • القطاعات
  • شبكة الشركاء
© 2026 OOZOU. جميع الحقوق محفوظة.
سياسة الخصوصيةمدونة قواعد السلوكسياسة مكافحة الرشوة والفساد