كيفية بناء قائمة أكورديون في React من الصفر دون أي مكتبات خارجية
مقدمة: لماذا نستخدم قائمة أكورديون في React؟
تُعد قائمة Accordion من الواجهات الشائعة في تطبيقات الويب، لأنها تساعد على عرض المحتوى بشكل منظم وقابل للتوسيع والطيّ عند الحاجة. يمكن استخدامها في صفحات الأسئلة الشائعة، والقوائم المتداخلة، وعرض تفاصيل الفروع أو الخدمات، وغيرها من السيناريوهات التي تتطلب تقديم كمية كبيرة من المعلومات بطريقة مرتبة وسهلة التصفح.
في هذا الدليل العملي، سنبني مكوّن Accordion في React من الصفر بالكامل، خطوة بخطوة، ومن دون استخدام أي مكتبات خارجية. كما سنعتمد على React Hooks لإدارة الحالة داخل المكونات بطريقة حديثة وواضحة.

تهيئة المشروع وبناء البيئة الأولية
ابدأ بإنشاء مشروع جديد باستخدام أداة create-react-app. هذه الخطوة توفر لك هيكلًا جاهزًا لتطبيق React مع إعدادات التطوير الأساسية.
npx create-react-app react-accordion-demo
بعد إنشاء المشروع، احذف الملفات الموجودة داخل مجلد src، ثم أنشئ الملفات التالية:
src/index.jssrc/App.jssrc/styles.css
ثم أنشئ مجلدًا إضافيًا باسم utils داخل src لاستخدامه لاحقًا في فصل البيانات الثابتة عن منطق الواجهة.
بعد ذلك، افتح ملف styles.css وأضف إليه التنسيقات المناسبة لعرض عناصر الأكورديون بشكل واضح وسهل الاستخدام.
إنشاء الصفحة الأولى لمكوّن الأكورديون
في البداية، سنعرض قسمًا واحدًا فقط لفهم الفكرة الأساسية قبل الانتقال إلى بناء نسخة قابلة لإعادة الاستخدام.
افتح ملف src/App.js وأضف الكود التالي:
import React from 'react' ;
const App = () => {
const accordionData = {
title : 'Section 1' ,
content : `Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quis sapiente laborum cupiditate possimus labore, hic temporibus velit dicta earum suscipit commodi eum enim atque at? Et perspiciatis dolore iure voluptatem.`
};
const { title, content } = accordionData;
return (
<React.Fragment>
<h1>React Accordion Demo</h1>
<div className="accordion">
<div className="accordion-item">
<div className="accordion-title">
<div>{title}</div>
<div>+</div>
</div>
<div className="accordion-content">
{content}
</div>
</div>
</div>
</React.Fragment>
);
};
export default App;
في هذا المثال، قمنا باستخدام الكائن accordionData لتخزين عنوان القسم ومحتواه. كما استُخدمت صيغة template literal في الخاصية content حتى يكون النص متعدد الأسطر وأسهل في القراءة داخل الكود.
بعد ذلك، افتح ملف src/index.js وأضف الكود التالي:
import React from 'react' ;
import ReactDOM from 'react-dom' ;
import App from './App' ;
import './styles.css' ;
ReactDOM.render(
<App />,
document.getElementById('root')
);
الآن شغّل التطبيق عبر الطرفية باستخدام الأمر yarn start، وستظهر لك الواجهة الأولى للمشروع.

إضافة ميزة فتح وإغلاق الأكورديون
حتى الآن، يعرض التطبيق قسمًا واحدًا فقط، لكنه مفتوح دائمًا ولا يمكن إغلاقه. لإضافة التفاعل، سنستخدم الحالة state عبر useState.
أضف الحالة التالية داخل المكوّن:
const [isActive, setIsActive] = useState(false);
ثم حدّث الاستيراد في أعلى الملف ليشمل useState:
import React, { useState } from 'react' ;
بعدها، أضف معالج النقر onClick إلى العنصر الذي يحمل الصنف accordion-title كما يلي:
<div className="accordion">
<div className="accordion-item">
<div className="accordion-title" onClick={() => setIsActive(!isActive)}>
<div>{title}</div>
<div>{isActive ? '-' : '+'}</div>
</div>
{isActive && <div className="accordion-content">{content}</div>}
</div>
</div>
ما الذي يحدث هنا؟ عند النقر على العنوان، يتم عكس قيمة isActive. إذا كانت false تصبح true، وإذا كانت true تعود إلى false. وبناءً على ذلك:
- نعرض الرمز
+عندما يكون القسم مغلقًا. - ونعرض الرمز
-عندما يكون القسم مفتوحًا. - كما لا يتم إظهار المحتوى إلا إذا كانت قيمة
isActiveتساويtrue.
وهذا الجزء هو المسؤول عن العرض الشرطي للمحتوى:
{isActive && <div className="accordion-content">{content}</div>}

