اذهب إلى المحتوى

البحث في الموقع

المحتوى عن 'دليل تعلم react'.

  • ابحث بالكلمات المفتاحية

    أضف وسومًا وافصل بينها بفواصل ","
  • ابحث باسم الكاتب

نوع المحتوى


التصنيفات

  • الإدارة والقيادة
  • التخطيط وسير العمل
  • التمويل
  • فريق العمل
  • دراسة حالات
  • التعامل مع العملاء
  • التعهيد الخارجي
  • السلوك التنظيمي في المؤسسات
  • عالم الأعمال
  • التجارة والتجارة الإلكترونية
  • نصائح وإرشادات
  • مقالات ريادة أعمال عامة

التصنيفات

  • مقالات برمجة عامة
  • مقالات برمجة متقدمة
  • PHP
    • Laravel
    • ووردبريس
  • جافاسكربت
    • لغة TypeScript
    • Node.js
    • React
    • Vue.js
    • Angular
    • jQuery
    • Cordova
  • HTML
  • CSS
    • Sass
    • إطار عمل Bootstrap
  • SQL
  • لغة C#‎
    • ‎.NET
    • منصة Xamarin
  • لغة C++‎
  • لغة C
  • بايثون
    • Flask
    • Django
  • لغة روبي
    • إطار العمل Ruby on Rails
  • لغة Go
  • لغة جافا
  • لغة Kotlin
  • لغة Rust
  • برمجة أندرويد
  • لغة R
  • الذكاء الاصطناعي
  • صناعة الألعاب
  • سير العمل
    • Git
  • الأنظمة والأنظمة المدمجة

التصنيفات

  • تصميم تجربة المستخدم UX
  • تصميم واجهة المستخدم UI
  • الرسوميات
    • إنكسكيب
    • أدوبي إليستريتور
  • التصميم الجرافيكي
    • أدوبي فوتوشوب
    • أدوبي إن ديزاين
    • جيمب GIMP
    • كريتا Krita
  • التصميم ثلاثي الأبعاد
    • 3Ds Max
    • Blender
  • نصائح وإرشادات
  • مقالات تصميم عامة

التصنيفات

  • مقالات DevOps عامة
  • خوادم
    • الويب HTTP
    • البريد الإلكتروني
    • قواعد البيانات
    • DNS
    • Samba
  • الحوسبة السحابية
    • Docker
  • إدارة الإعدادات والنشر
    • Chef
    • Puppet
    • Ansible
  • لينكس
    • ريدهات (Red Hat)
  • خواديم ويندوز
  • FreeBSD
  • حماية
    • الجدران النارية
    • VPN
    • SSH
  • شبكات
    • سيسكو (Cisco)

التصنيفات

  • التسويق بالأداء
    • أدوات تحليل الزوار
  • تهيئة محركات البحث SEO
  • الشبكات الاجتماعية
  • التسويق بالبريد الالكتروني
  • التسويق الضمني
  • استسراع النمو
  • المبيعات
  • تجارب ونصائح
  • مبادئ علم التسويق

التصنيفات

  • مقالات عمل حر عامة
  • إدارة مالية
  • الإنتاجية
  • تجارب
  • مشاريع جانبية
  • التعامل مع العملاء
  • الحفاظ على الصحة
  • التسويق الذاتي
  • العمل الحر المهني
    • العمل بالترجمة
    • العمل كمساعد افتراضي
    • العمل بكتابة المحتوى

التصنيفات

  • الإنتاجية وسير العمل
    • مايكروسوفت أوفيس
    • ليبر أوفيس
    • جوجل درايف
    • شيربوينت
    • Evernote
    • Trello
  • تطبيقات الويب
    • ووردبريس
    • ماجنتو
    • بريستاشوب
    • أوبن كارت
    • دروبال
  • الترجمة بمساعدة الحاسوب
    • omegaT
    • memoQ
    • Trados
    • Memsource
  • برامج تخطيط موارد المؤسسات ERP
    • تطبيقات أودو odoo
  • أنظمة تشغيل الحواسيب والهواتف
    • ويندوز
    • لينكس
  • مقالات عامة

التصنيفات

  • آخر التحديثات

أسئلة وأجوبة

  • الأقسام
    • أسئلة البرمجة
    • أسئلة ريادة الأعمال
    • أسئلة العمل الحر
    • أسئلة التسويق والمبيعات
    • أسئلة التصميم
    • أسئلة DevOps
    • أسئلة البرامج والتطبيقات

التصنيفات

  • كتب ريادة الأعمال
  • كتب العمل الحر
  • كتب تسويق ومبيعات
  • كتب برمجة
  • كتب تصميم
  • كتب DevOps

ابحث في

ابحث عن


تاريخ الإنشاء

  • بداية

    نهاية


آخر تحديث

  • بداية

    نهاية


رشح النتائج حسب

تاريخ الانضمام

  • بداية

    نهاية


المجموعة


النبذة الشخصية

تم العثور على 8 نتائج

  1. سنناقش في هذا الدرس حالة المكوِّنات (component state). ما هي حالة المكون (Component State)؟ أغلبية المكونات تأخذ الخاصيات props وتصيّر العنصر، لكن تتيح المكونات الحالة أيضًا، والتي يمكن أن تستخدم لتخزين معلومات حول المكوِّن التي يمكن أن تتغير مع مرور الوقت. وعادةً يأتي التغيير نتيجةً لأحداث المستخدم أو أحداث النظام (مثل رد لمدخلات المستخدم، أو طلبية للخادم بعد مرور فترة زمنية معيّنة). بعض الأمور التي يجب أخذها بالحسبان حول حالة مكوِّنات React: إذا كان للمكوِّن حالة فيمكن ضبط الحالة الافتراضية this.setState()‎ في الدالة البانية‎. تغيرات الحالة هي ما تجعلنا نعيد تصيير المكوِّن وجميع المكونات الفرعية التابعة له. يمكنك إعلام المكوِّن بتغيّر الحالة باستخدام this.setState()‎ لضبط حالةٍ جديدة. يمكن أن تدمج تغيرات الحالة البيانات الجديدة مع البيانات القديمة التي ما تزال محتواةً في الحالة (أي this.state). عند تغيير الحالة، فستجري عملية إعادة التصيير داخليًا، ولا يفترض بك استدعاء this.render()‎ مباشرةً أبدًا. يجب أن يحتوي كائن الحالة على القدر الأدنى من البيانات اللازم لواجهة المستخدم؛ فلا تضع البيانات المحسوبة أو مكونات React الأخرى أو الخاصيات props في كائن الحالة. العمل مع حالة المكونات يتطلب التعامل مع حالة المكونات عادةً ضبط الحالة الافتراضية، والوصول إلى الحالة الحالية، وتحديث الحالة. سننشِئ في المثال الآتي المكوِّن الذي يبيّن استخدام this.state.[STATE]‎ وthis.setState()‎. إذا ضغطتَ على المكوِّن في متصفح الويب (أي الوجه المبتسم) فستجد أنَّ المكوِّن سيبدِّل بين الحالات المتاحة (أي الأوجه المبتسمة)، وبالتالي تكون هنالك ثلاثة حالات ممكنة للمكوِّن وهي مرتبطة بالواجهة الرسومية ومعتمدة على نقرات المستخدم في واجهة المستخدم: class MoodComponent extends React.Component { state = {mood: ':|'}; constructor(props){ super(props); this.changeMood = this.changeMood.bind(this) } changeMood(event,a){ const moods = [':)',':|',':(']; const current = moods.indexOf(event.target.textContent); this.setState({mood: current === 2 ? moods[0] : moods[current+1]}); } render() { return ( <span style={{fontSize:'60',border:'1px solid #333',cursor:'pointer'}} onClick={this.changeMood}> {this.state.mood} </span> ) } }; ReactDOM.render(< MoodComponent />, app); لاحظ أنَّ للمكوِّن حالةً افتراضيةً هي :|، وهي مضبوطة باستخدام state = {mood: ':|'};‎ وهي تستخدم في المكوِّن عند تصييره لأول مرة عبر {this.state.mood}. أضفنا مستمعًا للأحداث لتغيير الحالة، وهي هذه الحالة سيؤدي حدث النقر (onClick) على عقدة إلى استدعاء الدالة changeMood، وداخل هذه الدالة سنستخدم this.setState()‎ للتبديل إلى الحالة التالية اعتمادًا على قيمة الحالة الحالية. بعد إجراء هذا التحديث (لاحظ أنَّ setState()‎ ستدمج التعديلات) فسيعاد تصيير العنصر وستتغير واجهة المستخدم. بعض الأمور التي علينا إبقاؤها في ذهننا عند الحديث عن حالة مكوِّنات React: إذا كان للمكوِّن حالة فيمكن ضبط الحالة الافتراضية الخاصية state‎. تغيرات الحالة هي ما تجعلنا نعيد تصيير المكوِّن وجميع المكونات الفرعية التابعة له. يمكنك إعلام المكوِّن بتغيّر الحالة باستخدام this.setState()‎ لضبط حالةٍ جديدة. هنالك طرائق أخرى (مثل forceUpdate()‎) ولكن لا يجدر بناء استخدامها إلا إذا أردنا دمج React مع مكتباتٍ من طرف ثالث. يمكن أن تدمج تغيرات الحالة البيانات الجديدة مع البيانات القديمة التي ما تزال محتواةً في الحالة (أي this.state). لكن هذا دمجٌ أو تحديثٌ سطحي، فلن يجرى عملية دمج عميقة. عند تغيير الحالة، فستجري عملية إعادة التصيير داخليًا، ولا يفترض بك استدعاء this.render()‎ مباشرةً أبدًا. يجب أن يحتوي كائن الحالة على القدر الأدنى من البيانات اللازم لواجهة المستخدم؛ فلا تضع البيانات المحسوبة أو مكونات React الأخرى أو الخاصيات props في كائن الحالة. الفروقات بين الحالة والخاصيات props هنالك أرضيةٌ مشتركة بين الحالة state والخاصيات props: كلاهما كائنات JavaScript عادية. يمكن لكليهما أن يمتلك قيمًا افتراضية. يمكن الوصول إليها باستخدام this.props أو this.state، لكن لا يجوز أن نضبط قيمهما بهذه الطريقة، إذ سيكون كلاهما للقراءة فقط عند استخدام this. لكنهما يستخدمان لأغراضٍ مختلفة وبطرائق مختلفة. الخاصيات props: تُمرَّر الخاصيات إلى المكوِّنات من البنية الأعلى منها، سواءً كانت من مكوِّنٍ أب أو بداية المجال حيث صُيَّرت React من الأساس. الغرض من الخاصيات هو تمرير قيم الضبط إلى المكوِّن. تخيل أنها كالوسائط المُمرَّرة إلى دالة (وإن لم تكن تستعمل صيغة JSX فهذا ما تفعله تحديدًا). الخاصيات غير قابلة للتعديل في المكوِّن الذي يستقبلها، أي لا يمكننا تعديل الخاصيات المُمرَّرة إلى المكوِّن من داخل المكوِّن نفسه. الحالة (state): الحالة هي تمثيلٌ للبيانات الذي سيرتبط في مرحلةٍ ما مع الواجهة الرسومية. يجب أن تبدأ الحالة دومًا بقيمةٍ افتراضية، ثم ستُعدّل الحالة داخليًا في المكوِّن باستخدام setState()‎. يمكن تعديل الحالة باستخدام المكوِّن الذي يحتوي عليها فقط، أي أنها خاصة. لا يفترض تعديل حالة المكوِّنات الأبناء، ويجب ألا يشارك المكوِّن حالةً قابلةً للتعديل. يجب أن يحتوي كائن الحالة على القدر الأدنى من البيانات اللازم لواجهة المستخدم؛ فلا تضع البيانات المحسوبة أو مكونات React الأخرى أو الخاصيات props في كائن الحالة. إنشاء مكونات عديمة الحالة (Stateless Components) عندما يكون المكون نتيجةً لاستخدام الخاصيات props فقط دون وجود حالة state، فيمكن كتابة المكوِّن على شكل دالة صرفة مما يجعلنا نتفادى إنشاء نسخة من مكوِّن React. ففي المثال الآتي سيكون MyComponent نتيجةً لاستدعاء دالة ناتجة عن React.createElement()‎. var MyComponent = function(props){ return <div>Hello {props.name}</div>; }; ReactDOM.render(<MyComponent name="doug" />, app); إذا ألقينا نظرةً على شيفرة JavaScript الناتجة عن عملية تحويل JSX، فستبدو الأمور جليةً وواضحةً لنا: var MyComponent = function MyComponent(props) { return React.createElement( "div", null, "Hello ", props.name ); }; ReactDOM.render(React.createElement(MyComponent, { name: "doug" }), app); إنشاء مكوِّن React دون اشتقاق الصنف React.Component‎ يشار إليه عادةً بمكوِّن عديم الحالة. لا يمكن تمرير خيارات الضبط إلى المكونات عديمة الحالة (مثل render أو componentWillUnmount …إلخ.)؛ لكن يمكن ضبط ‎.propTypes و ‎.defaultProps على الدالة. الشيفرة الآتية توضِّح مكوِّنًا عديم الحالة يستخدم ‎.propTypes و ‎.defaultProps: import PropTypes from 'prop-types'; var MyComponent = function(props){ return <div>Hello {props.name}</div>; }; MyComponent.defaultProps = {name:"John Doe"}; MyComponent.propTyes = {name: PropTypes.string}; ReactDOM.render(<MyComponent />, app); ملاحظات حاول أن تجعل أكبر عدد من المكونات عديمَ الحالة. ترجمة -وبتصرف- للفصل React Component State من كتاب React Enlightenment
  2. سنناقش في هذا الدرس استخدام خاصيات مكوِّنات React والتي تعرف بالخاصيات props. ما هي خاصيات المكونات؟ table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } أسهل طريقة لشرح خاصيات المكوِّنات هي القول أنَّها تسلك سلوك خاصيات HTML. بعبارةٍ أخرى، توفِّر الخاصيات خيارات الضبط للمكوِّن. فمثلًا، الشيفرة الآتية فيها المكوِّن Badge الذي يتوقع إرسال الخاصية name عند تهيئة المكوِّن: class Badge extends React.Component { render() { return <div>{this.props.name}</div>; } }; class BadgeList extends React.Component { render() { return (<div> <Badge name="Bill" /> <Badge name="Tom" /> </div>); } }; ReactDOM.render(<BadgeList />, document.getElementById('app')); داخل دالة التصيير للمكوِّن وعندما نستخدم المكوِّن ستُضاف الخاصية name إلى المكوِّن بنفس الطريقة التي نضيف فيها خاصية HTML إلى عنصر HTML (أي)؛ ثم ستستخدم الخاصية name من المكوِّن Badge (عبر this.props.name) كعقدة نصية للعقدة التي ستصيَّر عبر المكون Badge. هذا شبيهٌ بطريقة أخذ العنصر في HTML الخاصية value التي ستُستخدَم قيمتها لعرض نص داخل حقل الإدخال. طريقة أخرى للتفكير في خاصيات المكوِّنات هي تخيلها كأنها قيم لخيارات الضبط المُرسَلة إلى المكوِّن. فإذا نظرتَ إلى نسخة لا تحتوي على صيغة JSX من المثال السابق فسيبدو لك جليًا أنَّ خاصيات المكوِّن ما هي إلا كائنٌ يُمرَّر إلى الخاصية createElement()‎ (أي React.createElement(Badge, { name: "Bill" })‎): class Badge extends React.Component { render() { return React.createElement( "div", null, // null لم تُعرَّف خاصيات لذا ستكون القيمة this.props.name // كقيمة نصية this.prop.name استخدام ); } }; class BadgeList extends React.Component { render() { return React.createElement( "div", null, React.createElement(Badge, { name: "Bill" }), React.createElement(Badge, { name: "Tom" }) ); } }; ReactDOM.render(React.createElement(BadgeList, null), document.getElementById('app')); هذا شبيهٌ بطريقة ضبط الخاصيات مباشرةً على عقد React. لكن عند تمرير تعريف المكوِّن Badge إلى الدالة createElement()‎ بدلًا من قعدة، فستصبح الخاصيات props متاحةً على المكوِّن نفسه (أي this.props.name). تُمكِّننا خاصيات المكوِّنات من إعادة استخدام المكوِّن مع أي اسم. في الشيفرة التي ألقينا إليها نظرةً في هذا القسم، لاحظنا أنَّ المكوِّن BadgeList يستخدم مكونَي Badge مع كائن this.props خاصٌ بكلٍ واحدٍ منها. يمكننا التأكد من ذلك بعرض قيمة this.props عندما يُهيَّئ المكوِّن Badge: class Badge extends React.Component { render() { return <div>{this.props.name}{console.log(this.props)}</div>; } }; class BadgeList extends React.Component { render() { return (<div> <Badge name="Bill" /> <Badge name="Tom" /> </div>); } }; ReactDOM.render(<BadgeList />, document.getElementById('app')); نلاحظ أنَّ كل نسخة من مكوِّنات React تملك نسخةً خاصةً بها من خاصيةٍ اسمها props التي تكون كائن JavaScript فارغ، ثم سيُملَأ هذا الكائن عبر المكوِّن الأب باستخدام أي قيمة أو مرجعية في JavaScript، ثم ستُستخدَم هذه القيمة من المكوِّن أو تُمرَّر إلى المكونات الأبناء. ملاحظات في البيئات التي تستخدم ES5، لن نتمكن من تعديل الخاصية this.props لأنها كائنٌ مجمَّد (أي Object.isFrozen(this.props) === true;‎). يمكنك أن تعدّ this.props على أنها كائنٌ للقراءة فقط. إرسال الخاصيات props إلى مكوِّن تُرسَل الخاصيات إلى المكوِّن عند إضافة قيم شبيهة بخاصيات HTML إلى المكوِّن عند استخدمه وليس عند تعريفه، فمثلًا، سنجد في الشيفرة الآتية أنَّ المكوِّن Badge قد عُرِّفَ أولًا، ثم أرسلنا خاصيةً له وهي name="Bill"‎ أثناء استخدامه (أي عند تصيير ``): class Badge extends React.Component { render() { return <div>{this.props.name}</div>; } }; ReactDOM.render(<Badge name="Bill" />, document.getElementById('app')); أبقِ في ذهنك أنَّ بإمكاننا إرسال خاصية إلى المكوِّن في أي مكان يمكن أن يُستخدَم المكوِّن فيه. فعلى سبيل المثال، يبيّن المثال من القسم السابقة استخدام المكوِّن Badge والخاصية name ضمن المكوِّن BadgeList: class Badge extends React.Component { render() { return <div>{this.props.name}</div>; } }; class BadgeList extends React.Component { render() { return (<div> <Badge name="Bill" /> <Badge name="Tom" /> </div> ); } }; ReactDOM.render(<BadgeList />, document.getElementById('app')); ملاحظات يجب أن تُعدّ خاصيات المكوِّن غيرُ قابلةٍ للتعديل، ويمكن عدم تعديل الخاصيات المُرسَلة من مكوِّن آخر. فلو احتجتَ إلى تعديل قيمة خاصيات أحد المكوِّنات ثم إعادة تصييره، فلا تضبط الخاصيات باستخدام this.props.[PROP] = [NEW PROP]. الحصول على خاصيات المكوِّن كما ناقشنا في الدرس السابق، يمكن الوصول إلى إلى نسخة المكوِّن من أيٍّ من خيارات الضبط التي تستعمل دالةً عبر الكلمة المحجوزة this. ففي المثال الآتي استخدمنا الكلمة المحجوزة this للوصول إلى خاصيات props المكوِّن Badge من خيار الضبط render بكتابة this.props.name: class Badge extends React.Component { render() { return <div>{this.props.name}</div>; } }; ReactDOM.render(<Badge name="Bill" />, document.getElementById('app')); ليس من الصعب معرفة ما يحدث إذا ألقينا نظرةً على شيفرة JavaScript المحوَّلة من JSX: class Badge extends React.Component { render() { return React.createElement( "div", null, this.props.name ); } }; ReactDOM.render(React.createElement(Badge, { name: "Bill" }), document.getElementById('app')); أُرسِل الكائن { name: "Bill"‎ } إلى الدالة createElement()‎ إضافةً إلى مرجعيةٍ إلى المكوِّن Badge. القيمة { name: "Bill"‎ } ستُضبَط كخاصية للمكوِّن قابلةٌ للوصول من الكائن props، أي أنَّ this.props.name === "Bill"‎. ملاحظات تذكَّر أنَّ this.props للقراءة فقط، ولا يجوز ضبط الخاصيات باستخدام this.props.PROP = 'foo'‎. ضبط قيم افتراضية لخاصيات المكوِّن يمكن ضبط الخاصيات الافتراضية عند تعريف المكوِّن باستخدام خيار الضبط getDefaultProps. المثال الآتي يبيّن كيف عرَّفنا خيار ضبط افتراضي للمكوِّن Badge للخاصية name: class Badge extends React.Component { static defaultProps = { name:'John Doe' } render() { return <div>{this.props.name}</div>; } }; class BadgeList extends React.Component { render() { return (<div> <Badge /> <Badge name="Tom Willy" /> </div>); } }; ReactDOM.render(<BadgeList />, document.getElementById('app')); ستُضبَط الخاصيات الافتراضية على الكائن this.props إذا لم تُرسَل خاصيات إلى المكوِّن. يمكنك التحقق من ذلك بملاحظة أنَّ المكوِّن Badge الذي لم تُضبَط الخاصية name فيه ستأخذ القيمة الافتراضية 'John Doe'. خاصيات المكونات هي أكثر من مجرد سلاسل نصية قبل أن نلقي نظرةً على التحقق من الخاصيات، علينا أن نستوعب أولًا أنَّ خاصيات المكونات يمكن أن تكون أي قيمة صالحة في JavaScript. في المثال أدناه، سنضبط عدِّة خاصيات افتراضية تحتوي على مختلف قيم JavaScript: class MyComponent extends React.Component { static defaultProps = { propArray: [], propBool: false, propFunc: function(){}, propNumber: 5, propObject: {}, propString: 'string' } render() { return (<div> propArray: {this.props.propArray.toString()} <br /><br /> propFunc returns: {this.props.propFunc()} </div>) ; } }; ReactDOM.render(<MyComponent propArray={[1,2,3]} propFunc={function(){return 2;}} />, document.getElementById('app')); لاحظ كيف أعيدت الكتابة على الخاصيتين propArray و propObject مع قيم جديدة عند إنشاء نسخة من المكوِّن MyComponent. الفكرة الرئيسية من هذا القسم هو توضيح أنَّنا لسنا محدودين بالقيم النصية عند تمرير قيم للخاصيات. التحقق من خاصيات المكوِّنات لاستخدامٍ سليمٍ للخاصيات ضمن المكوِّنات، يجب أن نتحقق من قيمتها عند إنشاء نسخ المكوِّنات. عند تعريف خيار الضبط propTypes يمكننا أن نضبط كيف يجب أن تكون الخاصيات وكيف نتحقق منها. سنتحقق في المثال أدناه لنرى إن كانت الخاصيتان propArray و propObject من نوع البيانات الصحيح وسنرسلها إلى المكوِّن عند تهيئته: import PropTypes from 'prop-types'; class MyComponent extends React.Component { static propTypes = { propArray: PropTypes.array.isRequired, propFunc: PropTypes.func.isRequired, } render() { return (<div> propArray: {this.props.propArray.toString()} <br /><br /> propFunc returns: {this.props.propFunc()} </div>); } }; // لهذا المكوِّن خاصياتٌ خطأ ReactDOM.render(<MyComponent propArray={{test:'test'}} />, document.getElementById('app')); // لهذا المكوِّن خاصياتٌ صحيحة // ReactDOM.render(<MyComponent propArray={[1,2]} propFunc={function(){return 3;}} />, document.getElementById('app')); لم نرسل الخاصيات الصحيحة المُحدَّدة عبر propTypes لتوضيح أنَّ فعل ذلك سيؤدي إلى حدوث خطأ. ستؤدي الشيفرة السابقة إلى ظهور رسالة الخطأ الآتية: Warning: Failed propType: Invalid prop `propArray` of type `object` supplied to `MyComponent`, expected `array` Warning: Failed propType: Required prop `propFunc` was not specified in `MyComponent`. Uncaught TypeError: this.props.propFunc is not a function توفِّر React عددًا من المتحققات الداخلية (مثل PropTypes[VALIDATOR]‎) والتي سأشرحها بإيجاز فيما يلي، إضافةً إلى إمكانية إنشاء متحققات مخصصة. (انتقلت إلى مكتبة مختلفة*) المتحققات الأساسية من الأنواع React.PropTypes.string إذا اُستخدِمَت خاصيةٌ، فتحقق أنها سلسلة نصية React.PropTypes.bool إذا اُستخدِمَت خاصيةٌ، فتحقق أنها قيمة منطقية React.PropTypes.func إذا اُستخدِمَت خاصيةٌ، فتحقق أنها دالة React.PropTypes.number إذا اُستخدِمَت خاصيةٌ، فتحقق أنها عدد React.PropTypes.object إذا اُستخدِمَت خاصيةٌ، فتحقق أنها كائن React.PropTypes.array إذا اُستخدِمَت خاصيةٌ، فتحقق أنها مصفوفة React.PropTypes.any إذا اُستخدِمَت خاصيةٌ، فتحقق أنها من أي نوع من الأنواع متحققات القيم المطلوبة React.PropTypes.[TYPE].isRequired‎ إضافة ‎.isRequired إلى أي نوع من المتحققات سيؤدي إلى جعل الخاصية مطلوبةً (مثال ذلك propTypes:{propFunc:React.PropTypes.func.isRequired}) متحققات العناصر React.PropTypes.element الخاصية هي عنصر React. React.PropTypes.node أي شيء يمكن تصييره: الأرقام، أو السلاسل النصية، أو العناصر، أو مصفوفة تحتوي هذه الأنواع المتحققات المتعددة React.PropTypes.oneOf(['Mon','Fri'])‎ الخاصية هي أحد أنواع القيم المُحدَّدة React.PropTypes.oneOfType([React.PropTypes.string,React.PropTypes.number])‎ الخاصية هي كائن يمكن أن يكون أحد أنواع القيم المُحدَّدة متحققات المصفوفات والكائنات React.PropTypes.arrayOf(React.PropTypes.number)‎ الخاصية هي مصفوفة تحتوي على نوع واحد من القيم React.PropTypes.objectOf(React.PropTypes.number)‎ هي كائن يحتوي على أحد أنواع القيم React.PropTypes.instanceOf(People)‎ هي كائن يكون نسخةً من دالةٍ بانية معينة (كما في الكلمة المحجوزة instanceof) React.PropTypes.shape({color:React.PropTypes.string,size: React.PropTypes.number})‎ هي كائن يحتوي على خاصيات من أنواعٍ معينة المتحققات المخصصة function(props, propName, componentName){}‎ توفير دالة خاصة بك للتحقق ترجمة -وبتصرف- للفصل React Component Properties من كتاب React Enlightenment
  3. سيناقش هذا المقال إنشاء عقد React ‏(React Nodes) سواءً النصية (text nodes) أو عقد العناصر (element nodes) باستخدام JavaScript. ما هي عقدة React؟ النوع الأساسي أو القيمة التي تُنشَأ عند استخدام مكتبة React تسمى «عقدة React»، ويمكن تعريف عقدة React على أنها «تمثيل افتراضي خفيف وعديم الحالة وغير قابلٍ للتعديل لعقدة DOM» (لا تخف من الكلام السابق، هذا هو التعريف الرسمي، ما باليد حيلة :-) ). عقد React ليست عقد DOM حقيقية، وإنما تمثيل عن عقدة DOM محتملة، وهذا التمثيل يعد على أنه شجرة DOM افتراضية، ويُستخدم React لتعريف شجرة DOM الافتراضية باستخدام عقد React، والتي تُشكِّل البنية الأساسية لمكوِّنات React، والتي في نهاية المطاف ستُستخدَم لإنشاء بنية DOM الحقيقة وغير ذلك من البنى (مثل React Native). يمكن أن تُنشَأ عقد React باستخدام JSX أو JavaScript. سنلقي في هذا المقال نظرةً على إنشاء عقد React باستخدام JavaScript بمفردها دون استخدام JSX. إذ أرى أنَّ عليك فهم ما الذي تخفيه JSX وراء الكواليس لكي تفهم JSX فهمًا جيدًا. قد تحدثك نفسك بتجاوز هذا المقال لأنك تعرف مسبقًا أننا لن نستخدم JSX. لا تطعها! أقترح عليك أن تكمل قراءة هذا المقال لكي تعرف ما الذي تفعله لك JSX. ربما هذا أهم مقال في السلسلة ليتغلغل في ذهنك! إنشاء عقد العناصر سيُفضِّل المطورون في أغلبية الأوقات استخدام JSX مع React لإنشاء عقد العناصر، وبغض النظر عن ذلك، سنتعرّف في هذا المقال على طريقة إنشاء عقد React دون استخدام JSX باستعمال JavaScript فقط. أما المقال القادم فهو يناقش كيفية إنشاء عقد React باستخدام JSX. إنشاء عقد React سهلٌ جدًا فكل ما نحتاج له هو استخدام الدالة React.createElement(type, props, children)‎ وتمرير مجموعة من الوسائط إليها والتي تُعرِّف عقدة DOM الفعلية (مثالًا نضبط قيمة type إلى عنصر من عناصر HTML مثل <li> أو عنصر مخصص مثل <my-li>). وسائط الدالة React.createElement())‎ مشروحة أدناه: type (string | React.createClass())‎ يمكن أن تكون سلسلة نصية تُمثِّل عنصر HTML (أو عنصر HTML مخصص) أو نسخة من مكوِّنات React. props (null | object)‎ يمكن أن يكون null أو كائن يحتوي على خاصيات وقيم مرتبطة بها. children (null | string | React.Component | React.createElement())‎ يمكن أن تكون null أو سلسلة نصية التي ستحوّل إلى عقدة نصية، أو نسخة من React.Component‎ أو React.createElement()‎. سأستخدم في المثال الآتي الدالة React.createElement()‎ لإنشاء تمثيل في شجرة DOM الافتراضية لعقدة العنصر <li> والتي تحتوي على عقدة نصية فيها one (أي عقدة نصية في React) ومُعرِّف id يساوي li1. var reactNodeLi = React.createElement('li', {id:'li1'}, 'one'); لاحظ أنَّ أول وسيط يُعرِّف ما هو عنصر HTML الذي نريد تمثيله، ويعُرِّف الوسيط الثاني ما هي الخاصيات التي ستُضبَط على العناصر <li>، وأخيرًا سيُعرِّف الوسيط الثالث ما هي العقدة الموجودة داخل العنصر، وفي هذه الحالة وضعنا ببساطة عقدةً نصية (أي 'one') داخل <li>. لاحظ أنَّ آخر وسيط الذي سيصبح ابنًا للعقدة المُنشَأة يمكن أن يكون عقدة React نصية، أو عقدة عنصر في React، أو نسخة من مكوِّن React. كل ما فعلناه إلى الآن هو إنشاء عقدة عنصر في React تحتوي على عقدة React نصية، والتي خزناها في المتغير reactNodeLi، ولإنشاء شجرة DOM الوهمية فعلينا تصيير عقدة عنصر React في شجرة DOM الحقيقية، ولفعل ذلك سنستخدم الدالة ReactDOM.render()‎: ReactDOM.render(reactNodeLi, document.getElementById('app')); إذا أردنا وصف الشيفرة السابقة ببساطة، فهي تتضمن: إنشاء شجرة DOM افتراضية مبنية من عقدة عنصر React المُمرَّرة (أي reactNodeLi)، استخدام شجرة DOM الافتراضية لإعادة إنشاء فرع من شجرة DOM الحقيقية، إضافة الفرع المُنشَأ (أي <li id="li1">one</li>) إلى شجرة DOM الحقيقة كأحد أبناء العنصر <div id="app"></div>. بعبارةٍ أخرى، ستتغير شجرة DOM لمستند HTML من: <div id="app"></div> إلى: <div id="app"> // data-reactid قد أضافت الخاصية React لاحظ أنَّ <li id="li1" data-reactid=".0">one</li> </div> بيّن المثال السابق استخدامًا بسيطًا للدالة React.createElement()‎. يمكن للدالة React.createElement()‎ إنشاء بنى معقدة أيضًا. فعلى سبيل المثال سنستخدم الدالة React.createElement()‎ لإنشاء فرع من عقد React يُمثِّل قائمة غير مرتبة من العناصر (أي <ul>): // React في <li> إنشاء عقد عناصر var rElmLi1 = React.createElement('li', {id:'li1'}, 'one'); var rElmLi2 = React.createElement('li', {id:'li2'}, 'two'); var rElmLi3 = React.createElement('li', {id:'li3'}, 'three'); // السابقة إليها <li> وإضافة عناصر React في <ul> إنشاء عقدة العنصر var reactElementUl = React.createElement('ul', {className: 'myList'}, rElmLi1, rElmLi2, rElmLi3); قبل تصيير القائمة غير المرتبة إلى شجرة DOM، أرى إخبارك أنَّه يمكن تبسيط الشيفرة السابقة باستخدام React.createElement()‎ بدلًا من المتغيرات. هذا المثال يوضِّح كيف يمكن تعريف الهيكلية باستخدام JavaScript: var reactElementUl = React.createElement( 'ul', { className: 'myList' }, React.createElement('li', {id: 'li1'},'one'), React.createElement('li', {id: 'li2'},'two'), React.createElement('li', {id: 'li3'},'three') ); عند تصيير الشيفرة السابقة إلى DOM فستبدو شيفرة HTML الناتجة كما يلي: <ul class="myList" data-reactid=".0"> <li id="li1" data-reactid=".0.0">one</li> <li id="li2" data-reactid=".0.1">two</li> <li id="li3" data-reactid=".0.2">three</li> </ul> يجب أن يكون واضحًا أنَّ عقد React هي كائنات JavaScript في شجرةٍ تمثِّل عقد DOM حقيقية داخل شجرة DOM الافتراضية. ثم تُستخدَم شجرة DOM الافتراضية لتعديل شجرة DOM الحقيقية داخل صفحة HTML. ملاحظات يمكن أن يكون الوسيط type المُمرَّر إلى الدالة React.createElement()‎ سلسلةً نصية تُشير إلى عنصر من عناصر HTML القياسية (مثل 'li' الذي يمُثِّل العنصر <li>)، أو عنصرًا مخصصًا (مثل 'foo-bar' الذي يُمثِّل العنصر <foo-bar>)، أو نسخة من مكوِّن في React (أي نسخة من React.Component). هذه هي قائمة عناصر HTML التي تدعمها React (أي تمرير هذه العناصر كسلسلة نصية مكان المعامل type إلى الدالة createElement()‎ سيؤدي إلى إنشاء عنصر HTML القياسي في DOM): a abbr address area article aside audio b base bdi bdo big blockquote body br button canvas caption cite code col colgroup data datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 head header hgroup hr html i iframe img input ins kbd keygen label legend li link main map mark menu menuitem meta meter nav noscript object ol optgroup option output p param picture pre progress q rp rt ruby s samp script section select small source span strong style sub summary sup table tbody td textarea tfoot th thead time title tr track u ul var video wbr تصيير (rendering) العناصر إلى شجرة DOM توفِّر React الدالة React.render()‎ من الملف react-dom.js التي يمكن أن تُستخدَم لتصيير عقد عناصر React إلى شجرة DOM. لقد رأينا الدالة render()‎ مستخدمةً في هذا المقال. سنستخدم في المثال الآتي الدالة ReactDOM.render()‎ وستُصيّر عقد العناصر '<li>' و '<foo-bar>' إلى شجرة DOM: // حقيقي DOM والتي تُمثِّل عقدة عنصر React عقدة var HTMLLi = React.createElement('li', {className:'bar'}, 'foo'); // مخصص HTML والتي تُمثِّل عقد عنصر React عقدة var HTMLCustom = React.createElement('foo-bar', {className:'bar'}, 'foo'); // <div id="app1"></div> إلى HTMLLi تصيير عقدة العنصر ReactDOM.render(HTMLLi, document.getElementById('app1')); // <div id="app2"></div> إلى HTMLCustom تصيير عقدة العنصر ReactDOM.render(HTMLCustom, document.getElementById('app2')); بعد تصيير العناصر إلى شجرة DOM، فسيبدو مستند HTML المُحدَّث كما يلي: <body> <div id="app1"><li class="bar" data-reactid=".0">foo</li></div> <div id="app2"><foo-bar classname="bar" children="foo" data-reactid=".1">foo</foo-bar></div> </body> الدالة ReactDOM.render()‎ هي الطريقة المبدئية التي تمكننا من نقل عقد العناصر في React إلى شجرة DOM الوهمية، ثم إلى شجرة DOM الحقيقية. ملاحظات ستستبدل أي عقد DOM داخل عناصر DOM التي سيُصيَّر إليها (أي أنها ستُحذَف ويحلّ المحتوى الجديد محلها). الدالة ReactDOM.render()‎ لا تعدِّل عقدة عنصر DOM الذي تُصيّر React إليه، وإنما تتطلَّب React ملكيةً كاملةً للعقدة النصية عند التصيير، فلا يجدر بك إضافة أبناء أو إزالة أبناء من العقدة التي تضيف React فيها المكوِّن أو العقدة. التصيير إلى شجرة HTML DOM هو أحد الخيارات فقط، فهنالك خياراتٌ أخرى ممكنة، فمثلًا تستطيع التصيير إلى سلسلة نصية (أي ReactDOMServer.renderToString()‎) في جهة الخادم. إعادة تصيير نفس عنصر DOM سيؤدي إلى تحديث العقد الأبناء الحاليين إذا حدث تغييرٌ فيها، أو إذا أُضيفت عقدة عنصر ابن جديدة. تعريف خاصيات العقد الوسيط الثاني الذي يُمرَّر إلى الدالة React.createElement(type, props, children)‎ هو كائنٌ يحتوي على خاصيات ذات أسماء ترتبط بها قيم. تمتلك الخاصيات عدِّة أدوار: يمكن أن تتحول إلى خاصيات HTML. فلو طابقت إحدى الخاصيات خاصية HTML معروفة، فستُضاف إلى عنصر HTML النهائي في DOM. الخاصيات المُمرَّرة إلى createElement()‎ ستصبح قيمًا مخزنةً في الكائن prop كنسخة من React.createElement()‎ (أي ‎[INSTANCE].props.[NAME_OF_PROP]). تستخدم الخاصيات بكثرة لإدخال قيم إلى المكوِّنات. تمتلك بعض الخاصيات تأثيراتٍ جانبية (مثل key و ref و dangerouslySetInnerHTML). يمكنك أن تتخيل الخاصيات على أنها خيارات ضبط لعقد React، ويمكنك أن تتخيلها كخاصيات HTML. سنُعرِّف في المثال الآتي عقدة العنصر <li> مع خمس خاصيات، إحدى تلك الخاصيات غير قياسية في HTML (أقصد بها foo:'bar'‎) وبقية الخاصيات معروفة في HTML: var reactNodeLi = React.createElement('li', { foo:'bar', id:'li1', // للإشارة إلى صنف الأنماط في جافاسكربت className لاحظ استخدام الخاصية className: 'blue', 'data-test': 'test', 'aria-test': 'test', // CSS لضبط خاصية backgroundColor لاحظ استخدام الخاصية style: {backgroundColor: 'red'} }, 'text' ); ستبدو الشيفرة المُصيَّرة إلى صفحة HTML كما يلي: <li id="li1" data-test="test" class="blue" aria-test="test" style="background-color:red;" data-reactid=".0">text</li> ما عليك إدراكه من المثال السابق أنَّ خاصيات HTML القياسية فقط، وخاصيات data-*‎ و aria-*‎ هي الخاصيات الوحيدة التي تنتقل إلى شجرة DOM الحقيقية من شجرة DOM الافتراضية. لاحظ أنَّ الخاصية foo لن تظهر في شجرة DOM الحقيقية، فهذه الخاصية غير القياسية ستكون متاحةً في نسخة الكائن li لعقدة React (أي reactNodeLi.props.foo): var reactNodeLi = React.createElement('div',{ foo:'bar', id:'li1', className: 'blue', 'data-test': 'test', 'aria-test': 'test', style: {backgroundColor: 'red'} }, 'text' ); console.log(reactNodeLi.props.foo); // bar ReactDOM.render(reactNodeLi, document.getElementById('app')); ليس استخدام خاصيات React مقتصرًا على تحويلها إلى خاصيات HTML حقيقية، لكن يمكن أن تلعب دورًا في إعدادات الضبط المُمرَّرة إلى مكونات React. هذا الجانب من جوانب استخدام الخاصيات سيغطى في مقالٍ خاصٍ به، أما الآن فكل ما علينا إدراكه هو أنَّ تمرير خاصية إلى عقدة React هو أمرٌ يختلف عن تعريف خاصية في مكوِّن لكي تُستخدم لمدخلات ضبط ضمن ذاك المكوِّن. ملاحظات ترك قيمة إحدى الخاصيات فارغةً سيؤدي إلى جعل قيمتها مساويةً إلى true (أي id=""‎ ستصبح id="true"‎، و test ستصبح test="true"‎). إذا كانت خاصيةٌ ما مكررةً فستؤخذ آخر قيمة لها. إذا مررت خاصيات إلى عناصر HTML التي ليست موجودة في مواصفة HTML فلن تعرضها React. أما إذا استخدمتَ عناصر HTML مخصصة (أي أنها ليست قياسية) فستُضاف الخاصيات التي ليست موجودةً في مواصفة HTML إلى العناصر المخصصة (فمثلًا ‎<x-my-component custom-attribute="foo" />‎). الخاصية class يجب أن تكتب className. الخاصية for يجب أن تكتب htmlFor. الخاصية style تقبل إشارةً مرجعيةً إلى كائنٍ يحتوي على خاصيات CSS بالصيغة المتعارف عليها في JavaScript (أي background-color تصبح backgroundColor). خاصيات النماذج في HTML (مثل <input> أو <textarea></textarea> …إلخ.) المُنشَأة كعقد React ستدعم خاصيات التي يمكن تغييرها عبر تفاعل المستخدم مع العنصر؛ وهذه الخاصيات هي value و checked و selected. توفِّر React الخاصيات key و ref و dangerouslySetInnerHTML) التي لا تتوافر في DOM وتأخذ دورًا فريدًا. يجب أن تكتب جميع الخاصيات مع حذف الشرطة - وجعل أول حرف يليها مكتوبًا بحرفٍ كبير، أي أنَّ الخاصية accept-charset ستُكتَب acceptCharset. هذه هي خاصيات HTML التي تدعمها تطبيقات React: accept acceptCharset accessKey action allowFullScreen allowTransparency alt async autoComplete autoFocus autoPlay capture cellPadding cellSpacing challenge charSet checked classID className colSpan cols content contentEditable contextMenu controls coords crossOrigin data dateTime default defer dir disabled download draggable encType form formAction formEncType formMethod formNoValidate formTarget frameBorder headers height hidden high href hrefLang htmlFor httpEquiv icon id inputMode integrity is keyParams keyType kind label lang list loop low manifest marginHeight marginWidth max maxLength media mediaGroup method min minLength multiple muted name noValidate nonce open optimum pattern placeholder poster preload radioGroup readOnly rel required reversed role rowSpan rows sandbox scope scoped scrolling seamless selected shape size sizes span spellCheck src srcDoc srcLang srcSet start step style summary tabIndex target title type useMap value width wmode wrap تضمين أنماط CSS السطرية في عقد العناصر لتطبيق أنماط CSS السطرية (inline CSS styles) على عقدة React، فعليك تمرير كائن يحتوي على خاصيات CSS والقيم المرتبطة بها إلى الخاصية style. على سبيل المثال، سنمرر مرجعيةً إلى الكائن inlineStyles إلى الخاصية style: var inlineStyles = {backgroundColor: 'red', fontSize: 20}; var reactNodeLi = React.createElement('div', {style: inlineStyles}, 'styled') ReactDOM.render(reactNodeLi, document.getElementById('app1')); ستبدو شيفرة HTML الناتجة شبيهةً بما يلي: <div id="app1"> <div style="background-color:red;font-size:20px;" data-reactid=".0">styled</div> </div> هنالك أمران عليك الانتباه إليهما في الشيفرة السابقة: لم نضف الواحدة "px" إلى الخاصية fontSize لأنَّ React أضافته عوضًا عنّا. عند كتابة خاصيات CSS في JavaScript، يجب حذف الشرطة وجعل الحرف الذي يليها مكتوبًا بحرفٍ كبير (أي backgroundColor بدلًا من background-color). ملاحظات يجب أن تبدأ السابقات الخاصة بالمتصفحات (باستثناء ms) بحرفٍ كبير، لهذا السبب تبدأ الخاصية WebkitTransition بحرف W كبير (ولم أعد أنصح بإضافة هذه الخاصيات إلا لدعم المتصفحات القديمة جدًا). يجب ألا يفاجئك استخدام الأحرف الكبيرة في أسماء خاصيات CSS بدلًا من الشرطات، فهذه هي الطريقة المتبعة للوصول إلى تلك الخاصيات في شجرة DOM عبر JavaScript (كما في document.body.style.backgroundImage). عند تحديد قيمة بواحدة البكسل، فستضيف React السلسلة النصية "px" تلقائيًا بعد القيم الرقمية باستثناء الخاصيات الآتية: columnCount fillOpacity flex flexGrow flexShrink fontWeight lineClamp lineHeight opacity order orphans strokeOpacity widows zIndex zoom استخدام مصانع العناصر المُضمَّنة في React توفِّر React اختصارات مضمَّنة لإنشاء عقد عناصر HTML شائعة الاستخدام. تسمى هذه الاختصارات بمصانع عقد React. قبل الإصدار 16.0 من مكتبة React.js، كانت هذه المصانع مضمنة في المكتبة نفسها، لكن بعد الإصدار 16.0 فقد أسقط الدعم عنها، وظهرت وحدة باسم react-dom-factories) التي تتيح استخدام المصانع التي كانت مضمنة في React.js مع الإصدارات الجديدة من المكتبة. مصنع لعقد React هو دالةٌ تولِّد ReactElement مع قيمة معينة لخاصية type. باستخدام المصنع المضمن React.DOM.li()‎، يمكننا إنشاء عقدة العنصر <li>one</li> في React كما يلي: // DOM.li(props, children); import DOM from 'react-dom-factories'; var reactNodeLi = DOM.li({id:'li1'}, 'one'); وذلك بدلًا من استخدام: // React.createElement(type, prop, children) var reactNodeLi = React.createElement('li', null, 'one'); هذه قائمة بجميع المصانع المُضمَّنة في React: a,abbr,address,area,article,aside,audio,b,base,bdi,bdo,big,blockquote,body,br,button, canvas,caption,cite,code,col,colgroup,data,datalist,dd,del,details,dfn,dialog,div,dl, dt,em,embed,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,head,header,hgroup, hr,html,i,iframe,img,input,ins,kbd,keygen,label,legend,li,link,main,map,mark,menu, menuitem,meta,meter,nav,noscript,object,ol,optgroup,option,output,p,param,picture, pre,progress,q,rp,rt,ruby,s,samp,script,section,select,small,source,span,strong, style,sub,summary,sup,table,tbody,td,textarea,tfoot,th,thead,time,title,tr,track, u,ul,var,video,wbr,circle,clipPath,defs,ellipse,g,image,line,linearGradient,mask, path,pattern,polygon,polyline,radialGradient,rect,stop,svg,text,tspa ملاحظات إذا كنتَ تستخدم JSX فقد لا تحتاج إلى استخدام المصانع إطلاقًا. يمكن إنشاء مصانع مخصصة إن كان ذلك ضروريًا. تعريف أحداث عقد React يمكن إضافة الأحداث إلى عقد React كما لو أردنا إضافة الأحداث إلى عقد DOM. سنضيف في المثال الآتي حدثين بسيطين click و mouseover إلى عقدة <div>: var mouseOverHandler = function mouseOverHandler() { console.log('you moused over'); }; var clickhandler = function clickhandler() { console.log('you clicked'); }; var reactNode = React.createElement( 'div', { onClick: clickhandler, onMouseOver: mouseOverHandler }, 'click or mouse over' ); ReactDOM.render(reactNode, document.getElementById('app')); لاحظ أنَّ اسم الخاصية لأحداث React تبدأ بالحرفين 'on' وتُمرِّر في الكائن props المُمرَّر إلى الدالة ReactElement()‎. تُنشئ React ما تسميه SyntheticEvent لكل حدث، والذي يحتوي على تفاصيل الحدث، وهي شبيهة بتفاصيل الأحداث المُعرَّفة في DOM. يمكن مثلًا الاستفادة من نسخة الكائن SyntheticEvent بتمريره إلى معالجات الأحداث، وسأفعل ذلك في المثال الآتي لتسجيل تفاصيل الحدث SyntheticEvent. var clickhandler = function clickhandler(SyntheticEvent) { console.log(SyntheticEvent); }; var reactNode = React.createElement( 'div', { onClick: clickhandler}, 'click' ); ReactDOM.render(reactNode, document.getElementById('app')); كل كائن من النوع syntheticEvent يملك الخاصيات الآتية: bubbles cancelable DOMEventTarget currentTarget defaultPrevented eventPhase isTrusted DOMEvent nativeEvent void preventDefault()‎ isDefaultPrevented()‎ void stopPropagation()‎ isPropagationStopped()‎ DOMEventTarget target timeStamp type هنالك خاصيات إضافية متوافرة اعتمادًا على نوع أو تصنيف الحدث المستخدم. فعلى سبيل المثال، الحدث onClick يحتوي على الخاصيات الآتية: altKey button buttons clientX clientY ctrlKey getModifierState(key‎)‎ metaKey pageX pageY DOMEventTarget relatedTarget screenX screenY shiftKey يحتوي الجدول الآتي على خصائص syntheticEvent المتاحة لكل تصنيف من تصنيفات الأحداث: نوع الحدث الأحداث خاصيات متعلقة به الحافظة onCopy onCut onPaste DOMDataTransfer clipboardData التركيب onCompositionEnd onCompositionStart onCompositionUpdate data لوحة المفاتيح onKeyDown onKeyPress onKeyUp altKey charCode ctrlKey getModifierState(key)‎ key keyCode locale location metaKey repeat shiftKey which التركيز onChange onInput onSubmit DOMEventTarget relatedTarget النماذج OnFocus onBlur الفأرة onClick onContextMenu onDoubleClick onDrag onDragEnd onDragEnter onDragExit onDragLeave onDragOver onDragStart onDrop onMouseDown onMouseEnter onMouseLeave onMouseMove onMouseOut onMouseOver onMouseUp altKey button buttons clientX clientY ctrlKey getModifierState(key)‎ metaKey pageX pageY DOMEventTarget relatedTarget screenX screenY shiftKey الاختيار onSelect اللمس onTouchCancel onTouchEnd onTouchMove onTouchStart AltKey DOMTouchList changedTouches ctrlKey getModifierState(key)‎ metaKey shiftKey DOMTouchList targetTouches DOMTouchList touches واجهة المستخدم onScroll detail DOMAbstractView view الدولاب onWheel deltaMode deltaX deltaY deltaZ الوسائط onAbort onCanPlay onCanPlayThrough onDurationChange onEmptied onEncrypted onEnded onError onLoadedData onLoadedMetadata onLoadStart onPause onPlay onPlaying onProgress onRateChange onSeeked onSeeking onStalled onSuspend onTimeUpdate onVolumeChange onWaiting الصور onLoad onError الحركات onAnimationStart onAnimationEnd onAnimationIteration animationName pseudoElement elapsedTime الانتقالات onTransitionEnd propertyName pseudoElement elapsedTime ملاحظات توحِّد React التعامل مع الأحداث لكي تسلك سلوكًا متماثلًا في جميع المتصفحات. تنطلق الأحداث في React في مرحلة الفقاعات (bubbling phase). لإطلاق حدث في مرحلة الالتقاط (capturing phase) فأضف الكلمة "Capture" إلى اسم الحدث، أي أنَّ الحدث onClick سيصبح onClickCapture. إذا احتجتَ إلى تفاصيل كائن الأحداث المُنشَأ من المتصفح، فيمكنك الوصول إليه باستخدام الخاصية nativeEvent في كائن SyntheticEvent المُمرَّر إلى دالة معالجة الأحداث في React. لا تربط React الأحداث إلى العقد نفسها، وإنما تستخدم «تفويض الأحداث» (event delegation). يجب استخدام e.stopPropagation()‎ أو e.preventDefault()‎ يدويًا لإيقاف انتشار الأحداث بدلًا من استخدام return false;‎. لا تدعم React جميع أحداث DOM، لكن ما يزال بإمكاننا الاستفادة منها باستخدام توابع دورة الحياة في React. ترجمة وبتصرف للفصل React Nodes‎ من كتاب React Enlightenment table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; }
  4. تعلمنا في المقال السابق كيفية إنشاء عقد React باستخدام شيفرات JavaScript العادية، وسنلقي في هذا المقال نظرةً على إنشاء عقد React باستخدام صيغة JSX. إن لم تقرأ المقال السابق، عقد React، فانتقل إليه أولًا لقراءته ثم عد إلى هذا المقال. سنستخدم صيغة JSX بعد هذا المقال في بقية الكتاب ما لم نستعمل الدالة React.createElement()‎ لأغراض التوضيح. بعد انتهائك من قراءة هذا المقال، يمكنك الاطلاع على شرح تفصيلي لجميع ميزات JSX في موسوعة حسوب. ما هي صيغة JSX؟ JSX هي صيغة شبيهة بصيغة XML أو HTML التي تستخدمها React لتوسعة ECMAScript لكي نستطيع كتابة تعابير شبيهة بلغة XML أو HTML داخل شيفرة JavaScript. هذه الصيغة مهيئة للعمل مع برمجيات التحويل مثل Babel لتحويل النص الشبيه بشيفرات HTML في ملفات JavaScript إلى كائنات JavaScript التي تستطيع محركات JavaScript تفسيرها. أساسيًا، عند استخدمنا لصيغة JSX يمكننا أن نكتب بنى شبيهة ببنى HTML (أي هياكل من العناصر كما في DOM) بنفس الملف الذي تكتب فيه شيفرة JavaScript، ثم يحوِّل Babel هذه التعابير إلى شيفرة JavaScript حقيقية. وعلى عكس ما جرت عليه العادة بوضع شيفرات JavaScript داخل HTML، تسمح لنا صيغة JSX بوضع شيفرات HTML داخل JavaScript. تسمح لنا JSX بكتابة شيفرة JavaScript الآتية: var nav = ( <ul id="nav"> <li><a href="#">Home</a></li> <li><a href="#">About</a></li> <li><a href="#">Clients</a></li> <li><a href="#">Contact Us</a></li> </ul> ); وسيحولها Babel إلى الشيفرة الآتية: var nav = React.createElement( "ul", { id: "nav" }, React.createElement( "li", null, React.createElement( "a", { href: "#" }, "Home" ) ), React.createElement( "li", null, React.createElement( "a", { href: "#" }, "About" ) ), React.createElement( "li", null, React.createElement( "a", { href: "#" }, "Clients" ) ), React.createElement( "li", null, React.createElement( "a", { href: "#" }, "Contact Us" ) ) ); لذا يمكننا أن نعدّ JSX على أنها اختصار لاستدعاء React.createElement()‎. فكرة دمج شيفرات HTML و JavaScript في نفس الملف هي فكرة مثيرة للجدال، حاول أن تتجاهل ذلك وتستعملها إذا وجدتها مفيدةً، وإذا لم تجدها مفيدةً فاكتب شيفرة JavaScript الضرورية لإنشاء عقد React يدويًا، فالخيار عائد لك تمامًا. أرى شخصيًا أنَّ JSX توفِّر صيغة مختصرة ومألوفةً لتعريف بنى هيكلية مع الخاصيات اللازمة لها والتي لا تتطلب تعلّم صيغة قوالب خاصة أو تتطلب الخروج من شيفرة JavaScript؛ وكلا الميزتين السابقتين مفيدتان عند إنشاء التطبيقات الكبيرة. من الواضح أنَّ صيغة JSX أسهل قراءةً وكتابةً من الأهرامات الكبيرة من استدعاءات دوال JavaScript مع تعريف الكائنات داخلها (قارن الشيفرتين السابقتين في هذا القسم لتتأكد من ذلك). أضف إلى ذلك أنَّ فريق تطوير React يعتقد أنَّ JSX أفضل لتعريف واجهات المستخدم من أحد حلول القوالب (مثل Handlebars): «إنَّ الشيفرة البرمجية والبنى الهيكلية مرتبطتان مع بعضهما بعضًا ارتباطًا وثيقًا. أضف إلى ذلك أنَّ الشيفرات البرمجية معقدة جدًا واستخدام لغات القوالب ستجعل الأمر صعبًا جدًا. وجدنا أنَّ أفضل حل لهذه المشكلة هو توليد شيفرات HTML مباشرةً من شيفرة JavaScript، وبهذا نستطيع استخدام كامل قدرات لغة برمجية حقيقية لبناء واجهات المستخدم.» ملاحظات لا تفكر في JSX على أنها لغة قوالب، وإنما هي صيغة JavaScript خاصة يمكن تصريفها، أي أنَّ JSX هي صيغة تسمح بتحويل بنى شبيهة ببنى HTML إلى شيفرات JavaScript. أداة Babel هي الأداة التي اختارها فريق تطوير React لتحويل شيفرات ES*‎ و JSX إلى شيفرة ES5. يمكنك معرفة المزيد عن Babel بقراءة توثيقه الرسمي. باستخدام صيغة JSX: أصبح بإمكان الأشخاص غير المتخصصين تقنيًا فهم وتعديل الأجزاء المطلوبة. فيجد مطورو CSS والمصممون صيغة JSX أكثر ألفةً من شيفرة JavaScript. يمكنك استثمار كامل قدرات JavaScript في HTML وتتجنب تعلّم أو استخدام لغة خاصة بالقوالب. لكن اعلم أن JSX ليس محرّك قوالب، وإنما صيغة تصريحية للتعبير عن البنية الهيكلية الشجرية لمكونات UI. سيجد المُصرِّف (compiler) أخطاءً في شيفرة HTML الخاصة بك كنتَ ستغفل عنها. تحث صياغة JSX على فكر استخدام الأنماط السطرية (inline styles) وهو أمرٌ حسن. صيغة JSX منفصلة عن React، ولا تحاول JSX أن تتفق مع أي مواصفة تخص HTML أو XML، وإنما هي مصممة كميزة ECMAScript وهي تشبه HTML ظاهريًا فقط، وتجري كتابة مواصفة JSX كمسودة لكي تُستخدَم من أي شخص كإضافة لصياغة ECMAScript. في صيغة JSX، يجوز استخدام ‎<foo-bar />‎ بمفرده، لكن لا يجوز استخدام <foo-bar>. أي عليك إغلاق جميع الوسوم دومًا. إنشاء عقد React باستخدام JSX بإكمالنا لما تعلمناه في المقال الماضي، يجب أن تكون قادرًا على إنشاء عقد React باستخدام الدالة React.createElement()‎. فيمكن مثلًا استخدام هذه الدالة لإنشاء عقد React التي تُمثِّل عقدًا حقيقيةً في HTML DOM إضافةً إلى عقدٍ مخصصة. سأريك حالتي الاستخدام السابقتين في المثال الآتي: // حقيقية HTML DOM التي تمثِّل عقدة React عقدة var HTMLLi = React.createElement('li', {className:'bar'}, 'foo'); // مخصصة HTML DOM التي تمثِّل عقدة React عقدة var HTMLCustom = React.createElement('foo-bar', {className:'bar'}, 'foo'); لاستخدام صيغة JSX بدلًا من React.createElement()‎ لإنشاء هذه العقد، فكل ما علينا فعله هو تبديل استدعاءات الدالة React.createElement()‎ إلى وسوم شبيهة بوسوم HTML التي تُمثِّل عناصر HTML التي تريد إنشاء شجرة DOM الافتراضية بها. يمكننا أن نكتب الشيفرة السابقة بصيغة JSX كما يلي: // حقيقية HTML DOM التي تمثِّل عقدة React عقدة var HTMLLi = <li className="bar">foo</li>; // مخصصة HTML DOM التي تمثِّل عقدة React عقدة var HTMLCustom = <foo-bar className="bar" >foo</foo-bar>; لاحظ أنَّ صيغة JSX غير محتواة في سلسلة نصية في JavaScript، وتكتبها كأنك تكتب الشيفرة داخل ملف ‎.htmlعادي، وكما ذكرنا لعدّة مرات، ستحوِّل صيغة JSX إلى استدعاءات للدالة React.createElement()‎ باستخدام Babel: // حقيقية HTML DOM التي تمثِّل عقدة React عقدة var HTMLLi = <li className="bar">foo</li>; // مخصصة HTML DOM التي تمثِّل عقدة React عقدة var HTMLCustom = <foo-bar className="bar" >foo</foo-bar>; ReactDOM.render(HTMLLi, document.getElementById('app1')); ReactDOM.render(HTMLCustom, document.getElementById('app2')); إذا أردتَ تفحص شيفرة HTML الناتجة عن المثال السابق، فستجد أنَّها شبيهة بهذه الشيفرة: <body> <div id="app1"><li class="bar" data-reactid=".0">foo</li></div> <div id="app2"><foo-bar class="bar" data-reactid=".1">foo</foo-bar></div> </body> إنشاء عقد React باستخدام JSX سهل جدًا كما لو كنتَ تكتب شيفرة HTML داخل ملفات JavaScript. ملاحظات تدعم JSX صيغة إغلاق الوسوم الخاصة بلغة XML، لذا يمكنك أن تهمل إضافة وسم الإغلاق إذا لم يمتلك العنصر أي أبناء. إذا مررت خاصيات إلى عناصر HTML التي ليست موجودة في مواصفة HTML فلن تعرضها React. أما إذا استخدمتَ عناصر HTML مخصصة (أي أنها ليست قياسية) فستُضاف الخاصيات التي ليست موجودةً في مواصفة HTML إلى العناصر المخصصة (فمثلًا ‎<``x-my-component custom-attribute="foo" />‎). الخاصية class يجب أن تكتب className. الخاصية for يجب أن تكتب htmlFor. الخاصية style تقبل إشارةً مرجعيةً إلى كائنٍ يحتوي على خاصيات CSS بالصيغة المتعارف عليها في JavaScript (أي background-color تصبح backgroundColor). جميع الخاصيات مكتوبة بالصيغة المستخدمة في JavaScript، وذلك بحذف الشرطة وجعل الحرف الذي يليها كبيرًا (أي accept-charset ستصبح acceptCharset). لتمثيل عناصر HTML احرص على كتابة الوسوم بحرفٍ صغير. هذه هي قائمة بجميع خاصيات HTML التي تدعمها React: accept acceptCharset accessKey action allowFullScreen allowTransparency alt async autoComplete autoFocus autoPlay capture cellPadding cellSpacing challenge charSet checked classID className colSpan cols content contentEditable contextMenu controls coords crossOrigin data dateTime default defer dir disabled download draggable encType form formAction formEncType formMethod formNoValidate formTarget frameBorder headers height hidden high href hrefLang htmlFor httpEquiv icon id inputMode integrity is keyParams keyType kind label lang list loop low manifest marginHeight marginWidth max maxLength media mediaGroup method min minLength multiple muted name noValidate nonce open optimum pattern placeholder poster preload radioGroup readOnly rel required reversed role rowSpan rows sandbox scope scoped scrolling seamless selected shape size sizes span spellCheck src srcDoc srcLang srcSet start step style summary tabIndex target title type useMap value width wmode wrap تصيير JSX إلى DOM يمكن استخدام الدالة ReactDOM.render()‎ لتصيير تعابير JSX إلى DOM. في الواقع، كل ما تفعله Babel هو تحوي JSX إلى React.createElement()‎. في المثال الآتي، سنُصيّر العنصر <li> والعنصر المخصص <foo-bar> إلى DOM باستخدام تعابير JSX: // حقيقية HTML DOM التي تمثِّل عقدة React عقدة var HTMLLi = <li className="bar">foo</li>; // مخصصة HTML DOM التي تمثِّل عقدة React عقدة var HTMLCustom = <foo-bar className="bar" >foo</foo-bar>; // <div id="app1"></div> إلى HTMLLi باسم React تصيير عقدة ReactDOM.render(HTMLLi, document.getElementById('app1')); // <div id="app2"></div> إلى HTMLCustom باسم React تصيير عقدة ReactDOM.render(HTMLCustom, document.getElementById('app2')); ستبدو شيفرة HTML كما يلي بعد تصيير العناصر إلى DOM: <body> <div id="app1"><li class="bar" data-reactid=".0">foo</li></div> <div id="app2"><foo-bar classname="bar" children="foo" data-reactid=".1">foo</foo-bar></div> </body> تذكّر أنَّ Babel يأخذ JSX ويحولها إلى عقد React (أي استدعاءات الدالة React.createElement()‎) ثم باستخدام هذه العقد المُنشَأة باستخدام React (في شجرة DOM الافتراضية) سنُصيّر العناصر إلى شجرة DOM الحقيقية. ما تفعله الدالة ReactDOM.render()‎ هو تحويل عقد React إلى عقد DOM حقيقة ثم إضافتها إلى مستند HTML. ملاحظات ستستبدل أي عقد DOM داخل عناصر DOM التي سيُصيَّر إليها (أي أنها ستُحذَف ويحلّ المحتوى الجديد محلها). الدالة ReactDOM.render()‎ لا تعدِّل عقدة عنصر DOM الذي تُصيّر React إليه، وإنما تتطلَّب React ملكيةً كاملةً للعقدة النصية عند التصيير، فلا يجدر بك إضافة أبناء أو إزالة أبناء من العقدة التي تضيف React فيها المكوِّن أو العقدة. التصيير إلى شجرة HTML DOM هو أحد الخيارات فقط، فهنالك خياراتٌ أخرى ممكنة، فمثلًا تستطيع التصيير إلى سلسلة نصية (أي ReactDOMServer.renderToString()‎) في جهة الخادم. إعادة تصيير نفس عنصر DOM سيؤدي إلى تحديث العقد الأبناء الحاليين إذا حدث تغييرٌ فيها، أو إذا أُضيفت عقدة عنصر ابن جديدة. لا تستدعي this.render()‎ يدويًا أبدًا، اترك الأمر إلى React. استخدام تعابير JavaScript داخل JSX آمل الآن أن يكون واضحًا أنَّ JSX هي صيغة بسيطة ستحوَّل في نهاية المطاف إلى شيفرة JavaScript حقيقة، لكن ماذا سيحدث عندما نريد تضمين شيفرة JavaScript حقيقية داخل JSX؟ كل ما علينا فعله لكتابة تعابير JavaScript داخل JSX هو وضعها ضمن قوسين معقوفين {}. في شيفرة React الآتية، سنضيف تعبيرًا من تعابير JavaScript (وهو 2+2) محاطًا بقوسين معقوفين {} التي ستُفسَّر من معالج JavaScript: var label = '2 + 2'; var inputType = 'input'; // JSX لاحظ كيفية استخدام تعابير أو قيم جافاسكربت بين القوسين المعقوفين داخل صيغة var reactNode = <label>{label} = <input type={inputType} value={2+2} /></label>; ReactDOM.render(reactNode, document.getElementById('app')); ستحوَّل شيفرة JSX إلى النتيجة الآتية: var label = '2 + 2'; var inputType = 'input'; var reactNode = React.createElement( 'label', null, label, ' = ', React.createElement('input', { type: inputType, value: 2 + 2 }) ); ReactDOM.render(reactNode, document.getElementById('app')); بعد أن تُفسَّر الشيفرة السابقة من محرِّك JavaScript (أي المتصفح)، فستُقدَّر قيمة تعابير JavaScript وستبدو شيفرة HTML كما يلي: <div id="app"> <label data-reactid=".0"><span data-reactid=".0.0">2 + 2</span><span data-reactid=".0.1"> = </span><input type="input" value="4" data-reactid=".0.2"></label> </div> لا يوجد شيءٌ معقد في المثال السابق، بعد الأخذ بالحسبان أنَّ الأقواس ستُهرِّب شيفرة JSX. فالقوسان {} سيخبران JSX أنَّ المحتوى الموجود داخلهما هو شيفرة JavaScript فستتركها دون تعديل لكي يفسرها مُحرِّك JavaScript (أي التعبير 2+2) لاحظ أنَّ القوسين {} يمكن أن يستخدما في أي مكان داخل تعابير JSX لطالما كانت نتيجتها هو تعبيرٌ صالحٌ في JavaScript. استخدام تعليقات JavaScript داخل JSX يمكنك وضع تعليقات JavaScript في أي مكان في شيفرة JSX عدا المواضع التي تتوقع فيها JSX عقدة React ابن. في هذه الحالة ستحتاج إلى تهريب (escape) التعليق باستخدام {} لكي تعلم JSX أنَّ عليها تمرير المحتوى كشيفرة JavaScript. تفحص الشيفرة الآتية، واحرص على فهم متى عليك إخبار JSX أن تُهرِّب تعليق JavaScript لكيلا تُنشَأ عقدة React ابن: var reactNode = <div /*comment*/>{/* use {} here to comment*/}</div>; إذا لم نضع التعليق في الشيفرة السابقة الموجود داخل العقدة ضمن القوسين {} فسيحاول Babel تحويل التعليق إلى عقدة React نصية، وسيكون الناتج -غير المتوقع- دون استخدام {} كما يلي: var reactNode = React.createElement( "div", null, "/* use ", " here to comment*/" ); مما سينُتِج شيفرة HTML الآتية التي تحتوي على عقد نصية أُنشِئت خطأً: <div data-reactid=".0"> <span data-reactid=".0.0">/* use </span><span data-reactid=".0.1"> here to comment*/</span> </div> table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } استخدام أنماط CSS داخل JSX لتعريف أنماط CSS ضمن صيغة JSX، فعلينا تمرير مرجعية إلى كائنٍ يحتوي على خاصيات CSS وقيمها إلى الخاصية style. سنُهيّئ في بداية المثال الآتي كائن JavaScript باسم styles يحتوي على الأنماط التي نريد تضمينها سطريًا في JSX، ثم سنستخدم القوسين {} للإشارة إلى الكائن الذي يجب أن يُستخدَم كقيمة للأنماط (مثل style={styles}‎): var styles = { color: 'red', backgroundColor: 'black', fontWeight: 'bold' }; var reactNode = <div style={styles}>test</div>; ReactDOM.render(reactNode, document.getElementById('app1')); لاحظ أنَّ خاصيات CSS المُضمَّنة سطريًا مكتوبة بطريقة كتابة خاصيات CSS في JavaScript، وهذا ضروريٌ لأنَّ JavaScript لا تسمح باستخدام الشرطات – في أسماء الخاصيات. عند تحويل شيفرة JSX السابقة باستخدام Babel، ثم تفسيرها من محرِّك JavaScript فستكون شيفرة HTML الناتجة: <div style="color:red;background-color:black;font-weight:bold;" data-reactid=".0">test</div> ملاحظات يجب أن تبدأ السابقات الخاصة بالمتصفحات (باستثناء ms) بحرفٍ كبير، لهذا السبب تبدأ الخاصية WebkitTransition بحرف W كبير. يجب ألا يفاجئك استخدام الأحرف الكبيرة في أسماء خاصيات CSS بدلًا من الشرطات، فهذه هي الطريقة المتبعة للوصول إلى تلك الخاصيات في شجرة DOM عبر JavaScript (كما في document.body.style.backgroundImage). عند تحديد قيمة بواحدة البكسل، فستضيف React السلسلة النصية "px" تلقائيًا بعد القيم الرقمية باستثناء الخاصيات الآتية: columnCount fillOpacity flex flexGrow flexShrink fontWeight lineClamp lineHeight opacity order orphans strokeOpacity widows zIndex zoom تعريف الخاصيات في JSX في المقال السابق، ناقشنا تمرير خاصيات إلى الدالة React.createElement(type, props, children)‎ عند إنشاء عقد React. ولمّا كانت صيغة JSX ستحوِّل إلى استدعاءات للدالة React.createElement()‎ فأنت تملك فكرةً (من المقال السابق) كيف تعمل خاصيات React. لكن لمّا كانت JSX تُستخدَم للتعبير عن عناصر HTML فإنَّ الخاصيات المُعرَّفة ستُضاف إلى عنصر HTML الناتج. في المثال الآتي سأعرِّف عقدة <li> في React باستخدام JSX، ولها خمس خاصيات، لاحظ أنَّ إحداها هي خاصيةٌ غيرُ قياسيةٍ في HTML (وهي foo: 'bar'‎) أما البقية فهي خاصيات HTML عادية: var styles = {backgroundColor:'red'}; var tested = true; var text = 'text'; var reactNodeLi = <li id="" data-test={tested?'test':'false'} className="blue" aria-test="test" style={styles} foo="bar"> {text} </li>; ReactDOM.render(reactNodeLi, document.getElementById('app1')); ستبدو شيفرة JSX بعد تحويلها كما يلي، لاحظي أنَّ الخاصيات أصبح وسائط مُمرَّرة إلى الدالة: var reactNodeLi = React.createElement( 'li', { id: '', 'data-test': tested ? 'test' : 'false', className: 'blue', 'aria-test': 'test', style: styles, foo: 'bar' }, text ); عند تصيير العقد reactNodeLi إلى DOM، فستبدو كما يلي: <div id="app1"> <li id="true" data-test="test" class="blue" aria-test="test" style="background-color:red;" data-reactid=".0"> text </li> </div> يجب أن تلاحظ الأمور الأربعة الآتية: ترك قيمة إحدى الخاصيات فارغةً سيؤدي إلى جعل قيمتها مساويةً إلى true (أي id=""‎ ستصبح id="true"‎، و test ستصبح test="true"‎). الخاصية foo لن تُضاف إلى العنصر النهائي لأنها ليست خاصية HTML قياسية. لا يمكنك كتابة أنماط سطرية في JSX. عليك أن تُشير إلى كائنٍ يقطع في مجال تعريف شيفرة JSX أو تمرير كائن يحتوي على خاصيات CSS مكتوبةً كخاصيات JavaScript. يمكن أن تُضاف قيم JavaScript ضمن JSX باستخدام القوسين المعقوفين {} (كما في test={text}‎ و data-test={tested?'test':'false'}‎). ملاحظات إذا كانت خاصيةٌ ما مكررةً فستؤخذ آخر قيمة لها. إذا مررت خاصيات إلى عناصر HTML التي ليست موجودة في مواصفة HTML فلن تعرضها React. أما إذا استخدمتَ عناصر HTML مخصصة (أي أنها ليست قياسية) فستُضاف الخاصيات التي ليست موجودةً في مواصفة HTML إلى العناصر المخصصة (فمثلًا ‎<x-my-component custom-attribute="foo" />‎). الخاصية class يجب أن تكتب className. الخاصية for يجب أن تكتب htmlFor. الخاصية style تقبل إشارةً مرجعيةً إلى كائنٍ يحتوي على خاصيات CSS بالصيغة المتعارف عليها في JavaScript (أي background-color تصبح backgroundColor). خاصيات النماذج في HTML (مثل <input> أو <textarea></textarea> …إلخ.) المُنشَأة كعقد React ستدعم خاصيات التي يمكن تغييرها عبر تفاعل المستخدم مع العنصر؛ وهذه الخاصيات هي value و checked و selected. توفِّر React الخاصيات key و ref و dangerouslySetInnerHTML التي لا تتوافر في DOM وتأخذ دورًا فريدًا. يجب أن تكتب جميع الخاصيات مع حذف الشرطة - وجعل أول حرف يليها مكتوبًا بحرفٍ كبير، أي أنَّ الخاصية accept-charset ستُكتَب acceptCharset. هذه هي خاصيات HTML التي تدعمها تطبيقات React: accept acceptCharset accessKey action allowFullScreen allowTransparency alt async autoComplete autoFocus autoPlay capture cellPadding cellSpacing challenge charSet checked classID className colSpan cols content contentEditable contextMenu controls coords crossOrigin data dateTime default defer dir disabled download draggable encType form formAction formEncType formMethod formNoValidate formTarget frameBorder headers height hidden high href hrefLang htmlFor httpEquiv icon id inputMode integrity is keyParams keyType kind label lang list loop low manifest marginHeight marginWidth max maxLength media mediaGroup method min minLength multiple muted name noValidate nonce open optimum pattern placeholder poster preload radioGroup readOnly rel required reversed role rowSpan rows sandbox scope scoped scrolling seamless selected shape size sizes span spellCheck src srcDoc srcLang srcSet start step style summary tabIndex target title type useMap value width wmode wrap تعريف الأحداث في JSX في المقال السابق، شرحنا ووضحنا كيف يمكن أن ترتبط الأحدث مع عقد React. لفعل المثل في JSX عليك إضافة الأحداث ودالة المعالجة الخاصة بها كخاصية لشيفرة JSX التي تُمثِّل عقدة React. أُخِذ المثال الآتي من المقال السابق ويبيّن طريقة إضافة حدث إلى عقدة React دون استخدام JSX: var mouseOverHandler = function mouseOverHandler() { console.log('you moused over'); }; var clickhandler = function clickhandler() { console.log('you clicked'); }; var reactNode = React.createElement( 'div', { onClick: clickhandler, onMouseOver: mouseOverHandler }, 'click or mouse over' ); ReactDOM.render(reactNode, document.getElementById('app')); يمكن أن تُكتَب الشيفرة السابقة باستخدام JSX: var mouseOverHandler = function mouseOverHandler() { console.log('you moused over'); }; var clickHandler = function clickhandler() { console.log('you clicked'); }; var reactNode = <div onClick={clickHandler} onMouseOver={mouseOverHandler} >click or mouse over</div>; ReactDOM.render(reactNode, document.getElementById('app')); لاحظ أننا استخدمنا القوسين {} لربط دالة JavaScript إلى الحدث (أي onMouseOver={mouseOverHandler}‎)، وهذه الطريقة تشابه طريقة ربط الأحداث السطرية في DOM. الأحداث التي تدعمها React موجودةٌ في الجدول الآتي: نوع الحدث الأحداث خاصيات متعلقة به الحافظة onCopy onCut onPaste DOMDataTransfer clipboardData التركيب onCompositionEnd onCompositionStart onCompositionUpdate data لوحة المفاتيح onKeyDown onKeyPress onKeyUp altKey charCode ctrlKey getModifierState(key)‎ key keyCode locale location metaKey repeat shiftKey which التركيز onChange onInput onSubmit DOMEventTarget relatedTarget النماذج OnFocus onBlur الفأرة onClick onContextMenu onDoubleClick onDrag onDragEnd onDragEnter onDragExit onDragLeave onDragOver onDragStart onDrop onMouseDown onMouseEnter onMouseLeave onMouseMove onMouseOut onMouseOver onMouseUp altKey button buttons clientX clientY ctrlKey getModifierState(key)‎ metaKey pageX pageY DOMEventTarget relatedTarget screenX screenY shiftKey الاختيار onSelect اللمس onTouchCancel onTouchEnd onTouchMove onTouchStart altKey DOMTouchList changedTouches ctrlKey getModifierState(key)‎ metaKey shiftKey DOMTouchList targetTouches DOMTouchList touches واجهة المستخدم onScroll detail DOMAbstractView view الدولاب onWheel deltaMode deltaX deltaY deltaZ الوسائط onAbort onCanPlay onCanPlayThrough onDurationChange onEmptied onEncrypted onEnded onError onLoadedData onLoadedMetadata onLoadStart onPause onPlay onPlaying onProgress onRateChange onSeeked onSeeking onStalled onSuspend onTimeUpdate onVolumeChange onWaiting الصور onLoad onError الحركات onAnimationStart onAnimationEnd onAnimationIteration animationName pseudoElement elapsedTime الانتقالات onTransitionEnd propertyName pseudoElement elapsedTime ملاحظات توحِّد React التعامل مع الأحداث لكي تسلك سلوكًا متماثلًا في جميع المتصفحات. تنطلق الأحداث في React في مرحلة الفقاعات (bubbling phase). لإطلاق حدث في مرحلة الالتقاط (capturing phase) فأضف الكلمة "Capture" إلى اسم الحدث، أي أنَّ الحدث onClick سيصبح onClickCapture). إذا احتجتَ إلى تفاصيل كائن الأحداث المُنشَأ من المتصفح، فيمكنك الوصول إليه باستخدام الخاصية nativeEvent في كائن SyntheticEvent المُمرَّر إلى دالة معالجة الأحداث في React. لا تربط React الأحداث إلى العقد نفسها، وإنما تستخدم «تفويض الأحداث» (event delegation). يجب استخدام e.stopPropagation()‎ أو e.preventDefault()‎ يدويًا لإيقاف انتشار الأحداث بدلًا من استخدام return false;‎. لا تدعم React جميع أحداث DOM، لكن ما يزال بإمكاننا الاستفادة منها باستخدام توابع دورة الحياة في React. ترجمة وبتصرف للفصل JavaScript Syntax Extension (a.k.a, JSX)‎ من كتاب React Enlightenment
  5. يبيّن هذا الدرس كيفية استخدام عقد React لإنشاء مكوِّنات React أساسية. ما هي مكونات React؟ سنشرح في هذا القسم طبيعة مكونات React ونغطي بعض التفاصيل التي تتعلق بإنشاء مكونات React. عادةً تكون واجهة المستخدم (تدعى بالشجرة tree) مقسمةً إلى أقسامٍ منطقية تسمى بالفروع (branches)، وتصبح هذه الشجرة نقطة البداية للمكوِّن وكل قسم في واجهة المستخدم يصبح مكونًا فرعيًا التي يمكن بدورها أن تُقسَّم إلى مكونات فرعية؛ ويؤدي ذلك إلى تنظيم واجهة المستخدم ويسمح أيضًا لتغيرات البيانات والحالات أن تمر من الشجرة إلى الفروع ومنها إلى الفروع الداخلية. إذا كان الشرح السابق لمكونات React مبهمًا فأقترح عليك أن تعاين واجهة المستخدم لأي تطبيق وتحاول ذهنيًا تقسيمها إلى أقسام منطقية. من المحتمل تسمية هذه الأقسام بالمكونات. مكوِّنات React هي تجريد برمجي (programmatic abstraction) لعناصر واجهة المستخدم وللأحداث ولتغيرات الحالة ولتغيرات DOM والغرض منها هو تسهيل إنشاء هذه الأقسام والأقسام الفرعية. فعلى سبيل المثال، الكثير من واجهات المستخدم للتطبيقات تحتوي على مكوِّن للتخطيط (layout) كأحد المكونات الرئيسية في واجهة المستخدم، وهذا المكون يحتوي بدوره على على مكونات فرعية مثل مكوِّن البحث ومكوِّن قائمة التنقل. ويمكن تقسيم مكوِّن البحث مثلًا إلى مكونات فرعية، فيمكن أن يكون حقل الإدخال منفصلًا عن زر البحث. وكما ترى، يمكن بسهولة أن تصبح واجهة المستخدم مكونةً من شجرةٍ من المكونات، وتُنشَأ العديد من واجهات المستخدم للتطبيقات حاليًا باستخدام مكونات ذاتُ غرضٍ وحيد. توفِّر React الطرائق اللازمة لإنشاء هذه المكونات عبر React.Component إذا كنتَ تستخدم الأصناف في ES6. تقبل الدالة createReactClass()‎ من الحزمة create-react-class كائن ضبط وتُعيد نسخةً (instance) من مكوِّن React. يمكننا أن نقول أنَّ مكوِّن React هو أي جزء من الواجهة البرمجية التي تحتوي على عقد React (عبر React.createElement()‎ أو صيغة JSX). أمضينا وقتًا طويلًا في بداية هذا الكتاب ونحن نتحدث عن عقد React، لذا أتوقع أنَّ محتويات مكوِّن React أصبح واضحةً وجليةً لك. كل ذلك يبدو سهلًا وبسيطًا حتى ندرك أنَّ مكوِّنات React يمكنها أن تحتوي على مكونات React فرعية، وهذا لا يختلف عن فكرة أنَّ عقد React يمكنها أن تحتوي على عقد React أخرى في شجرة DOM الافتراضية. قد يؤلمك رأسك من الكلام السابق، لكن إذا فكرتَ مليًا فستجد أنَّ المكوِّن يحيط نفسه بمجموعة منطقية من الفروع في شجرة من العقد. وبهذا يمكنك تعريف واجهة المستخدم كلها باستخدام مكوِّنات React وستكون النتيجة النهائية هي شجرة من عقد React التي يمكن تحويلها بسهولة إلى مستند HTML (الذي يتكون من عقد DOM التي تؤلِّف واجهة المستخدم). إنشاء مكونات React يمكن إنشاء مكوِّن React الذي قد تكون له حالة state باستدعاء الدالة createReactClass()‎ من الحزمة create-react-class (أو اشتقاق React.Component إذا كنتَ تستخدم الأصناف في ES6). هذه الدالة (أو الدالة البانية) تأخذ وسيطًا واحدًا هو كائن يُستخدَم لتوفير تفاصيل المكوِّن. الجدول الآتي يبيّن خيارات الضبط المتاحة لمكوِّنات React: خيار الضبط الشرح render قيمة مطلوبة، وتُمثِّل عادةً دالةً تُعيد عقد React أو مكوِّنات React الأخرى أو القيمة null أو false. getInitialState كائن يحتوي على القيمة الابتدائية للخاصية this.state، يمكنك الحصول على قيمتها إذا كنت تستعمل الأصناف في ES6 عبر استخدام this.state في الدالة البانية. getDefaultProps كائن يحتوي على القيم التي ستُضبَط في الخاصية this.props، يمكنك ضبط قيمتها إذا كنت تستعمل الأصناف في ES6 باستخدام الخاصية static defaultProps في الصنف المشتق. propTypes كائن يحتوي على مواصفات التحقق من الخاصيات props. mixins مصفوفة من المخاليط (mixins، وهي كائنات تحتوي على توابع [methods]) التي يمكن مشاركتها بين المكوِّنات. لاحظ أن ES6 لا تدعم المخاليط، فلن تتمكن من استعمالها إذا كنتَ تستعمل الأصناف في ES6. statics كائن يحتوي على التوابع الساكنة (static methods). displayName سلسلة نصية تعطي اسمًا للمكوِّن، وتُستخدَم في رسائل التنقيح، وستُضبَط هذه القيمة تلقائيًا إذا كنّا نستعمل JSX. componentWillMount دالة رد نداء (callback function) التي ستستدعى مرةً واحدةً تلقائيًا قبل التصيير الابتدائي مباشرةً‎. componentDidMount دالة رد نداء (callback function) التي ستستدعى مرةً واحدةً تلقائيًا بعد التصيير الابتدائي مباشرةً‎. UNSAFE_componentWillReceiveProps دالة رد نداء (callback function) التي ستستدعى تلقائيًا عندما يستلم المكوِّن خاصياتٍ props جديدة‎. shouldComponentUpdate دالة رد نداء (callback function) التي ستستدعى تلقائيًا عندما يستلم المكوِّن خاصياتٍ props جديدة‎ أو تغيّرت حالته state. UNSAFE_componentWillUpdate دالة رد نداء (callback function) التي ستستدعى تلقائيًا قبل أن يستلم المكوِّن خاصياتٍ props جديدة‎ أو تغيّرت حالته state. componentDidUpdate دالة رد نداء (callback function) التي ستستدعى تلقائيًا بعد نقل التحديثات التي جرت على المكوِّن إلى DOM. componentWillUnmount دالة رد نداء (callback function) التي ستستدعى تلقائيًا قبل إزالة المكوِّن من شجرة DOM. أهم خيارات ضبط المكوِّنات هو render، وهذا الخيار مطلوبٌ وهو دالةٌ تعيد عقد ومكونات React. وجميع خيارات ضبط المكوِّنات الأخرى اختيارية. الشيفرة الآتية هي مثالٌ عن إنشاء المكوِّن Timer من عقد React باستخدام createReactClass()‎ (سنستخدم الحزمة create-react-class في هذا المثال لترى كيف تستعمل، وسنستعمل الأصناف في ES6 لإنشاء بقية مكونات React لاحقًا). احرص على قراءة التعليقات في الشيفرة: var createReactClass = require('create-react-class'); // وتمرير كائن من خيارات الضبط Timer إنشاء المكون var Timer = createReactClass({ // this.state دالة تعيد كائنًا والذي سيصبح قيمة getInitialState: function() { return { secondsElapsed: Number(this.props.startTime) || 0 }; }, tick: function() { // تابع مخصص this.setState({ secondsElapsed: this.state.secondsElapsed + 1 }); }, componentDidMount: function() { // دالة رد النداء لدورة حياة المكوِّن this.interval = setInterval(this.tick, 1000); }, componentWillUnmount: function() { // دالة رد النداء لدورة حياة المكوِّن clearInterval(this.interval); }, render: function() { // JSX باستخدام صيغة React دالة تعيد عقد return ( <div> Seconds Elapsed: {this.state.secondsElapsed} </div> ); } }); ReactDOM.render(< Timer startTime="60" />, app); // startTime تمرير قيمة الخاصية تبدو الشيفرة السابقة طويلةً، لكن أغلبية الشيفرة السابقة تُنشِئ المكوِّن بتمرير كائن إلى الدالة createReactClass()‎ لإنشاء مكوِّنٍ مع كائن ضبط يحتوي على خمس خاصيات (وهي getInitialState و tick و componentDidMount و componentWillUnmount و render). لاحظ أنَّ اسم المكوِّن Timer يبدأ بحرفٍ كبير، فعند إنشاء مكوِّنات React مخصصة، عليك أن تجعل أول حرفٍ من اسمها كبيرًا. أضف إلى ذلك أنَّ القيمة this ضمن خيارات الضبط تُشير إلى نسخة المكوِّن المُنشَأة. سنناقش الواجهة البرمجية للمكوِّنات بالتفصيل في نهاية هذا الدرس، لكن دعنا نتأمل خيارات الضبط المتاحة عند تعريف مكوِّن React وكيف استطعنا الإشارة إلى المكوِّن باستخدام الكلمة المحجوزة this. لاحظ أيضًا أنني أضفت في المثال السابق تابعًا خاصًا بالمكوِّن (وهو tick). بعد تركيب (mount) المكوِّن، فيمكننا استخدام الواجهة البرمجية التي تحتوي على أربعة توابع، وهي مبيّنة في الجدول الآتي: التابع مثال الشرح setState()‎ this.setState({mykey: 'my new value'}); this.setState(function(previousState, currentProps) { return {myInteger: previousState.myInteger + 1}; });‎ التابع الرئيسي التي يُستخدَم لإعادة تصيير المكوِّن ومكوناته الفرعية. ForceUpdate()‎ this.forceUpdate(function(){//callback});‎ استدعاء التابع forceUpdate()‎ سيؤدي إلى استخدام التابع render()‎ دون استدعاء shouldComponentUpdate()‎. أكثر تابع مستخدم في واجهة المكوِّنات البرمجية هو التابع setState()‎، وسيُشرَح استخدامه في درس حالة مكونات React. ملاحظات تسمى دوال رد النداء في خيارات ضبط المكوِّن (وهي componentWillUnmount و componentDidUpdate و UNSAFE_componentWillUpdate و shouldComponentUpdate و UNSAFE_componentWillReceiveProps و componentDidMount و UNSAFE_componentWillMount)‎ بتوابع دورة الحياة (lifecycle methods) لأنَّ هذه التوابع المختلفة ستُنفَّذ في نقاط معيّنة في دورة حياة المكوِّن. الدالة createReactClass هي دالة تُنشِئ نسخةً من المكوِّنات وهي موجودة في الحزمة create-react-class. الدالة render()‎ هي دالة صرفة، وهذا يعني أنها لا تعدّل حالة المكوِّن، وتعيد نفس النتيجة في كل مرة تستدعى فيها، ولا تقرأ أو تكتب إلى DOM أو تتفاعل مع المتصفح (باستخدام setTimout على سبيل المثال). فإذا أردت التعامل مع المتصفح، فافعل ذلك في التابع componentDidMount()‎ أو غيره من توابع دورة الحياة. الإبقاء على الدالة render()‎ صرفةً يجعل التصيير عمليًا ويُسهِّل التفكير في المكوِّنات. المكونات تعيد عقدة أو مكون واحد قيمة خيار الضبط render التي تُعرَّف عند إنشاء المكوِّن يجب أن تعيد مكوِّن أو عقدة React واحدة فقط. هذه العقدة أو المكون يمكن أن تحتوي على أي عدد من الأبناء. عقدة البداية في الشيفرة الآتية يمكن أن تحتوي هذه العقدة على أي عدد من العقد الأبناء: class MyComponent extends React.Component { render() { return <reactNode> <span>test</span> <span>test</span> </reactNode>; } }; ReactDOM.render(<MyComponent />, app); لاحظ أننا تستطيع إعادة عقد React بجعلها على عدّة أسطر، ويمكنك أن تحيط القيمة المعادة بقوسين هلاليين (). لاحظ كيف أعدنا المكوِّن MyComponent المُعرَّف باستخدام JSX بوضعه بين قوسين: class MyComponent extends React.Component { render() { return ( <reactNode> <span>test</span> <span>test</span> </reactNode> ); } }; ReactDOM.render(<MyComponent />, app); سيحدث خطأ إذا حاولنا إعادة أكثر من عقدة React واحدة. يمكنك أن تفكر فيها مليًا، فالخطأ يحدث لأنَّ من غير الممكن إعادة دالتَي React.createElement()‎ باستخدام JavaScript. class MyComponent extends React.Component { render() { return ( <span>test</span> <span>test</span> ); } }; ReactDOM.render(<MyComponent />, app); ستؤدي الشيفرة السابقة إلى حدوث الخطأ الآتي: Syntax error: Adjacent JSX elements must be wrapped in an enclosing tag (8:3) 6 | return ( 7 | <span>test</span> \> 8 | <span>test</span> | ^ 9 | ); 10 | } 11 | }); من الشائع أن نرى المطورين يضيفون عنصر حاوي لتفادي هذا الخطأ. هذه المشكلة تؤثر على المكوِّنات أيضًا كما تؤثر على عقد React. فلا يمكن إعادة إلا مكوِّنٍ وحيد، ولكن يمكن أن يمتلك هذا المكوِّن عددًا غير محدودٍ من الأبناء. class MyComponent extends React.Component { render() { return ( <MyChildComponent/> ); } }; class MyChildComponent extends React.Component { render() { return <test>test</test>; } }; ReactDOM.render(<MyComponent />, app); إذا أعدتَ مكونين متجاورين، فسيحدث نفس الخطأ: var MyComponent = createReactClass({ render: function() { return ( <MyChildComponent/> <AnotherChildComponent/> ); } }); var MyChildComponent = createReactClass({ render: function() {return <test>test</test>;} }); var AnotherChildComponent = createReactClass({ render: function() {return <test>test</test>;} }); ReactDOM.render(<MyComponent />, app); رسالة الخطأ: Syntax error: Adjacent JSX elements must be wrapped in an enclosing tag (8:2) 6 | return ( 7 | <MyChildComponent/> \> 8 | <AnotherChildComponent/> | ^ 9 | ); 10 | } 11 | }); الإشارة إلى نسخة المكون عندما يُصيّر أحد العناصر (render) فستُنشأ نسخة من مكوِّن React من الخيارات المُمرَّرة إليه، يمكننا الوصول إلى هذه النسخة (instance) وخاصياتها (مثل this.props) وتوابعها (مثل this.setState()‎) بطريقتين. أوّل طريقة هي استخدام الكلمة المحجوزة this داخل دالة ضبط المكوِّن. ففي المثال الآتي، جميع تعابير console.log(this) ستُشير إلى نسخة المكوِّن: class Foo extends React.Component { componentWillMount(){ console.log(this); } componentDidMount(){ console.log(this); } render() { return <div>{console.log(this)}</div>; } }; ReactDOM.render(<Foo />, document.getElementById('app')); الطريقة الأخرى للحصول على مرجعية لنسخة المكوِّن تتضمن استخدام القيمة المُعادة من استدعاء الدالة ReactDOM.render()‎. بعبارةٍ أخرى، ستُعيد الدالة ReactDOM.render()‎ مرجعيةً إلى أعلى مكوِّن جرى تصييره: class Bar extends React.Component { render() { return <div></div>; } }; var foo; // تخزين مرجعية إلى النسخة خارج دالة class Foo extends React.Component { render() { return <Bar>{foo = this}</Bar>; } }; var FooInstance = ReactDOM.render(<Foo />, document.getElementById('app')); // Foo التأكد أنَّ القيمة المُعادة هي مرجعية إلى نسخة console.log(FooInstance === foo); // true الناتج ملاحظات تُستخدَم الكلمة المحجوزة this داخل المكوِّن للوصول إلى خصائص المكوِّن كما في this.props.[NAME OF PROP] و this.props.children و this.state. يمكن أن تستخدم أيضًا لاستدعاء خاصيات وتوابع الصنف التي تتشاركها جميع المكوِّنات مثل this.setState‎. تعريف الأحداث في المكونات يمكن إضافة الأحداث إلى عقد React داخل خيار الضبط render كما ناقشنا سابقًا. سنضبط -في المثال الآتي- حدثَي React (وهما onClick و onMouseOver) على عقد React بصيغة JSX كما لو كنّا نضبط خيارات المكوِّن: class MyComponent extends React.Component { mouseOverHandler(e) { console.log('you moused over'); console.log(e); // sysnthetic event instance } clickHandler(e) { console.log('you clicked'); console.log(e); // sysnthetic event instance } render(){ return ( <div onClick={this.clickHandler} onMouseOver={this.mouseOverHandler}>click or mouse over</div> ) } }; ReactDOM.render(<MyComponent />, document.getElementById('app')); عندما تُصيّر React مكوِّنًا فستبحث عن خاصيات ضبط الأحداث في React (مثل onClick)، وتعامل هذه الخاصيات معاملةً مختلفةً عن بقية الخاصيات (جميع أحداث React مذكورة في الجدول أدناه). ومن الجلي أنَّ الأحداث في شجرة DOM الحقيقية سترتبط مع معالجاتها وراء الكواليس. أحد جوانب هذا الارتباط هو جعل سياق دالة معالجة الحدث في نفس مجال (scope) نسخة المكوِّن. لاحظ أنَّ قيمة this داخل دالة معالجة الحدث ففي المثال الآتي ستُشير إلى نسخة المكوِّن نفسها. class MyComponent extends React.Component { constructor(props) { super(props); // this هذا الربط ضروري لتعمل this.mouseOverHandler = this.mouseOverHandler.bind(this); } mouseOverHandler(e) { console.log(this); // إلى نسخة الكائن this تشير console.log(e); // sysnthetic event instance } render(){ return ( <div onMouseOver={this.mouseOverHandler}>mouse over me</div> ) } }; ReactDOM.render(<MyComponent />, document.getElementById('app')); تدعم React الأحداث الآتية: نوع الحدث الأحداث خاصيات متعلقة به الحافظة OnCopy onCut onPaste DOMDataTransfer clipboardData التركيب OnCompositionEnd onCompositionStart onCompositionUpdate data لوحة المفاتيح OnKeyDown onKeyPress onKeyUp AltKey charCode ctrlKey getModifierState(key) key keyCode locale location metaKey repeat shiftKey which التركيز OnChange onInput onSubmit DOMEventTarget relatedTarget النماذج OnFocus onBlur الفأرة OnClick onContextMenu onDoubleClick onDrag onDragEnd onDragEnter onDragExit onDragLeave onDragOver onDragStart onDrop onMouseDown onMouseEnter onMouseLeave onMouseMove onMouseOut onMouseOver onMouseUp AltKey button buttons clientX clientY ctrlKey getModifierState(key) metaKey pageX pageY DOMEventTarget relatedTarget screenX screenY shiftKey الاختيار onSelect اللمس OnTouchCancel onTouchEnd onTouchMove onTouchStart AltKey DOMTouchList changedTouches ctrlKey getModifierState(key) metaKey shiftKey DOMTouchList targetTouches DOMTouchList touches واجهة المستخدم onScroll Detail DOMAbstractView view الدولاب onWheel DeltaMode deltaX deltaY deltaZ الوسائط OnAbort onCanPlay onCanPlayThrough onDurationChange onEmptied onEncrypted onEnded onError onLoadedData onLoadedMetadata onLoadStart onPauseonPlay onPlaying onProgress onRateChange onSeeked onSeeking onStalled onSuspend onTimeUpdate onVolumeChange onWaiting الصور onLoad onError الحركات onAnimationStart onAnimationEnd onAnimationIteration animationName pseudoElement elapsedTime الانتقالات onTransitionEnd propertyName pseudoElement elapsedTime ملاحظات توحِّد React التعامل مع الأحداث لكي تسلك سلوكًا متماثلًا في جميع المتصفحات. تنطلق الأحداث في React في مرحلة الفقاعات (bubbling phase). لإطلاق حدث في مرحلة الالتقاط (capturing phase) فأضف الكلمة "Capture" إلى اسم الحدث، أي أنَّ الحدث onClick سيصبح onClickCapture). إذا احتجتَ إلى تفاصيل كائن الأحداث المُنشَأ من المتصفح، فيمكنك الوصول إليه باستخدام الخاصية nativeEvent في كائن SyntheticEvent المُمرَّر إلى دالة معالجة الأحداث في React. لا تربط React الأحداث إلى العقد نفسها، وإنما تستخدم «تفويض الأحداث» (event delegation). يجب استخدام e.stopPropagation()‎ أو e.preventDefault()‎ يدويًا لإيقاف انتشار الأحداث بدلًا من استخدام return false;‎. لا تدعم React جميع أحداث DOM، لكن ما يزال بإمكاننا الاستفادة منها باستخدام توابع دورة الحياة في React. تركيب المكونات إذا لم يكن واضحًا لك أنَّ مكوِّنات React تستطيع أن تستخدم مكوِّنات React الأخرى فاعلم أنَّها تستطيع فعل ذلك. فيمكن أن تحتوي دالة الضبط render عند تعريف أحد مكوِّنات React إشارةً إلى المكونات الأخرى. فعندما يحتوي مكوِّنٌ ما على مكوِّنٍ آخر فيمكننا أن نقول أنَّ المكوِّن الأب «يمتلك» مكوِّنًا ابنًا (وهذا يسمى بالتركيب [composition]). في الشيفرة أدناه، يحتوي (أو يمتلك) المكوِّن BadgeList المكوِّنين BadgeBill و BadgeTom: class BadgeBill extends React.Component { render() {return <div>Bill</div>;} }; class BadgeTom extends React.Component { render() {return <div>Tom</div>;} }; class BadgeList extends React.Component { render() { return ( <div> <BadgeBill/> <BadgeTom /> </div>); } }; ReactDOM.render(<BadgeList />, document.getElementById('app')); جعلتُ الشيفرة السابقة بسيطةً عمدًا لتوضيح كيفية تركيب المكونات، وسنلقي في الدرس القادم نظرةً عن كيفية كتابة شيفرات التي تستخدم الخاصيات props لإنشاء مكوِّن Badge عام (generic). يمكن أن يأخذ المكوِّن العام Badge أي قيمة مقارنةً مع كتابة Badge مكتوبٌ فيه الاسم سابقًا. ملاحظات سمة أساسية لكتابة واجهات مستخدم قابلة للصيانة والإدارة هو تركيب المكوِّنات. صُمِّمَت مكوِّنات React لتحتوي على مكوِّناتٍ أخرى. لاحظ كيف تندمج شيفرة HTML والمكونات المُعرَّفة مسبقًا مع بعضهما بعضًا في دالة الضبط render()‎. استيعاب دورة حياة المكوِّن تمرّ مكوِّنات React بأحداث خلال حياتها وتسمى هذه الأحداث بدورة الحياة (lifecycle events). ترتبط هذه الأحدث بتوابع دورة الحياة. يمكنك أن تتذكر أننا ناقشنا بعض تلك التوابع في بداية هذا الدرس عندما ناقشنا عملية إنشاء المكوِّنات. توابع دورة الحياة توفِّر ارتباطات مع مراحل وطبيعة المكوِّن. ففي المثال الآتي الذي استعرته من أحد الأقسام السابقة، سنُسجِّل وقوع أحداث دورة الحياة componentDidMount و componentWillUnmount و getInitialState: var createReactClass = require('create-react-class'); // وتمرير كائن من خيارات الضبط Timer إنشاء المكون var Timer = createReactClass({ // this.state دالة تعيد كائنًا والذي سيصبح قيمة getInitialState: function() { return { secondsElapsed: Number(this.props.startTime) || 0 }; }, tick: function() { // تابع مخصص this.setState({ secondsElapsed: this.state.secondsElapsed + 1 }); }, componentDidMount: function() { // دالة رد النداء لدورة حياة المكوِّن this.interval = setInterval(this.tick, 1000); }, componentWillUnmount: function() { // دالة رد النداء لدورة حياة المكوِّن clearInterval(this.interval); }, render: function() { // JSX باستخدام صيغة React دالة تعيد عقد return ( <div> Seconds Elapsed: {this.state.secondsElapsed} </div> ); } }); ReactDOM.render(< Timer startTime="60" />, app); // startTime تمرير قيمة الخاصية يمكن تقسيم هذه التوابع إلى ثلاثة تصنيفات: مرحلة التركيب (mount) والتحديث (update) والإزالة (unmount). سأعرض جدولًا في كل تصنيف يحتوي على توابع دورة الحياة الخاصة به. مرحلة التركيب تحدث مرحلة التركيب (mounting phase) مرةً واحدةً في دورة حياة المكوِّن، وهي أول مرحلة وتبدأ عندما نُهيِّئ المكوِّن، وفي هذه المرحلة ستُعرَّف وتُضبَط خاصيات وحالة المكوِّن، وسيُركَّب المكوِّن مع جميع أبنائه إلى واجهة المستخدم المُحدَّدة (سواءً كانت DOM أو UIView أو غيرها). وفي النهاية يمكننا أن نجري بعض عمليات المعالجة إن كان ذلك لازمًا. التابع الشرح getInitialState()‎ سيستدعى قبل تركيب المكوِّن، ويجب أن تعرِّف المكونات ذات الحالة (stateful) هذا التابع وتعيد بيانات الحالة الابتدائية. componentWillMount()‎ سيستدعى مباشرةً قبل تركيب المكوِّن. componentDidMount()‎ سيستدعى مباشرةً بعد تركيب المكوِّن. عملية التهيئة التي تتطلب وجود عقد DOM ستُعرَّف في هذا التابع. مرحلة التحديث تقع مرحلة التحديث (updating phase) مرارًا وتكرارًا خلال دورة حياة المكوِّن، وفي هذه المرحلة يمكننا إضافة خاصيات جديدة أو تغيير الحالة أو معالجة تفاعلات المستخدم أو التواصل مع شجرة المكوِّنات، وسنقضي جُلَّ وقتنا في هذه المرحلة. التابع الشرح componentWillReceiveProps(object nextProps)‎ * shouldComponentUpdate(object nextProps, object nextState)‎ سيستدعى عندما يقرر المكوِّن أنَّ أي تغييرات ستحتاج إلى تحديث شجرة DOM. إذا أردت استخدامه فيجب أن تقارن this.props مع nextProps و this.state مع nextState وتُعيد القيمة false لتخبر React بإمكانية تجاوز التحديث. componentWillUpdate(object nextProps, object nextState)‎ سيستدعى مباشرةً قبل وقوع التحديث. لا يمكنك استدعاء this.setState()‎ هنا. componentDidUpdate(object prevProps, object prevState)‎ سيستدعى مباشرةً بعد وقوع التحديث. مرحلة الإزالة تقع مرحلة الإزالة (unmounting phase) مرةً واحدةً في دورة حياة المكوِّنات، وهي تحدث عند إزالة نسخة المكوِّن من واجهة المستخدم. ويمكن أن يحدث ذلك عندما ينتقل المستخدم إلى صفحة أخرى أو تتغير واجهة المستخدم أو اختفى المكوِّن …إلخ. التابع الشرح componentWillUnmount()‎ سيستدعى مباشرةً قبل إزالة المكوِّن وحذفه. يجب أن تأتي عمليات «التنظيف» هنا. ملاحظات التابعان componentDidMount و componentDidUpdate هما مكانان جيدان تضع فيهما البنى المنطقية للمكتبات. راجع توثيق React في موسوعة حسوب لتأخذ نظرةً تفصيليةً حول أحداث دورة حياة مكونات React. ترتيب مرحلة التركيب: التهيئة الابتدائية getDefaultProps()‎ ‏(React.createClass) أو MyComponent.defaultProps (صنف ES6) getInitialState()‎ ‏(React.createClass) أو this.state (دالة بانية في ES6) componentWillMount()‎ render()‎ عملية تهيئة الأبناء وضبط دورة حياتهم componentDidMount()‎ ترتيب مرحلة التحديث: componentWillReceiveProps()‎ shouldComponentUpdate()‎ render()‎ توابع دورة الحياة للأبناء componentWillUpdate()‎ ترتيب مرحلة الإزالة: componentWillUnmount()‎ توابع دورة الحياة للأبناء إزالة نسخة المكوِّن الوصول إلى المكونات أو العقد الأبناء إذا احتوى مكوِّنٌ على مكونات أبناء أو عقد React داخله (كما في أو test) فيمكن الوصول إلى عقد React أو نسخ المكوِّنات الأبناء باستخدام الخاصية this.props.children. عند استخدام المكوِّن Parent في الشيفرة التالية، الذي يحتوي على عنصرَي والتي بدورها تحتوي على عقد React نصية. يمكن الوصول إلى جميع نسخ الأبناء داخل المكوِّن باستخدام this.props.children. وسأحاول في المثال الآتي أن أصل إليها داخل تابع دورة الحياة componentDidMount للعنصر الأب Parent: class Parent2 extends React.Component { componentDidMount() { // <span>child2text</span> الوصول إلى // ولا حاجة لاستخدام مصفوفة لطالما كان هنالك ابن واحد فقط console.log(this.props.children); // <span> لأننا نحصل على ابن العنصر child2text سينتج console.log(this.props.children.props.children); } render() {return <div />;} } class Parent extends React.Component { componentDidMount() { // <div>test</div><div>test</div> الوصول إلى مصفوفة تحتوي على console.log(this.props.children); // في المصفوفة <div> لأننا نصل إلى أول عنصر childtext سينتج console.log(this.props.children[1].props.children); } render() {return <Parent2><span>child2text</span></Parent2>;} } ReactDOM.render( <Parent><div>child</div><div>childtext</div></Parent>, document.getElementById('app') ); تعيد الخاصية this.props.children للمكوِّن Parent مصفوفةً تحتوي على إشارات لنسخ كائنات عقد React الأبناء. سنعرض هذه المصفوفة باستخدام console.log، أضف إلى ذلك أننا سنعرض في المكوِّن Parent قيمة العنصر الابن للعقدة الأولى (أي سنعرض قيمة العقدة النصية). لاحظ عندما استخدمنا المكوِّن Parent2 داخل المكوِّن Parent كان المكوِّن Parent2 يحتوي على عقدة React وحيدة هي child2text وبالتالي ستكون قيمة الخاصية this.props.children في تابع دورة الحياة componentDidMount للمكوِّن Parent2 هي إشارة مباشرة إلى عقدة في React، وليست مصفوفةً تحتوي على عنصرٍ وحيد. ولمّا كان من المحتمل أن تعيد الخاصية this.props.children مجموعةً واسعةً من العقد، فتوفِّر React مجموعةً من الأدوات للتعامل معها، وهذه الأدوات مذكورة في الجدول أدناه. الأداة الشرح React.Children.map(this.props.children, function(){})‎ تستدعي دالة لكل عنصر ابن مباشر موجود ضمن children مع تعيين this إلى قيمة thisArg. إن كان children عبارة عن جزء (fragment) مع مفتاح أو مصفوفة فلن تُمرَّر الدالة للكائن الحاوي. إن كانت قيمة children هي null أو undefined فستُعيد null أو undefined بدلًا من المصفوفة. React.Children.forEach(this.props.children, function(){})‎ مماثلة للتابع React.Children.map()‎ ولكن لا تُعيد مصفوفة. React.Children.count(this.props.children)‎ تُعيد العدد الكلي للمكوّنات الموجود ضمن children، مُكافئ لعدد المرات التي يُمرَّر فيها رد النداء إلى map أو التي يُستدعى فيها forEach. React.Children.only(this.props.children)‎ تتحقّق من أنّ children يمتلك فقط ابنًا واحدًا (عنصر React) وتُعيده. فيما عدا ذلك سيرمي هذا التابع خطأً. React.Children.toArray(this.props.children)‎ تُعيد بنية البيانات children كمصفوفة مع تعيين مفتاح لكل عنصر ابن. تكون مفيدةً إن أردت التعامل مع مجموعة من العناصر الأبناء في توابع التصيير لديك، خاصّة إن أردت إعادة ترتيب أو تجزئة this.props.children قبل تمريره. ملاحظات عندما يكون هنالك عنصرٌ ابن وحيد، فستكون قيمة this.props.children هي العنصر نفسه دون أن يحتوى في مصفوفة. وهذا يوفِّر عملية تهيئة المصفوفة. قد يربكك بدايةً أنَّ قيمة children ليست ما يعيده المكوِّن، وإنما ما هو محتوى داخل المكوِّن. استخدام الخاصيات ref تجعل الخاصية ref من الممكن تخزين نسخة من عنصر أو مكوِّن React معيّن باستخدام دالة الضبط render()‎. وهذا مفيدٌ جدًا عندما تريد الإشارة من داخل المكوِّن إلى عنصر أو مكوِّن آخر محتوى داخل الدالة render()‎. لإنشاء مرجعية، ضع الخاصية ref مع ضبط دالة كقيمةٍ لها في أي عنصر أو مكوِّن، ثم من داخل الدالة سيكون أول معامل (parameter) داخل مجال الدالة يشير إلى العنصر أو المكوِّن الذي عُرِّفَت الخاصية ref عليه. على سبيل المثال، سنعرض المحتويات المرجعية المشار إليها باستخدام ref عبر console.log: class C2 extends React.Component { render() {return <span ref={function(span) {console.log(span)}} />} } class C1 extends React.Component { render() { return( <div> <C2 ref={function(c2) {console.log(c2)}}></C2> <div ref={function(div) {console.log(div)}}></div> </div>); } } ReactDOM.render(<C1 ref={function(ci) {console.log(ci)}} />,document.getElementById('app')); لاحظ أنَّ الإشارات إلى المرجعيات تعيد نسخ المكوِّنات، بينما الإشارات إلى عناصر React تعيد مرجعيات إلى عناصر DOM في شجرة DOM الحقيقية (أي ليست مرجعيةً تشير إلى شجرة DOM الافتراضية، وإنما شجرة DOM الحقيقية). استخدامٌ شائعٌ للخاصية ref هو تخزين مرجعية إلى نسخة المكوِّن. ففي الشيفرة الآتية سأستخدم دالة رد نداء ref على حقل إدخال input نصي لتخزين مرجعية لنسخة المكوِّن لكي تستطيع التوابع الأخرى الوصول إليها عبر الكلمة المحجوزة this (كما في this.textInput.focus()‎): class MyComponent extends React.Component { constructor(props) { super(props); this.handleClick = this.handleClick.bind(this) } handleClick() { // DOM API التركيز على المحتوى النصية باستخدام this.textInput.focus(); } render() { // قيمة الخاصية ref هي دالة رد نداء التي تؤدي إلى // عند تركيب المكون this.textInput الإشارة إلى return ( <div> <input type="text" ref={(thisInput) => {this.textInput = thisInput}} /> <input type="button" value="Focus the text input" onClick={this.handleClick} /> </div> ); } } ReactDOM.render( <MyComponent />, document.getElementById('app') ); ملاحظات لا يمكن ربط ref مع دالة عديم الحالة لأنَّ المكوِّن لا يملك نسخةً (instance) بعد. ربما تشاهد الخاصية ref مع سلسلة نصية بدلًا من دالة؛ وهذا أمرٌ مهمل في الإصدارات المستقبلية، ومن المُفضَّل استخدام الدوال. ستستدعى دالة رد نداء ref مباشرةً بعد تركيب المكوِّن. تسمح لنا الإشارات إلى نسخة المكوِّن باستدعاء توابع مخصصة على المكوِّن إذا كانت موجودةً عند تعريفه. كتابة المراجع مع تعبير تعريف دالة سطري يعني أنَّ React سترى كائن دالة مختلف في كل تحديث، وستستدعى ref مع القيمة null مباشرةً قبل استدعائها مع نسخة المكوِّن، أي أنَّ نسخة المكون ستزال في كل تغيير لقيمة ref وستستدعى ref القديمة مع القيمة null كوسيط. يبين توثيق React ملاحظةً مهمةً «ربّما تكون رغبتك الأولى لاستخدام المراجع هي تحقيق كل ما تُريده في تطبيقك. إن كانت هذه حالتك فخُذ لحظة للتفكير حول المكان المُلائم لوضع الحالة في التسلسل الهرمي للمُكوِّنات. يتضح عادةً أنّ المكان المناسب لوضع الحالة هو في المستويات العليا من التسلسل الهرمي للمُكوِّنات.». إجبار إعادة تصيير العنصر من المرجّح أنَّ: لاحظتَ أنَّ استدعاء ReactDOM.render()‎ هو عملية التحميل الابتدائية للمكوِّن وجميع المكونات الفرعية له. وبعد عملية التركيب الابتدائية للمكوِّنات، ستحدث إعادة التصيير عندما: استدعي التابع setState()‎ في المكوِّن استدعي التابع forceUpdate()‎ في المكوِّن في أي وقت يجير إعادة تصيير المكوِّن (أو يُصيّر التصيير الابتدائي) فستُصيّر جميع المكوِّنات الأبناء التابعة له داخل شجرة DOM الافتراضية والتي قد تُسبِّب تغييرًا في شجرة DOM الحقيقية (أي واجهة المستخدم). ما أريد إيصاله لك هنا هو أنَّ إعادة تصيير أحد المكوِّنات في شجرة DOM الافتراضية لا يعني بالضرورة أنَّ تحديثًا سيطرأ على شجرة DOM الحقيقية. في المثال أدناه، سيبدأ استدعاء ReactDOM.render(< App / >, app);‎ عملية التصيير، والذي سيؤدي إلى تصيير و ، عملية إعادة التصيير التالية ستحدث عند استخدام setInterval()‎ لتابع المكوِّن setState()‎ الذي سيؤدي إلى إعادة تصيير و ، لاحظ كيف ستتغير واجهة المستخدم عند تغيير حالة now: var Timer = createReactClass({ render: function() { return ( <div>{this.props.now}</div> ) } }); var App = createReactClass({ getInitialState: function() { return {now: Date.now()}; }, componentDidMount: function() { var foo = setInterval(function() { this.setState({now: Date.now()}); }.bind(this), 1000); setTimeout(function(){ clearInterval(foo); }, 5000); // .forceUpdate() لا تفعل هذا! هذا مجرد مثال توضيحي عن استخدام setTimeout(function(){ this.state.now = 'foo'; this.forceUpdate() }.bind(this), 10000); }, render: function() { return ( <Timer now={this.state.now}></Timer> ) } }); ReactDOM.render(< App />, app); تحدث عملية إعادة التصيير الآتية عند تشغيل setTimout()‎ واستدعاء التابع this.forceUpdate()‎ لاحظ أنَّ تحديث الحالة (this.state.now = 'foo';‎) لن تؤدي بمفردها إلى إعادة التصيير. ضبنا بداية الحالة باستخدام this.state ثم استدعينا this.forceUpdate()‎ لكي يعاد تصيير العنصر مع الحالة الجديدة. ملاحظات لا تُحدِّث الحالة باستخدام this.state إطلاقًا! فعلنا ذلك في المثال السابق لتبيين طريقة استخدام this.forceUpdate()‎ فحسب. ترجمة -وبتصرف- للفصل Basic React Components من كتاب React Enlightenment table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; }
  6. سنتحدث في هذا المقال عن كيفية ضبط صفحة HTML لكي يمكن تفسيرها في متصفح الويب، وسيتمكن المتصفح في وقت التنفيذ من تحويل تعابير JSX ويشغِّل شيفرة React بنجاح. استخدام react.js و react-dom.js في صفحة HTML الملف react.js هو الملف الأساسي الذي نحتاج له لإنشاء عناصر React وكتابة مكونات React. عندما ترغب بعرض المكونات التي أنشأتها في مستند HTML (أي كتابتها إلى DOM) فستحتاج أيضًا إلى الملف react-dom.js. يعتمد الملف react-dom.js على الملف react.js ويجب تضمينه بعد تضمين الملف react.js. مثالٌ عن مستند HTML يحتوي على React: <!DOCTYPE html> <html> <head> <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script> </head> <body> </body> </html> بتحميل الملفين react.js و react-dom.js في صفحة HTML، أصبح بإمكاننا إنشاء عقد ومكونات React ثم عرضها في DOM. المثال البسيط الآتي يُنشِئ مكوِّن HelloMessage يحتوي على عقدة العنصر <div> ثم سيُعرَض في شجرة DOM داخل عنصر HTML المُعرَّف <div id="app"></div>: <!DOCTYPE html> <html> <head> <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script> <script src="https://unpkg.com/create-react-class@15.6.0-rc.0/create-react-class.js" crossorigin></script> </head> <body> <div id="app"></div> <script> var HelloMessage = createReactClass({ // استخدمنا هذه الوحدة لترى كيف تستعمل displayName: 'HelloMessage', render: function render() { return React.createElement('div',null,'Hello ',this.props.name); } }); ReactDOM.render(React.createElement(HelloMessage,{ name: 'Ahmed' }), document.getElementById('app')); </script> </body> </html> هذا كل ما تحتاج له لاستخدام React. لكنه لن يُمكِّنك من استخدام JSX، والتي سنناقشها في القسم التالي. ملاحظات لا تجعل العنصر <body> العنصر الأساسي في تطبيق React. احرص دومًا على وضع عنصر ؤ داخل <body>، وأعطه ID، ثم صيِّر (render) المكونات داخله. وهذا يعطي React مجالًا خاصًا بها لتجري التعديلات، دون أن تقلق عن أي شيءٍ آخر يجري تعديلات على العناصر الأبناء للعنصر <body>. استخدام JSX عبر Babel عملية إنشاء المكوِّن HelloMessage وعنصر <div> في المثال الآتي جرت باشتقاق الصنف React.Component واستخدام الدالة React.createElement()‎. يُفترَض أن تبدو الشيفرة الآتية مألوفةً لأنها مماثلة لشيفرة HTML من القسم السابق: <!DOCTYPE html> <html> <head> <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script> </head> <body> <div id="app"></div> <script> class HelloMessage extends React.Component { render(){ return React.createElement('div', null, 'Hello ', this.props.name); } }; ReactDOM.render(React.createElement(HelloMessage, { name: 'Ahmed' }), document.getElementById('app')); </script> </body> </html> يمكنك أن تستخدم JSX اختياريًا عبر Babel، فمن الممكن تبسيط عملية إنشاء عناصر React بتجريد استدعاءات الدالة React.createElement()‎ ونكتبها بطريقة تشبه طريقة كتابة عناصر HTML. فبدلًا من كتابة الشيفرة الآتية التي تستخدم الدالة React.createElement()‎: return React.createElement('div', null, 'Hello ', this.props.name); يمكننا أن نكتب ما يلي باستخدام JSX: return <div>Hello {this.props.name}</div>; ثم باستخدام Babel نستطيع تحويلها إلى الشيفرة التي تستخدم React.createElement()‎ لكي يتمكن محرِّك JavaScript من تفسيرها. يمكننا القول مجازًا أنَّ JSX هي شكلٌ من أشكال HTML التي تستطيع كتابتها مباشرةً ضمن شيفرة JavaScript والتي تحتاج إلى خطوة إضافية هي التحويل، وتجرى عملية التحويل باستخدام Babel إلى شيفرة ECMAScript 5 لكي تتمكن المتصفحات من تشغيلها. بعبارةٍ أخرى، سيحوِّل Babel شيفرة JSX إلى استدعاءات للدالة React.createElement()‎. سنتحدث عن المزيد من تفاصيل JSX في القسم المخصص لها، لكننا الآن يمكننا أن نعدّ JSX على أنها طبقة تجريد اختيارية توفَّر للسهولة عند إنشاء عناصر React، ولن تعمل في متصفحات ES5 ما لم نحوِّلها بدايةً باستخدام Babel. تحويل JSX عبر Babel في المتصفح يُضبَط عادةً Babel لكي يُعالِج ملفات JavaScript أثناء عملية التطوير باستخدام أداة سطر الأوامر الخاصة به (عبر استخدام Webpack على سبيل المثال)؛ لكن من الممكن استخدام Babel مباشرةً في المتصفح بتضمين سكربت معيّن. ولمّا كنّا في بداياتنا فسنتفادى استخدام الأدوات التي تعمل من سطر الأوامر أو نتعلم استخدام مُحمِّل للوحدات، وذلك لكي ننطلق في React. استخدام browser.js لتحويل JSX في المتصفح حوّلنا مكوِّن React في مستند HTML الآتي إلى صياغة JSX، وستحدث عملية التحويل بسبب تضيمننا لملف babel.min.js وإعطائنا لعنصر <script> الخاصية type مع القيمة text/``b``abel: <!DOCTYPE html> <html> <head> <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js" crossorigin></script> </head> <body> <div id="app"></div> <script type="text/babel"> class HelloMessage extends React.Component { // React.Component لاحظ استخدام render(){ return <div>Hello {this.props.name}</div>; } }; ReactDOM.render(<HelloMessage name="Ahmed" />, document.getElementById('app')); /*** سابقًا ***/ /* var HelloMessage = createReactClass({ * render: function() { * return <div>Hello {this.props.name}</div>; * } * }); * * ReactDOM.render(<HelloMessage name="Ahmed" />, document.getElementById('app')); */ </script> </body> </html> تحويلنا لشيفرة JSX في المتصفح هو حلٌ سهلٌ وبسيط، لكنه ليس مثاليًا لأنَّ عملية التحويل تجري في وقت التشغيل، وبالتالي استخدام babel.min.js ليس حلًا عمليًا يمكن استخدامه في البيئات الإنتاجية. ملاحظات أداة Babel هي اختيار المطورين من فريق React لتحويل شيفرات ES*‎ وصيغة JSX إلى شيفرة ES5. تعلّم المزيد حول Babel بمراجعة توثيقه. باستخدام صيغة JSX: أصبح بإمكان الأشخاص غير المتخصصين تقنيًا فهم وتعديل الأجزاء المطلوبة. فيجد مطورو CSS والمصممون صيغة JSX أكثر ألفةً من شيفرة JavaScript. يمكنك استثمار كامل قدرات JavaScript في HTML وتتجنب تعلّم أو استخدام لغة خاصة بالقوالب. لكن اعلم أن JSX ليس محرّك قوالب، وإنما صيغة تصريحية للتعبير عن البنية الهيكلية الشجرية لمكونات UI. سيجد المُصرِّف (compiler) أخطاءً في شيفرة HTML الخاصة بك كنتَ ستغفل عنها. تحث صياغة JSX على فكر استخدام الأنماط السطرية (inline styles) وهو أمرٌ حسن. اعرف محدوديات JSX. تجري كتابة مواصفة JSX كمسودة لكي تُستخدَم من أي شخص كإضافة لصياغة ECMAScript. استخدام ES6 و ES*‎ مع React Babel ليس جزءًا من React، وليس الغرض من إنشاء Babel هو تحويل JSX، وإنما أُنشِئ كمُصرِّف JavaScript (‏compiler) بادئ الأمر. إذ يأخذ شيفرة ES‎ ويحوِّلها لكي تعمل على المتصفحات التي لا تدعم شيفرة ES‎. في هذه الآونة، يستخدم Babel أساسيًا لتحويل شيفرات ES6 و ES7 إلى ES5. عند إجراء عمليات التحويل هذه فمن البسيط تحويل تعابير JSX إلى استدعاءات React.createElement()‎. وبجانب تحويل Babel لشيفرات JSX، فيسمح أيضًا تحويل الشيفرات التي تعمل في إصدارات مستقبلية من ES*‎. مستند HTML الآتي يحتوي على مكوِّن HelloMessage مع إعادة كتابته لكي يستفيد من ميزة الأصناف في ES6. فلن يحوِّل Babel صيغة JSX فحسب، بل سيحوِّل صيغة أصناف ES6 إلى صيغةٍ تستطيع المتصفحات التي تحتوي محرِّك ES5 تفسيرها: <!DOCTYPE html> <html> <head> <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js" crossorigin></script> </head> <body> <div id="app"></div> <script type="text/babel"> class HelloMessage extends React.Component { // React.Component لاحظ استخدام render(){ return <div>Hello {this.props.name}</div>; } }; ReactDOM.render(<HelloMessage name="John" />, document.getElementById('app')); /*** سابقًا ***/ /* var HelloMessage = createReactClass({ * render: function() { * return <div>Hello {this.props.name}</div>; * } * }); * * ReactDOM.render(<HelloMessage name="John" />, document.getElementById('app')); */ </script> </body> </html> يأخذ Babel في المستند السابق الشيفرةَ الآتية: class HelloMessage extends React.Component { render(){ return <div>Hello {this.props.name}</div>; } }; ReactDOM.render(<HelloMessage name="John" />, document.getElementById('app')); ويحولها إلى: "use strict"; var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var HelloMessage = (function (_React$Component) { _inherits(HelloMessage, _React$Component); function HelloMessage() { _classCallCheck(this, HelloMessage); _get(Object.getPrototypeOf(HelloMessage.prototype), "constructor", this).apply(this, arguments); } _createClass(HelloMessage, [{ key: "render", value: function render() { return React.createElement( "div", null, "Hello ", this.props.name ); } }]); return HelloMessage; })(React.Component); ; ReactDOM.render(React.createElement(HelloMessage, { name: "John" }), document.getElementById('app')); أغلبية ميزات ES6 (مع بعض الاستثناءات الطفيفة) يمكن تحويلها باستخدام Babel. ملاحظات بالطبع ما يزال بإمكاننا استخدام Babel لغرضه الأساسي (وهو تصريف شيفرات JavaScript الحديثة إلى شيفرة JavaScript القديمة) دون استخدام JSX. لكن أغلبية المطورين الذين يستعملون Babel يستفيدون من قدرته على تحويل JSX إضافةً إلى ميزات ES*‎ غير المدعومة في المتصفحات القديمة. يمكنك مراجعة توثيق Babel لمزيدٍ من المعلومات. تجربة React في JSFiddle يمكن استخدام نفس الضبط الذي أجريناه في هذا المقال مع JSFilddle. إذ تُستعمَل نفس الملفات (react.js و react-dom.js و babel.min.js) لجعل تنفيذ تطبيقات React سهلًا‎.‎ هذا مثالٌ عن JSFiddle يحتوي على مكوِّن HelloMessage المستخدم في هذا المقال. لاحظ أنَّ لسان Babel يشير إلى أنَّ شيفرة JavaScript الموجودة في هذا اللسان ستحوَّل باستخدام Babel. وإذا ضغطتَ على لسان Resources ستجد أنَّ JSFiddle يُضمِّن الملفين react.js و react-dom.js. سيُفتَرَض بعد قراءتك لهذا المقال أنَّك تدرك ما هي المتطلبات الأساسية لإعداد React و Babel عبر babel.min.js، وصحيحٌ أنَّ JSFiddle لا يُصرِّح بوضوح عن طريقة إعداده، لكنه يستخدم نفس الضبط السابق (أو ما يشبهه) لتشغيل شيفرات React. ترجمة وبتصرف للفصل React Setup‎ من كتاب React Enlightenment
  7. React.js هي مكتبة JavaScript التي يمكن أن تستخدم لبناء واجهات المستخدم؛ فباستخدام React يمكن للمستخدمين إنشاء مكوِّنات قابلة لإعادة الاستخدام، وهذه المكونات تظهر البيانات أثناء تغيِّرها مع الزمن. يسمح لنا React Native بإنشاء تطبيقات أصيلة للهواتف الذكية باستخدام React. بكلمات أخرى، React هي أداة في JavaScript التي تُسهِّل إنشاء وصيانة واجهات المستخدم ذات الحالة (stateful) وعديمة الحالة (stateless)، وتوفر القدرة على تعريف وتقسيم واجهة المستخدم إلى مكوِّنات منفصلة (تسمى أيضًا بمكونات React) باستخدام عقد شبيهة بلغة HTML تسمى عقد React (أي React nodes). ستتحول عقد React في النهاية إلى صيغة قابلة للعرض في واجهات المستخدم (مثل HTML/DOM أو canvas أو SVG …إلخ.). يمكنني أن أستفيض بالشرح محاولًا تعريف React باستخدام الكلمات، لكنني أظن أنَّ من الأفضل أن أريك ما تفعله. لا تحاول أن تفهم كل التفاصيل الدقيقة أثناء شرحي لما بقي من هذا المقال، فالغرض من بقية هذه السلسلة أن تشرح لك بالتفصيل ما سيرد في هذه المقدمة. استخدام React لإنشاء مكونات شبيهة بعنصر ‎select>‎> ما يلي هو عنصر <select> يحتوي على عناصر <option>. لحسن الحظ، الغرض من العنصر <select> معروفٌ لديك: <select size="4"> <option>Volvo</option> <option>Saab</option> <option selected>Mercedes</option> <option>Audi</option> </select> عندما يفسِّر المتصفح الشجرة السابقة من العناصر فسيُنتِج واجهة مستخدم تحتوي على قائمة نصية من العناصر التي يمكن اختيارها. أما في المتصفح، فشجرة DOM وشجرة DOM الظل (shadow DOM) تعملان معًا خلف الكواليس لتحويل العنصر <select> إلى مكوِّن UI. لاحظ أنَّ المكوِّن <select> يسمح للمستخدم باختيار أحد العناصر وبالتالي سيُخزِّن حالة ذاك الاختيار (أي انقر على Volvo وستختارها بدلًا من Mercedes). يمكننا باستخدام React إنشاء مكوِّن <select> خاص بنا باستخدام عقد React لإنشاء مكوِّن React والذي في النهاية سيُنتِج عناصر HTML في شجرة DOM. لنُنشِئ مكوِّنًا خاصًا بنا شبيهًا بالعنصر <select> باستخدام React. تعريف مكون React (أي React Component) سنُنشِئ فيما يلي مكوِّن React باشتقاق الصنف (class‎) ‏React.Component لإنشاء المكوِّن MySelect. كما ترى، المكوِّن MySelect مُنشَأ من عدِّة أنماط إضافةً إلى عقدة <div> فارغة: class MySelect extends React.Component { // MySelect تعريف المكوِّن render(){ var mySelectStyle = { border: '1px solid #999', display: 'inline-block', padding: '5px' }; // JSX استخدام {} للإشارة إلى متغير جافاسكربت داخل // JSX باستخدام <div> سنعيد عنصر return <div style={mySelectStyle}></div>; } }; العنصر <div> السابق شبيهٌ بعناصر HTML العادية، وهو موجودٌ داخل شيفرة JavaScript التي تسمى JSX! صيغة JSX هي صياغة JavaScript اختيارية ومخصصة التي تستخدمها مكتبة React للتعبير عن عقد React التي يمكن أن ترتبط مع عناصر HTML حقيقية، أو عناصر مخصصة، أو عقد نصية. علينا ألّا نفترض أنَّ عقد React المُعرَّفة باستخدام JSX مماثلة تمامًا لعناصر HTML، فهنالك بعض الاختلافات بينها، وبعض القصور أيضًا. يجب تحويل صياغة JSX إلى شيفرات JavaScript حقيقية التي يمكن تفسيرها من محركات ECMAScript 5، فإن لم تحوّل الشيفرة السابقة فستسبب خطأً في JavaScript. الأداة الرسمية لتحويل شيفرات JSX إلى شيفرات JavaScript تسمى Babel. بعد أن يحوِّل Babel العنصر <div> في الشيفرة السابقة إلى شيفرة JavaScript فستبدو كما يلي: return React.createElement('div', { style: mySelectStyle }); بدلًا من: return <div style={mySelectStyle}></div>; في الوقت الحالي، ضع في ذهنك أنَّه عندما تكتب عناصر شبيهة بعناصر HTML في شيفرة React فستحوَّل في نهاية المطاف إلى شيفرة JavaScript حقيقية، إضافةً إلى تحويل أي شيفرة مكتوبة تحتوي على ميزات ECMAScript 6 وما بعدها إلى ECMAScript 5. المكوِّن <MySelect> يحتوي -عند هذه النقطة- على عقدة <div> فارغة فقط، أي أنَّ مكوِّن دون أي فائدة، لذا دعونا نغيِّر ذلك. سنُعرِّف مكونًا آخر باسم <MyOption> وسنستخدم المكوِّن <MyOption> داخل المكوِّن <MySelect> (ويسمى ذلك التركيب أي composition). تفحَّص شيفرة JavaScript المُحدَّثة الآتية التي تُعرِّف كلًا من مكونَي <MySelect> و <MyOption>: class MySelect extends React.Component { // MySelect تعريف المكوِّن render(){ var mySelectStyle = { border: '1px solid #999', display: 'inline-block', padding: '5px' }; // JSX استخدام {} للإشارة إلى متغير جافاسكربت داخل // <MyOption> يحتوي على المكون JSX باستخدام <div> إعادة عنصر return ( <div style={mySelectStyle}> <MyOption value="Volvo"></MyOption> <MyOption value="Saab"></MyOption> <MyOption value="Mercedes"></MyOption> <MyOption value="Audi"></MyOption> </div> ); } }; class MyOption extends React.Component { // MyOption تعريف المكون render(){ // JSX باستخدام <div> إعادة عنصر return <div>{this.props.value}</div>; } }; يفترض أنَّك لاحظت وجود المكوِّن <MyOption> داخل المكوِّن <MySelect> والذين أنشأناهما باستخدام JSX. تمرير خيارات المكون باستخدام خاصيات React لاحظ أنَّ المكوِّن <MyOption> يتألف من عنصر <div> يحتوي على التعبير {this.props.value}. تُستخدَم الأقواس المعقوفة {} داخل JSX للإشارة أنَّ محتواها هو تعبيرٌ صالحٌ في JavaScript. بعبارة أخرى، يمكننا أن نكتب شيفرات JavaScript عادية داخل القوسين {}. استخدمنا القوسين {} للوصول إلى الخاصيات المُمرَّرة إلى المكوِّن <MyOption>. بعبارةٍ أخرى، عندما يعرض المكوِّن <MyOption> فستوضع قيمة الخيار value التي جرى تمريرها عبر خاصيةٍ شبيهةٍ بخاصيات HTML (أي value="Volvo"‎) داخل عنصر <div>. هذه الخاصيات التي تشبه خاصيات HTML تسمى خاصيات React، وتستخدمها مكتبة React لتمرير الخيارات التي لا تتغير إلى المكوِّنات، ومرَّرنا في مثالنا الخاصية value إلى المكوِّن <MyOption>، والأمر لا يختلف عن تمرير وسيط إلى دالة JavaScript، وهذا ما تفعله JSX خلف الكواليس. تصيير (Render) مكوِّن إلى شجرة DOM الافتراضية (Virtual DOM) ثم إلى شجرة DOM في هذه المرحلة، عرَّفنا مكوِّنين من مكونات React، لكننا لم نصيِّرها إلى شجرة DOM الافتراضية ومنها إلى شجرة HTML DOM. قبل أن نفعل ذلك، أود أن أشير إلى أنَّ كل ما فعلناه هو تعريف مكونين باستخدام JavaScript. وكل ما فعلناه -نظريًا- هو تعريف مكونات UI، وليس من الضروري أن تذهب هذه المكونات إلى شجرة DOM أو حتى إلى شجرة DOM الافتراضية (Virtual DOM). ويمكننا -نظريًا- أن نصيّر (render) هذه المكونات إلى منصة من منصات الهواتف الذكية أو إلى العنصر <canvas>)، لكننا لن نفعل ذلك هنا. تذكّر أنَّ استخدام React يمنحنا تنظيمًا لعناصر واجهة المستخدم التي يمكن تحويلها إلى شجرة DOM أو تطبيقاتٍ أخرى. لنصيّر الآن المكوِّن <MySelect> إلى شجرة DOM الافتراضية والتي بدورها ستصيّر إلى شجرة DOM الأساسية داخل صفحة HTML. في شيفرة JavaScript التالية، ستلاحظ أننا أضفنا استدعاءً للدالة ReactDOM.render()‎ في آخر سطر، ومررنا إلى الدالة ReactDOM.render()‎ المكوِّن الذي نريد تصييره (وهو <MySelect>) ومرجعية إلى عنصر HTML موجودٌ في شجرة HTML DOM (وهو <div id="app"></div>) الذي نريد عرض المكوِّن <MySelect> فيه. class MySelect extends React.Component { // MySelect تعريف المكوِّن render(){ var mySelectStyle = { border: '1px solid #999', display: 'inline-block', padding: '5px' }; // JSX استخدام {} للإشارة إلى متغير جافاسكربت داخل // <MyOption> يحتوي على المكون JSX باستخدام <div> إعادة عنصر return ( <div style={mySelectStyle}> <MyOption value="Volvo"></MyOption> <MyOption value="Saab"></MyOption> <MyOption value="Mercedes"></MyOption> <MyOption value="Audi"></MyOption> </div> ); } }; class MyOption extends React.Component { // MyOption تعريف المكون render(){ // JSX باستخدام <div> إعادة عنصر return <div>{this.props.value}</div>; } }; ReactDOM.render(<MySelect />, document.getElementById('app')); لاحظ أنَّ كل ما فعلناه هو إخبار React أين ستبدأ بتصيير المكونات وما هي المكونات التي عليها بدء التصيير بها. بعد ذلك ستصيّر React أيّة مكونات محتواة داخل المكوِّن الأصلي (مثل المكوِّن <MyOption> داخل <MySelect>). انتظر لحظة! ربما تفكِّر الآن أننا لم نُنشِئ العنصر <select> أصلًا، وكل ما فعلناه هو إنشاء قائمة ثابتة عديم الحالة من السلاسل النصية. سنصلح ذلك في الخطوة القادمة. قبل أن نكمل إلى الخطوة القادمة، أحب أن أشير إلى عدم وجود أي تعاملات ضمنية مع شجرة DOM لكي نعرض المكوِّن في شجرة DOM. بعبارةٍ أخرى، لم نستدعِ شيفرة jQuery أثناء إنشاء هذا المكوِّن؛ وجميع التعاملات مع شجرة DOM الفعلية قد أصبحت مجردةً (abstract) عبر استعمال شجرة DOM الافتراضية الخاصة بمكتبة React. في الواقع، عندما نستخدم React فما نفعله هو وصف شجرة DOM الافتراضية التي تأخذها React وتحوِّلها إلى شجرة DOM الفعلية لنا. استخدام حالة React (أي React state) لكي نجعل عنصر <MySelect> الخاص بنا يحاكي عنصر <select> الأصلي في HTML فعلينا أن نضيف حالةً (state) له. فما فائدة عنصر <select> المخطط إذا لم يكن قادرًا على الاحتفاظ بقيمة الاختيار الذي اخترناه. تأتي الحالة (state) عندما يحتوي المكوِّن على نسخة من المعلومات. وبخصوص عنصر <MyOption> المخصص، الحالة هي النص المختار حاليًا أو عدم وجود نص مختار من الأساس. لاحظ أنَّ الحالة تتضمن عادةً أحداثًا تابعة للمستخدم (مثل الفأرة أو لوحة المفاتيح أو حافظة النسخ …إلخ.) أو أحداثًا تابعة للشبكة (أي AJAX) وتستخدم قيمتها لتحديد إن كانت واجهة المستخدم للمكوِّن تحتاج إلى إعادة تصيير (re-render، فتغيير القيمة سيؤدي إلى إعادة التصيير). ترتبط الحالة عادةً بأعلى مكوِّن الذي يُنشِئ مكوِّن UI. كنا في السابق نستخدم الدالة getInitialState()‎ في React لنستطيع ضبط الحالة الافتراضية، فلو أردنا ضبط حالة المكون إلى false (أي لا يوجد أي نص مختار) فسنعيد كائن حالة عند استدعاء الدالة getInitialState()‎ (أي return {selected: false};‎). دورة حياة الدالة getInitialState()‎ هي استدعاء الدالة مرةً قبل تركيب المكوِّن، وستُستخدَم القيمة المعادة منها كقيمة افتراضية للخاصية this.state. الطريقة السابقة قديمة ولم تعد مستخدمةً إلا إذا كنتَ من محبي الوحدة create-react-class والتي سنأتي على ذكرها لاحقًا، أما في الأصناف في ES6، فنحن نستعمل this.state ضمن الدالة البانية (constructor) للصنف الخاص بالمكون. أي أننا سنكتب في الدالة البانية للصنف MySelect تعريفًا للحالة التي نريدها. هذه نسخة مُحدَّثة من الشيفرة أضفنا فيها الحالة إلى المكوِّن، أنصحك بقراءة التعليقات التي أضعها في الشيفرة والتي تجذب انتباهك إلى التغييرات التي حدثت في الشيفرة. class MySelect extends React.Component { constructor(){ // إضافة الحالة الافتراضية super(); this.state = {selected: false}; // this.state.selected = false; } render(){ var mySelectStyle = { border: '1px solid #999', display: 'inline-block', padding: '5px' }; return ( <div style={mySelectStyle}> <MyOption value="Volvo"></MyOption> <MyOption value="Saab"></MyOption> <MyOption value="Mercedes"></MyOption> <MyOption value="Audi"></MyOption> </div> ); } }; class MyOption extends React.Component { render(){ return <div>{this.props.value}</div>; } }; ReactDOM.render(<MySelect />, document.getElementById('app')); بعد ضبط الحالة الافتراضية، سنستدعي دالة رد نداء (callback function) باسم select التي ستُطلَق عندما يضغط المستخدم على خيارٍ ما. داخل هذه الدالة سنحصل على نص الخيار الذي اختاره المستخدم (عبر المعامل event) وسنستخدمه لضبط الحالة setState للمكوِّن الحالي. لاحظ أننا نستخدم تفاصيل الكائن event المُمرَّر إلى دالة رد النداء select. يُفترَض أنَّ هذا النمط من البرمجة مألوفٌ لديك إن كانت لديك أيّ خبرة مع مكتبة jQuery من قبل. من أهم ما يجب ملاحظته في الشيفرة الآتية هو اتباع التوابع في مكوّنات React المُعرَّفة كأصناف ES6 لنفس القواعد في أصناف ES6 الاعتيادية، يعني هذا أنّها لا تربط this بنسخة الكائن، بل يجب عليك أن تستخدم بشكل صريح التابع ‎.bind(this)‎ في الدالة البانية: class MySelect extends React.Component { constructor(){ // إضافة الحالة الافتراضية super(); this.state = {selected: false}; // this.state.selected = false; this.select = this.select.bind(this); // هذا السطر مهم، راجع الشرح أعلاه } select(event){ // select إضافة الدالة if(event.target.textContent === this.state.selected){ // إزالة التحديد this.setState({selected: false}); // تحديث الحالة }else{ // إضافة التحديد this.setState({selected: event.target.textContent}); // تحديث الحالة } } render(){ var mySelectStyle = { border: '1px solid #999', display: 'inline-block', padding: '5px' }; return ( <div style={mySelectStyle}> <MyOption value="Volvo"></MyOption> <MyOption value="Saab"></MyOption> <MyOption value="Mercedes"></MyOption> <MyOption value="Audi"></MyOption> </div> ); } }; class MyOption extends React.Component { render(){ return <div>{this.props.value}</div>; } }; ReactDOM.render(<MySelect />, document.getElementById('app')); ولكي تحصل مكوِّنات <MyOption> على وصولٍ للدالة select فمررنا إشارةً مرجعيةً إليها عبر خاصيات React (‏props) من المكوِّن <MySelect> إلى المكوِّن <MyOption>. ولفعل ذلك أضفنا select={this.select}‎ إلى مكونات <MyOption>. بعد ضبط ما سبق، يمكننا إضافة onClick={this.props.select}‎ إلى المكوِّن <MyOption>. أرجو أن يكون واضحًا أنَّ ما فعلناه هو ربط الحدث click الذي سيستدعي الدالة select. تتكفّل React بربط دالة التعامل مع حدث النقر الحقيقي في شجرة DOM نيابةً عنّا. class MySelect extends React.Component { constructor(){ super(); this.state = {selected: false}; this.select = this.select.bind(this); } select(event){ if(event.target.textContent === this.state.selected){ this.setState({selected: false}); }else{ this.setState({selected: event.target.textContent}); } } render(){ var mySelectStyle = { border: '1px solid #999', display: 'inline-block', padding: '5px' }; return ( <div style={mySelectStyle}> <MyOption select={this.select} value="Volvo"></MyOption> <MyOption select={this.select} value="Saab"></MyOption> <MyOption select={this.select} value="Mercedes"></MyOption> <MyOption select={this.select} value="Audi"></MyOption> </div> ); } }; class MyOption extends React.Component { render(){ return <div onClick={this.props.select}>{this.props.value}</div>; } }; ReactDOM.render(<MySelect />, document.getElementById('app')); بعد فعلنا لذلك، يمكننا الآن ضبط الحالة بالنقر على أحد الخيارات؛ وبعبارةٍ أخرى، عندما تنقر على خيارٍ ما فستستدعى الدالة select وتضبط حالة المكوِّن <MySelect>. لكن مستخدم المكوِّن لن يعرف أبدًا أنَّ ذلك قد حصل لأنَّ كل ما فعلناه حتى الآن هو تغيير حالة المكوِّن، ولا توجد أي تغذية بصرية تشير إلى اختيار أي عنصر. لذا لنصلح ذلك. ما علينا فعله الآن هو تمرير الحالة الراهنة إلى المكوِّن <MyOption> لكي يستجيب -بصريًا- إلى تغيير حالة المكوِّن. باستخدام الخاصيات عبر props، سنُمرِّر الحالة selected من المكوِّن <MySelect> إلى المكوِّن <MyOption> بوضع الخاصية state={this.state.selected}‎ في جميع مكونات <MyOption>. أصبحنا نعلم الآن ما هي الحالة (أي this.props.state) والقيمة الحالية (أي this.props.value) للخيار لكي نتحقق إذا كانت الحالة تُطابِق القيمة الموجودة في مكوِّن <MyOption> ما. وإذا كانت تطابقها، فسنعلم أنَّه يجب تحديد هذا الخيار، وسنفعل ذلك باستخدام عبار if بسيطة التي تضيف أنماط تنسيق (selectedStyle) إلى عنصر <div> في JSX إذا كانت الحالة تُطابِق قيمة الخيار الحالي. وفيما عدا ذلك، سنعيد عنصر React مع النمط unSelectedStyle: class MySelect extends React.Component { constructor(){ super(); this.state = {selected: false}; this.select = this.select.bind(this); } select(event){ if(event.target.textContent === this.state.selected){ this.setState({selected: false}); }else{ this.setState({selected: event.target.textContent}); } } render(){ var mySelectStyle = { border: '1px solid #999', display: 'inline-block', padding: '5px' }; return ( <div style={mySelectStyle}> <MyOption state={this.state.selected} select={this.select} value="Volvo"></MyOption> <MyOption state={this.state.selected} select={this.select} value="Saab"></MyOption> <MyOption state={this.state.selected} select={this.select} value="Mercedes"></MyOption> <MyOption state={this.state.selected} select={this.select} value="Audi"></MyOption> </div> ); } }; class MyOption extends React.Component { render(){ var selectedStyle = {backgroundColor:'red', color:'#fff',cursor:'pointer'}; var unSelectedStyle = {cursor:'pointer'}; if(this.props.value === this.props.state){ return <div style={selectedStyle} onClick={this.props.select}>{this.props.value}</div>; }else{ return <div style={unSelectedStyle} onClick={this.props.select}>{this.props.value}</div>; } } }; ReactDOM.render(<MySelect />, document.getElementById('app')); صحيحٌ أنَّ عنصر <select> الذي أنشأناه ليس جميلًا أو كاملًا كما كنتَ ترجو، لكنني أظن أنَّك ترى ما الغرض الذي حققناه. مكتبة React تسمح لك بالتفكير بالعناصر بطريقة منظمة ومهيكلة هيكليةً صحيحة. قبل الانتقال إلى شرح دور شجرة DOM الافتراضية، أود أنَّ أوضِّح أنَّه من غير الضروري استخدام JSX و Babel. يمكنك تخطي هذه الأدوات واستخدام شيفرات JavaScript مباشرة. سأريك نسخةً أخيرةً من الشيفرة بعد تحويل JSX باستخدام Babel. إذا لم ترغب باستخدام JSX فيمكنك أن تكتب الشيفرة الآتية يدويًا بدلًا من الشيفرة التي كتبناها خلال هذا المقال: class MySelect extends React.Component { constructor() { super(); this.state = { selected: false }; this.select = this.select.bind(this); } select(event) { if (event.target.textContent === this.state.selected) { this.setState({ selected: false }); } else { this.setState({ selected: event.target.textContent }); } } render() { var mySelectStyle = { border: '1px solid #999', display: 'inline-block', padding: '5px' }; return React.createElement("div", { style: mySelectStyle }, React.createElement(MyOption, { state: this.state.selected, select: this.select, value: "Volvo" }), React.createElement(MyOption, { state: this.state.selected, select: this.select, value: "Saab" }), React.createElement(MyOption, { state: this.state.selected, select: this.select, value: "Mercedes" }), React.createElement(MyOption, { state: this.state.selected, select: this.select, value: "Audi" })); } }; class MyOption extends React.Component { render() { var selectedStyle = { backgroundColor: 'red', color: '#fff', cursor: 'pointer' }; var unSelectedStyle = { cursor: 'pointer' }; if (this.props.value === this.props.state) { return React.createElement("div", { style: selectedStyle, onClick: this.props.select }, this.props.value); } else { return React.createElement("div", { style: unSelectedStyle, onClick: this.props.select }, this.props.value); } } }; ReactDOM.render(React.createElement(MySelect, null), document.getElementById('app')); فهم دور شجرة DOM الافتراضية (virtual DOM) سأنهي جولتنا بأكثر جوانب React حديثًا بين المطورين، إذ سأتحدث عن شجرة DOM الافتراضية (React virtual DOM). لاحظنا -عبر الأمثلة في هذا المقال- أنَّ التعامل الوحيد مع شجرة DOM الحقيقية أثناء إنشائنا لعنصر <select> خاص بنا هو عندما أخبرنا الدالة ReactDOM.render()‎ أين ستعرض مكوِّنات UI في صفحة HTML (أي عندما عرضناها في <div id="app"></div>). من المرجح أن يكون هذا تعاملك الوحيد مع شجرة DOM الحقيقية عندما تبني تطبيق React من شجرة من المكوِّنات. وهنا تأتي قيمة مكتبة React. فعند استخدامك لها، ليس عليك أن تفكر بشجرة DOM بنفس الطريقة التي كنتَ تفكِّر فيها عند كتابتك لشيفرة jQuery. فمكتبة React تستبدل jQuery عبر تجريد استخدام شجرة DOM. ولأنَّ شجرة DOM الافتراضية حلّت محل شجرة DOM الحقيقية، سمح ذلك بإجراء تحديثات لشجرة DOM الحقيقية مع أداءٍ ممتاز. تبقي شجرة DOM الافتراضية سجلًا بجميع التغيرات في واجهة المستخدم اعتمادًا على الحالة والخاصيات (state و props)، ثم تقارنها بشجرة DOM الحقيقية وتجري أقل مقدار ممكن من التعديلات عليها. بصيغةٍ أخرى، لا تُحدَّث شجرة DOM الحقيقية إلا بأقل قدر ممكن وذلك عند تغيير الحالة أو الخاصيات. صراحةً، هذا المفهوم ليس ثوريًا أو جديدًا، يمكنك فعل المثل باستخدام شيفرة jQuery مكتوبة بعناية، لكنك لن تحتاج إلى التفكير بهذه الأمور عند استخدام React. فشجرة DOM الافتراضية تجري عمليات تحسين الأداء عوضًا عنك، فلا حاجة لأن تقلق حول أي شيء، فكل ذلك يحدث وراء الكواليس ونادرًا ما تحتاج إلى التعامل مع شجرة DOM الحقيقية نفسها. أرغب أن أنهي هذه المقدمة بالقول أنَّ استخدام React يلغي تقريبًا الحاجة إلى استخدام أي مكتبات أخرى مثل jQuery. واستخدام شجرة DOM الافتراضية يريحنا من كثيرٍ من التفاصيل الدقيقة، لكن قيمة مكتبة React لا تكمن في شجرة DOM الافتراضية فقط، وإنما يمكننا أن نعدّ شجرة DOM الافتراضية على أنها الفستق الحلبي المبشور فوق الحلوى؛ فببساطة، قيمة مكتبة React تكون في أنها توفِّر طريقةً سهلةً الإنشاء والصيانة لإنشاء شجرة من مكوِّنات الواجهة الرسومية. ‎تخيل بساطة إنشاء واجهة رسومية إذا بنيتَ تطبيقك باستخدام مكوِّنات React القابلة لإعادة الاستخدام. تذكر هذه السلسلة عندما تريد أن تعرِّف ما هي React. مكتبة React.js هي مكتبة JavaScript التي يمكن استخدامها لبناء واجهات المستخدم، وباستخدام React يمكن للمطورين إنشاء مكونات قابلة لإعادة الاستخدام، وهذه المكونات تُظهِر البيانات وتستطيع تغييرها مع الزمن؛ وتوجد أيضًا مكتبة React Native لبناء تطبيقات للهواتف الذكية باستخدام React. ترجمة وبتصرف للفصل What is React?‎ من كتاب React Enlightenment
  8. قبل أن نتعرف سويةً على آلية عمل React، دعنا نتعرف بدايةً على بعض المصطلحات التي ستسهِّل عملية التعليم. سأورد قائمةً من المصطلحات الشائعة، إضافةً إلى تعريفاتها، وسنستعمل هذه المصطلحات أثناء حديثنا عن React. Babel يحوِّل Babel شيفرة JavaScript ES‎ (أي JS 2015 و JS 2016 و JS 2017) إلى شيفرة ES5. إنَّ Babel هي أداة من اختيار مطوري React لكتابة شيفرات ES‎ وتحويل JSX إلى شيفرة ES5. Babel CLI يأتي Babel مع أداة تعمل من سطر الأوامر تسمى Babel CLI، ويمكن أن تستخدم لبناء الملفات من سطر الأوامر. خيارات ضبط المكونات (Component Configuration Options) وهي وسائط الضبط التي تُمرَّر (ككائن) إلى الدالة React.createClass()‎ أو الدالة البانية (في حال كنت تستعمل الأصناف في ES6) مما ينتج نسخةً (instance) من مكوِّن React. توابع دورة حياة المكونات (Component Life Cycle Methods) توابع دورة حياة المكونات هي مجموعة فرعية من أحداث المكونات، المفصولة (اصطلاحيًا) عن الخيارات الأخرى لضبط المكونات، أي هي: componentWillUnmount componentDidUpdate UNSAFE_componentWillUpdate shouldComponentUpdate UNSAFE_componentWillReceiveProps componentDidMount UNSAFE_componentWillMount تُنفَّذ هذه التوابع في نقاط مُحدَّدة من دورة حياة المكوِّن. شجرة DOM‏ (Document Object Model) شجرة DOM هي الواجهة البرمجية لمستندات HTML و XML و SVG، وهي توفِّر تمثيلًا هيكليًا للمستند كشجرة. تُعرِّف DOM الدوال التي تسمح بالوصول إلى الشجرة، لذا يمكنها تغيير بنية المستند وأنماط التنسيق التابعة له ومحتواه. توفِّر DOM تمثيلًا للمستند على شكل مجموعة مهيكلة من العقد (nodes) والكائنات (objects)، والتي تملك مختلف الخاصيات (properties) والتوابع (methods). يمكن أن تملك العقد معالجات أحداث (event handlers) مرتبطة بها، وستستدعى تلك المعالجات عند تفعيل الحدث. باختصار، تصل شجرة DOM بين صفحات الويب ولغات البرمجة. ES5 الإصدار الخامس من معيار ECMAScript. ES6 أو ECMAScript 2015 الإصدار السادس من معيار ECMAScript، والذي يحتوي على إضافات كثيرة على لغة JavaScript. ES7 أو ECMAScript 2016 الإصدار السابع من معيار ECMAScript. ES*‎ تستخدم لتمثيل النسخة الحالية من JavaScript إضافةً إلى الإصدارات المستقبلية، والتي يمكن الكتابة بها باستخدام أدوات مثل Babel. عندما ترى «ES*‎» فمن المرجح أنَّ المقصود بها هو ES5 و ES6 و ES7 معًا. JSX JSX هي صيغة إضافية اختيارية تشبه XML لمعيار ECMAScript التي يمكن أن تُستخدم لتعريف بنية شجريّة شبيهة بلغة HTML في ملفات JavaScript. ستحوَّل تعابير JSX في ملف JavaScript إلى صياغة JavaScript قبل أن يتمكن محرِّك JavaScript من تفسير الملف. تُستَخدم برمجية Babel عادةً لتحويل تعابير JSX. Node.js Node.js هي بيئة تشغيل مفتوحة المصدر ومتعددة المنصات لكتابة شيفرات JavaScript. بيئة التشغيل Node.js تُفسِّر شيفرات JavaScript باستخدام محرِّك V8. npm npm هو مدير حزم للغة JavaScript نَشَأ من مجتمع Node.js. خاصيات React (أي React props) يمكنك أن تعدّ الخاصيات (props) على أنها خيارات الضبط لعقد React، وفي نفس الوقت يمكنك أن تتخيلها كخاصيات HTML. تملك الخاصيات عدِّة أدوار: يمكن أن تصبح خاصيات HTML، فلو طابقت خاصيةٌ ما إحدى خاصيات HTML فستُضاف كخاصية HTML في شجرة DOM النهائية. الخاصيات المُمرَّرة إلى الدالة createElement()‎ تصبح قيمًا مخزنةً في الكائن prop كنسخة (instance) من React.createElement()‎ أي ‎[INSTANCE].props.[NAME_OF_PROP]‎ تستخدم الخاصيات بكثرة لتمرير قيم إلى المكونات. بعض الخاصيات لها تأثيرات جانبية (مثل key و ref و dangerouslySetInnerHTML). React React هي مكتبة JavaScript تُستخدم لكتابة واجهات للمستخدمة بمرونة وكفاءة وفعالية عالية. مكوِّن React يُنشَأ مكوِّن React باستدعاء الوحدة create-react-class (أو React.Component عند استخدام الأصناف في ES6). هذه الدالة تأخذ كائنًا من الخيارات الذي يُستخدَم لضبط وإنشاء مكونات React. أحد أشهر خيارات الضبط هو الدالة render التي تعيد عقد React أي React nodes. والتالي يمكننك أن تعدّ مكوِّن React على أنه تجريد (abstraction) يحتوي على مكوِّن أو عقدة React واحد أو أكثر. الوحدة create-react-class توفر هذه الوحدة طريقة لإنشاء مكونات React دون إنشاء صنف جديد وجعله مشتقًا من الصنف React.Component. هذه الوحدة موجودة لكتابة شيفرات React.js دون ES6. عقد عناصر React عقد عناصر React (‏React Element Nodes أو ReactElement) هو تمثيل يشبه عناصر HTML في شجرة DOM يُنشَأ باستخدام React.createElement()‎;‎. عقد React عقد React (‏React Nodes أي عقد العناصر والعقد النصية) هو نوع الكائنات الرئيسي في React ويمكن إنشاؤه باستخدام React.createElement('div');‎. بعبارةٍ أخرى، عقد React هي كائنات تُمثِّل عقد DOM وعقد DOM الأبناء التابعة لها. وهي تمثيلٌ خفيفٌ وعديم الحالة (stateless) وغير قابلٍ للتعديل (immutable) لعقدة DOM. مصانع عقد React مصانع عقد React (‏React Node Factories) هي دالة تولِّد عقد عنصر React ذات نوعٍ (type) مُحدَّد. كانت هذه الخاصية موجودة في إصدارات سابقة من React.js ثم أهملت. دالة مكون React عديم الحالة أي React Stateless Function Component، وتكون عندما يتألف المكوِّن من الخاصيات فقط، دون حالة، ويمكن أن يكتب المكوِّن كدالة نقية مما يجعلنا نتجنب إنشاء نسخة من مكوِّن React. var MyComponent = function(props){ return <div>Hello {props.name}</div>; }; ReactDOM.render(<MyComponent name="Ahmed" />, app); عقد عناصر React عقد عناصر React (‏React Text Nodes، أي ReactText) هي تمثيل للعقد النصية في شجرة DOM الوهمية كما في React.createElement('div', null, 'a text node');‎. شجرة DOM الوهمية (Virtual DOM) Virtual DOM هي شجرة مخزَّنة في ذاكرة JavaScript لعناصر ومكونات React وتُستخدَم لإعادة تصيير (re-render) شجرة DOM بكفاءة عالية (بمعرفة الاختلافات بينها وبين شجرة DOM الحقيقية). Webpack Webpack هو مُحمِّل للوحدات (modules) والمجمِّع (bundler) لها، والذي يأخذ الوحدات (‎.js أو ‎.css أو ‎.txt …إلخ.) مع اعتمادياتها ويولِّد وسائط ساكنة (static assets) تُمثِّل هذه الوحدات. ترجمة -وبتصرف- للفصل React Semantics‎ من كتاب React Enlightenment
×
×
  • أضف...