تحويل الأكورديون إلى مكوّن قابل لإعادة الاستخدام
الاعتماد على كود مكرر لكل قسم ليس خيارًا جيدًا، خصوصًا عندما يزداد عدد العناصر. لذلك، من الأفضل إنشاء مكوّن مستقل يعرض عنصر أكورديون واحدًا، ثم إعادة استخدامه أكثر من مرة.
أنشئ ملفًا جديدًا باسم Accordion.js داخل مجلد src، ثم أضف الكود التالي:
import React, { useState } from 'react' ;
const Accordion = ({ title, content }) => {
const [isActive, setIsActive] = useState(false);
return (
<div className="accordion-item">
<div className="accordion-title" onClick={() => setIsActive(!isActive)}>
<div>{title}</div>
<div>{isActive ? '-' : '+'}</div>
</div>
{isActive && <div className="accordion-content">{content}</div>}
</div>
);
};
export default Accordion;
في هذا المكوّن، نقلنا منطق الحالة والعرض من App.js إلى ملف مستقل، وأصبح المكوّن يستقبل القيم الديناميكية عبر props باستخدام تفكيك الكائنات destructuring بالشكل التالي:
const Accordion = ({ title, content }) => {
إضافة عدة أقسام داخل الأكورديون
بعد إنشاء مكوّن مستقل، يمكننا عرض عدة عناصر بسهولة بالاعتماد على مصفوفة بيانات ثم المرور عليها باستخدام الدالة map().
افتح ملف App.js واستبدل محتواه بالكود التالي:
import React from 'react' ;
import Accordion from './Accordion' ;
const App = () => {
const accordionData = [
{
title : 'Section 1' ,
content : `Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quis sapiente laborum cupiditate possimus labore, hic temporibus velit dicta earum suscipit commodi eum enim atque at? Et perspiciatis dolore iure voluptatem.`
},
{
title : 'Section 2' ,
content : `Lorem ipsum, dolor sit amet consectetur adipisicing elit. Mollitia veniam reprehenderit nam assumenda voluptatem ut. Ipsum eius dicta, officiis quaerat iure quos dolorum accusantium ducimus in illum vero commodi pariatur? Impedit autem esse nostrum quasi, fugiat a aut error cumque quidem maiores doloremque est numquam praesentium eos voluptatem amet! Repudiandae, mollitia id reprehenderit a ab odit!`
},
{
title : 'Section 3' ,
content : `Sapiente expedita hic obcaecati, laboriosam similique omnis architecto ducimus magnam accusantium corrupti quam sint dolore pariatur perspiciatis, necessitatibus rem vel dignissimos dolor ut sequi minus iste? Quas?`
}
];
return (
<div>
<h1>React Accordion Demo</h1>
<div className="accordion">
{accordionData.map(({ title, content }) => (
<Accordion title={title} content={content} />
))}
</div>
</div>
);
};
export default App;
هنا حوّلنا accordionData من كائن واحد إلى مصفوفة من الكائنات. بعد ذلك استخدمنا map() لإنشاء مكوّن Accordion لكل عنصر داخل المصفوفة، مع تمرير title وcontent لكل قسم.
هذه الطريقة تمنحك مرونة أكبر في التعامل مع البيانات، وتختصر كثيرًا من التكرار داخل الواجهة.

إعادة تنظيم الكود وفصل البيانات عن الواجهة
من أفضل الممارسات في تطوير تطبيقات React فصل البيانات الثابتة عن ملفات العرض. هذا يجعل المشروع أنظف وأسهل في الصيانة، كما يسهل لاحقًا استبدال البيانات الثابتة ببيانات قادمة من API أو قاعدة بيانات.
أنشئ ملفًا جديدًا باسم content.js داخل مجلد utils، ثم أضف إليه البيانات التالية:
export const accordionData = [
{
title : 'Section 1' ,
content : `Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quis sapiente laborum cupiditate possimus labore, hic temporibus velit dicta earum suscipit commodi eum enim atque at? Et perspiciatis dolore iure voluptatem.`
},
{
title : 'Section 2' ,
content : `Lorem ipsum, dolor sit amet consectetur adipisicing elit. Mollitia veniam reprehenderit nam assumenda voluptatem ut. Ipsum eius dicta, officiis quaerat iure quos dolorum accusantium ducimus in illum vero commodi pariatur? Impedit autem esse nostrum quasi, fugiat a aut error cumque quidem maiores doloremque est numquam praesentium eos voluptatem amet! Repudiandae, mollitia id reprehenderit a ab odit!`
},
{
title : 'Section 3' ,
content : `Sapiente expedita hic obcaecati, laboriosam similique omnis architecto ducimus magnam accusantium corrupti quam sint dolore pariatur perspiciatis, necessitatibus rem vel dignissimos dolor ut sequi minus iste? Quas?`
}
];
ثم افتح ملف App.js وعدّله ليصبح بالشكل التالي:
import React from 'react' ;
import Accordion from './Accordion' ;
import { accordionData } from './utils/content' ;
const App = () => {
return (
<div>
<h1>React Accordion Demo</h1>
<div className="accordion">
{accordionData.map(({ title, content }) => (
<Accordion title={title} content={content} />
))}
</div>
</div>
);
};
export default App;
بهذا الشكل أصبح ملف App.js مسؤولًا فقط عن تجميع المكونات وعرضها، بينما أصبحت البيانات في ملف منفصل. هذه البنية أوضح وأسهل للتوسّع مستقبلًا.

أفضل ممارسات لتحسين مكوّن Accordion
1. إضافة خاصية key عند استخدام map()
في التطبيقات الفعلية، من المهم إضافة خاصية key لكل عنصر يتم إنشاؤه داخل map() حتى يتمكن React من تتبع العناصر بكفاءة:
{accordionData.map(({ title, content }, index) => (
<Accordion key={index} title={title} content={content} />
))}
ويُفضّل استخدام معرّف فريد بدلًا من الفهرس index إذا كانت البيانات ديناميكية وقابلة للتعديل.
2. تحسين تجربة المستخدم
- إضافة انتقالات بصرية
CSS transitionsلجعل الفتح والإغلاق أكثر سلاسة. - دعم الوصول
Accessibilityعبر استخدام أزرار فعلية بدل عناصرdivالقابلة للنقر فقط. - إتاحة فتح عنصر واحد فقط في كل مرة إذا كان هذا أنسب لتجربة المستخدم.
3. فصل المنطق عند التوسع
إذا أصبح المشروع أكبر، يمكنك نقل منطق التحكم في العنصر المفتوح إلى مكوّن الأب App، خصوصًا إذا كنت تريد التحكم في جميع العناصر من مكان واحد.
لماذا هذا الأسلوب مناسب لمشاريع حقيقية؟
بناء مكوّن Accordion من الصفر يمنحك تحكمًا كاملًا في السلوك والشكل والأداء، ويجنبك تحميل مكتبات إضافية لمجرد ميزة بسيطة يمكن تنفيذها بسهولة. كما أن هذا الأسلوب يساعدك على فهم مبادئ أساسية في React مثل:
- إدارة الحالة باستخدام
useState. - تمرير البيانات عبر
props. - تقسيم الواجهة إلى مكونات قابلة لإعادة الاستخدام.
- فصل البيانات عن منطق العرض.
الخلاصة التقنية
إنشاء قائمة Accordion في React دون مكتبات خارجية هو تمرين ممتاز لفهم بنية المكونات وآلية إدارة الحالة والعرض الشرطي. هذا النهج مناسب جدًا عندما تحتاج إلى مكوّن خفيف، واضح، وسهل التخصيص. ومن الناحية التقنية، فإن فصل المكوّنات عن البيانات وتحويل المنطق إلى وحدات قابلة لإعادة الاستخدام يجعل المشروع أكثر نظافة وقابلية للتوسع، وهي نقطة مهمة في أي تطبيق احترافي مبني باستخدام React.