عبد اللطيف ايمش

الأعضاء
  • المساهمات

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

  • تاريخ آخر زيارة

  • Days Won

    63

السُّمعة بالموقع

272 Excellent
  1. دليل تعلم react

    سنناقش في هذا الدرس حالة المكوِّنات (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 أساسية. ما هي مكونات 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; }
  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 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; }
  6. دليل تعلم react

    سنتحدث في هذا المقال عن كيفية ضبط صفحة 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

    قبل أن نتعرف سويةً على آلية عمل 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
  8. دليل تعلم react

    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
  9. يسمح لك استخدام حلقات for أو while في بايثون بأتمتة وتكرار المهام بطريقة فعّالة. لكن في بعض الأحيان، قد يتدخل عامل خارجي في طريقة تشغيل برنامجك، وعندما يحدث ذلك، فربما تريد من برنامجك الخروج تمامًا من حلقة التكرار، أو تجاوز جزء من الحلقة قبل إكمال تنفيذها، أو تجاهل هذا العامل الخارجي تمامًا. لذا يمكنك فعل ما سبق باستخدام تعابير break و continue و pass. التعبير break يوفِّر لك التعبير break القدرة على الخروج من حلقة التكرار عند حدوث عامل خارجي. حيث عليك وضع التعبير break في الشيفرة التي ستُنفَّذ في كل تكرار للحلقة، ويوضع عادةً ضمن تعبير if. ألقِ نظرةً إلى أحد الأمثلة الذي يستعمل التعبير break داخل حلقة for: number = 0 for number in range(10): number = number + 1 if number == 5: break # break here print('Number is ' + str(number)) print('Out of loop') هذا برنامجٌ صغيرٌ، هيّأنا في بدايته المتغير number بجعله يساوي الصفر، ثم بنينا حلقة تكرار for التي تعمل لطالما كانت قيمة المتغير number أصغر من 10. ثم قمنا بزيادة قيمة المتغير number داخل حلقة for بمقدار 1 في كل تكرار، وذلك في السطر number = number + 1. ثم كانت هنالك عبارة if التي تختبر إن كان المتغير number مساوٍ للرقم 5، وعند حدوث ذلك فسيُنفَّذ التعبير break للخروج من الحلقة. وتوجد داخل حلقة التكرار الدالة print()‎ التي تُنفَّذ في كل تكرار إلى أن نخرج من الحلقة عبر التعبير break، وذلك لأنَّها موجودة بعد التعبير break. لكي نتأكد أننا خرجنا من الحلقة، فوضعنا عبارة print()‎ أخيرة موجودة خارج حلقة for. سنرى الناتج الآتي عند تنفيذ البرنامج: Number is 1 Number is 2 Number is 3 Number is 4 Out of loop الناتج السابق يُظهِر أنَّه بمجرد أن أصبح العدد الصحيح number مساويًا للرقم 5، فسينتهي تنفيذ حلقة التكرار عبر التعبير break. الخلاصة: التعبير break يؤدي إلى الخروج من حلقة التكرار. التعبير continue التعبير continue يسمح لنا بتخطي جزء من حلقة التكرار عند حدوث عامل خارجي، لكن إكمال بقية الحلقة إلى نهايتها. بعبارةٍ أخرى: سينتقل تنفيذ البرنامج إلى أوّل حلقة التكرار عند تنفيذ التعبير continue. يجب وضع التعبير continue في الشيفرة التي ستُنفَّذ في كل تكرار للحلقة، ويوضع عادةً ضمن تعبير if. سنستخدم نفس البرنامج الذي استعملناها لشرح التعبير break أعلاه، لكننا سنستخدم التعبير continue بدلًا من break: number = 0 for number in range(10): number = number + 1 if number == 5: continue # continue here print('Number is ' + str(number)) print('Out of loop') الفرق بين استخدام التعبير continue بدلًا من break هو إكمال تنفيذ الشيفرة بغض النظر عن التوقف الذي حدث عندما كانت قيمة المتغير number مساويةً إلى الرقم 5. لننظر إلى الناتج: Number is 1 Number is 2 Number is 3 Number is 4 Number is 6 Number is 7 Number is 8 Number is 9 Number is 10 Out of loop نلاحظ أنَّ السطر الذي يجب أن يحتوي على Number is 5 ليس موجودًا في المخرجات، لكن سيُكمَل تنفيذ حلقة التكرار بعد هذه المرحلة مما يطبع الأرقام من 6 إلى 10 قبل إنهاء تنفيذ الحلقة. يمكنك استخدام التعبير continue لتفادي استخدام تعابير شرطية معقدة ومتشعّبة، أو لتحسين أداء البرنامج عن طريق تجاهل الحالات التي ستُرفَض نتائجها. الخلاصة: التعبير continue سيؤدي إلى جعل البرنامج يتجاهل تنفيذ حلقة التكرار عند تحقيق شرط معين، لكن بعدئذٍ سيُكمِل تنفيذ الحلقة كالمعتاد. التعبير pass التعبير pass يسمح لنا بالتعامل مع أحد الشروط دون إيقاف عمل حلقة التكرار بأي شكل، أي ستُنفَّذ جميع التعابير البرمجية الموجودة في حلقة التكرار ما لم تستعمل تعابير مثل break أو continue فيها. وكما هو الحال مع التعابير السابقة، يجب وضع التعبير pass في الشيفرة التي ستُنفَّذ في كل تكرار للحلقة، ويوضع عادةً ضمن تعبير if. سنستخدم نفس البرنامج الذي استعملناها لشرح التعبير break أو continue أعلاه، لكننا سنستخدم التعبير pass هذه المرة: number = 0 for number in range(10): number = number + 1 if number == 5: pass # pass here print('Number is ' + str(number)) print('Out of loop') التعبير pass الذي يقع بعد العبارة الشرطية if يخبر البرنامج أنَّ عليه إكمال تنفيذ الحلقة وتجاهل مساواة المتغير number للرقم 5. لنشغِّل البرنامج ولننظر إلى الناتج: Number is 1 Number is 2 Number is 3 Number is 4 Number is 5 Number is 6 Number is 7 Number is 8 Number is 9 Number is 10 Out of loop لاحظنا عند استخدامنا للتعبير pass في هذا البرنامج أنَّ البرنامج يعمل كما لو أننا لم نضع عبارة شرطية داخل حلقة التكرار؛ حيث يخبر التعبير pass البرنامج أن يكمل التنفيذ كما لو أنَّ الشرط لم يتحقق. يمكن أن تستفيد من التعبير pass عندما تكتب برنامجك لأوّل مرة أثناء تفكيرك بحلّ مشكلة ما عبر خوارزمية، لكن قبل أن تضع التفاصيل التقنية له. الخلاصة تسمح لك التعابير break و continue و pass باستعمال حلقات for و while بطريقةٍ أكثر كفاءة. ترجمة –وبتصرّف– للمقال How To Use Break, Continue, and Pass Statements when Working with Loops in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا الدرس التالي: كيفية تعريف الدوال في بايثون 3 الدرس السابق: كيفية إنشاء حلقات تكرار for في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون
  10. يسمح لنا استخدام حلقات التكرار في برمجة الحاسوب بأتمتة وتكرار المهام المتشابهة مرّاتٍ عدِّة. وسنشرح في هذا الدرس كيفية استخدام حلقة for في بايثون. حلقة for تؤدي إلى تكرار تنفيذ جزء من الشيفرات بناءً على عدّاد أو على متغير، وهذا يعني أنَّ حلقات for تستعمل عندما يكون عدد مرات تنفيذ حلقة التكرار معلومًا قبل الدخول في الحلقة، وذلك على النقيض من حلقات while المبنية على شرط. حلقات for تُبنى حلقات for في بايثون كما يلي: for [iterating variable] in [sequence]: [do something] ستُنفَّذ الشيفرات الموجودة داخل حلقة التكرار عدِّة مرات إلى أن تنتهي الحلقة. لننظر إلى كيفية مرور الحلقة for على مجالٍ من القيم: for i in range(0,5): print(i) سيُخرِج البرنامج السابق عند تشغيله الناتج الآتي: 0 1 2 3 4 ضبطنا المتغير i في حلقة for ليحتوي على القيمة التي ستُنفَّذ عليها حلقة التكرار، وكان مجال القيم التي ستُسنَد إلى هذا المتغير من 0 إلى 5. ثم طبعًا قيمة المتغير في كل دوران لحلقة التكرار، لكن أبقِ في ذهنك أنَّنا نميل إلى بدء العد من الرقم 0 في البرمجة، وعلى الرغم من عرض خمسة أرقام، لكنها تبدأ بالرقم 0 وتنتهي بالرقم 4. من الشائع أن ترى استخدامًا لحلقة for عندما تحتاج إلى تكرار كتلة معيّنة من الشيفرات لعددٍ من المرات. استخدام حلقات التكرار مع الدالة range()‎ إحدى أنواع السلاسل غير القابلة للتعديل في بايثون هي تلك الناتجة من الدالة range()‎، وتستخدم الدالة range()‎ في حلقات التكرار للتحكم بعدد مرات تكرار الحلقة. عند التعامل مع الدالة range()‎ عليك أن تمرر معاملًا رقميًا أو معاملين أو ثلاثة معاملات: start يشير إلى القيم العددية الصيحية التي ستبدأ بها السلسلة، وإذا لم تُمرَّر قيمة لهذا المعامل فستبدأ السلسلة من 0 stop هذا المعامل مطلوب دومًا وهو القيمة العددية الصحيحة التي تمثل نهاية السلسلة العددية لكن دون تضمينها step هي مقدار الخطوة، أي عدد الأرقام التي يجب زيادتها (أو إنقاصها إن كنّا نتعامل مع أرقام سالبة) في الدورة القادمة، وقيمة المعامل step تساوي 1 في حال لم تُحدَّد له قيمة لننظر إلى بعض الأمثلة التي نُمرِّر فيها مختلف المعاملات إلى الدالة range()‎. لنبدأ بتمرير المعامل stop فقط، أي أنَّ السلسلة الآتية من الشكل range(stop): for i in range(6): print(i) كانت قيمة المعامل stop في المثال السابق مساويةً للرقم 6، لذا ستمر حلقة التكرار من بداية المجال 0 إلى نهايته 6 (باستثناء الرقم 6 كما ذكرنا أعلاه): 0 1 2 3 4 5 المثال الآتي من الشكل range(start ,stop) الذي تُمرَّر قيم بدء السلسلة ونهايتها: for i in range(20,25): print(i) المجال –في المثال السابق– يتراوح بين 20 (بما فيها الرقم 20) إلى 25 (باستثناء الرقم 25)، لذا سيبدو الناتج كما يلي: 20 21 22 23 24 الوسيط step الخاص بالدالة range()‎ شبيه بمعامل الخطوة الذي نستعمله عند تقسيم [السلاسل النصية](آلية فهرسة السلاسل النصية وطريقة تقسيمها في بايثون 3) لأنه يستعمل لتجاوز بعض القيم ضمن السلسلة. يأتي المعامل step في آخر قائمة المعاملات التي تقبلها الدالة range()‎ وذلك بالشكل الآتي range(start, stop, step). لنستعمل المعامل step مع قيمة موجبة: for i in range(0,15,3): print(i) سيؤدي المثال السابق إلى إنشاء سلسلة من الأرقام التي تبدأ من 0 وتنتهي عند 15 لكن قيمة المعامل step هي 3، لذا سيتم تخطي رقمين في كل دورة، أي سيكون الناتج كالآتي: 0 3 6 9 12 يمكننا أيضًا استخدام قيمة سالبة للمعامل step للدوران إلى الخلف، لكن علينا تعديل قيم start و stop بما يتوافق مع ذلك: for i in range(100,0,-10): print(i) قيمة المعامل start في المثال السابق هي 100، وكانت قيمة المعامل stop هي 0، والخطوة هي ‎-10، لذا ستبدأ السلسلة من الرقم 100 وستنتهي عند الرقم 0، وسيكون التناقص بمقدار 10 في كل دورة، ويمكننا ملاحظة ذلك في الناتج الآتي: 100 90 80 70 60 50 40 30 20 10 الخلاصة: عندما نبرمج باستخدام لغة بايثون، فسنجد أننا نستفيد كثيرًا من السلاسل الرقمية التي تنتجها الدالة range()‎. استخدام حلقة for مع أنواع البيانات المتسلسلة يمكن الاستفادة من القوائم (من النوع list) وغيرها من أنواع البيانات المتسلسلة واستعمالها كمعاملات لحلقات for، فبدلًا من الدوران باستخدام الدالة range()‎ فيمكننا تعريف قائمة ثم الدوران على عناصرها. سنُسنِد في المثال الآتي قائمةً إلى متغير، ثم سنستخدم حلقة for للدوران على عناصر القائمة: sharks = ['hammerhead', 'great white', 'dogfish', 'frilled', 'bullhead', 'requiem'] for shark in sharks: print(shark) في هذه الحالة، قمنا بطباعة كل عنصر موجود في القائمة؛ وصحيحٌ أننا استعملنا الكلمة shark كاسم للمتغير، لكن يمكنك استعمال أي اسم صحيح آخر ترغب به، وستحصل على نفس النتيجة: hammerhead great white dogfish frilled bullhead requiem الناتج السابق يُظهِر دوران الحلقة for على جميع عناصر القائمة مع طباعة كل عنصر في سطرٍ منفصل. يشيع استخدام القوائم والأنواع الأخرى من البيانات المتسلسلة مثل السلاسل النصية وبنى tuple مع حلقات التكرار لسهولة الدوران على عناصرها. يمكنك دمج هذه الأنواع من البيانات مع الدالة range()‎ لإضافة عناصر إلى قائمة، مثلًا: sharks = ['hammerhead', 'great white', 'dogfish', 'frilled', 'bullhead', 'requiem'] for item in range(len(sharks)): sharks.append('shark') print(sharks) الناتج: ['hammerhead', 'great white', 'dogfish', 'frilled', 'bullhead', 'requiem', 'shark', 'shark', 'shark', 'shark', 'shark', 'shark'] أضفنا هنا السلسلة النصية 'shark' خمس مرات (وهو نفس طول القائمة sharks الأصلي) إلى القائمة sharks. يمكننا استخدام حلقة for لبناء قائمة جديدة: integers = [] for i in range(10): integers.append(i) print(integers) هيّئنا في المثال السابق قائمةً فارغةً باسم integers لكن حلقة التكرار for ملأت القائمة لتصبح كما يلي: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] وبشكلٍ شبيهٍ بما سبق، يمكننا الدوران على السلاسل النصية: sammy = 'Sammy' for letter in sammy: print(letter) الناتج: S a m m y يمكن الدوران على بنى tuple كما هو الحال في القوائم والسلاسل النصية. عند المرور على عناصر نوع البيانات dictionary، فمن المهم أن تبقي بذهنك البنية الخاصة به (key:value) لكي تضمن أنَّك تستدعي العنصر الصحيح من المتغير. هذا مثالٌ بسيطٌ نعرض فيه المفتاح (key) والقيمة (value): sammy_shark = {'name': 'Sammy', 'animal': 'shark', 'color': 'blue', 'location': 'ocean'} for key in sammy_shark: print(key + ': ' + sammy_shark[key]) الناتج: name: Sammy animal: shark location: ocean color: blue عند استخدام متغيرات من النوع dictionary مع حلقات for فيكون المتغير المرتبط بحلقة التكرار متعلقًا بمفتاح القيم، وعلينا استخدام التعبير dictionary_variable[iterating_variable] للوصول إلى القيمة الموافقة للمفتاح. ففي المثال السابق كان المتغير المرتبط بحلقة التكرار باسم key وهو يُمثِّل المفاتيح، واستعملنا التعبير sammy_shark[key] للوصول إلى القيمة المرتبطة بذاك المفتاح. الخلاصة: تُستعمَل حلقات التكرار عادةً للدوران على عناصر البيانات المتسلسلة وتعديلها. حلقات for المتشعّبة يمكن تشعّب حلقات التكرار في بايثون، كما هو الحال في بقية لغات البرمجة. حلقة التكرار المتشعبة هي الحلقة الموجودة ضمن حلقة تكرار أخرى، وهي شبيهة بعبارات if المتشعّبة. تُبنى حلقات التكرار المتشعبة كما يلي: for [first iterating variable] in [outer loop]: # Outer loop [do something] # Optional for [second iterating variable] in [nested loop]: # Nested loop [do something] يبدأ البرنامج بتنفيذ حلقة التكرار الخارجية، ويُنفَّذ أوّل دوران فيها، وأوّل دوران سيؤدي إلى الدخول إلى حلقة التكرار الداخلية، مما يؤدي إلى تنفيذها إلى أن تنتهي تمامًا. ثم سيعود تنفيذ البرنامج إلى بداية حلقة التكرار الخارجية، ويبدأ بتنفيذ الدوران الثاني، ثم سيصل التنفيذ إلى حلقة التكرار الداخلية، وستُنفَّذ حلقة التكرار الداخلية بالكامل، ثم سيعود التنفيذ إلى بداية حلقة التكرار الخارجية، وهلّم جرًا إلى أن ينتهي تنفيذ حلقة التكرار الخارجية أو إيقاف حلقة التكرار عبر استخدام [التعبير break](كيفية استخدام تعابير break و continue و pass عند التعامل مع حلقات التكرار في بايثون 3) أو غيره من التعابير. لنُنشِئ مثالًا يستعمل حلقة forمتشعبة لكي نفهم كيف تعمل بدقة. حيث ستمر حلقة التكرار الخارجية في المثال الآتي على قائمة من الأرقام اسمها num_list، أما حلقة التكرار الداخلية فستمر على قائمة من السلاسل النصية اسمها alpha_list: num_list = [1, 2, 3] alpha_list = ['a', 'b', 'c'] for number in num_list: print(number) for letter in alpha_list: print(letter) سيظهر الناتج الآتي عند تشغيل البرنامج: 1 a b c 2 a b c 3 a b c يُظهِر الناتج السابق أنَّ البرنامج أكمل أوّل دوران على عناصر حلقة التكرار الخارجية بطباعة الرقم 1، ومن ثم بدأ تنفيذ حلقة التكرار الدخلية مما يطبع الأحرف a و b و c على التوالي. وبعد انتهاء تنفيذ حلقة التكرار الداخلية، فعاد البرنامج إلى بداية حلقة التكرار الخارجية طابعًا الرقم 2، ثم بدأ تنفيذ حلقة التكرار الداخلية (مما يؤدي إلى إظهار a و b و c مجددًا). وهكذا. يمكن الاستفادة من حلقات for المتشعبة عند المرور على عناصر قوائم تتألف من قوائم. فلو استعملنا حلقة تكرار وحيدة لعرض عناصر قائمة تتألف من عناصر تحتوي على قوائم، فستُعرَض قيم القوائم الداخلية: list_of_lists = [['hammerhead', 'great white', 'dogfish'],[0, 1, 2],[9.9, 8.8, 7.7]] for list in list_of_lists: print(list) الناتج: ['hammerhead', 'great white', 'dogfish'] [0, 1, 2] [9.9, 8.8, 7.7] وفي حال أردنا الوصول إلى العناصر الموجودة في القوائم الداخلية، فيمكننا استعمال حلقة for متشعبة: list_of_lists = [['hammerhead', 'great white', 'dogfish'],[0, 1, 2],[9.9, 8.8, 7.7]] for list in list_of_lists: for item in list: print(item) الناتج: hammerhead great white dogfish 0 1 2 9.9 8.8 7.7 الخلاصة: نستطيع الاستفادة من حلقات for المتشعبة عندما نريد الدوران على عناصر محتوى في قوائم. الخلاصة رأينا في هذا الدرس كيف تعمل حلقة التكرار for في لغة بايثون، وكيف نستطيع إنشاءها واستعمالها. حيث تستمر حلقة for بتنفيذ مجموعة من الشيفرات لعددٍ مُحدِّدٍ من المرات. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة –وبتصرّف– للمقال How To Construct For Loops in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا الدرس التالي: كيفية استخدام تعابير break و continue و pass عند التعامل مع حلقات التكرار في بايثون 3 الدرس السابق: كيفية إنشاء حلقات تكرار while في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون
  11. نستفيد من البرامج الحاسوبية خيرَ استفادة في أتمتة المهام وإجراء المهام التكرارية لكيلا نحتاج إلى القيام بها يدويًا، وإحدى طرائق تكرار المهام المتشابهة هي استخدام حلقات التكرار، وسنشرح في درسنا هذا حلقة تكرار while. حلفة تكرار while تؤدي إلى تكرار تنفيذ قسم من الشيفرة بناءً على متغير منطقي (boolean)، وسيستمر تنفيذ هذه الشيفرة لطالما كانت نتيجة التعبير المستعمل معها تساوي true. يمكنك أن تتخيل أنَّ حلقة while هي عبارة شريطة تكرارية، فبعد انتهاء تنفيذ العبارة الشرطية if فيُستَكمَل تنفيذ بقية البرنامج، لكن مع حلقة while فسيعود تنفيذ البرنامج إلى بداية الحلقة بعد انتهاء تنفيذها إلى أن يصبح الشرط مساويًا للقيمة false. وعلى النقيض من حلقات for التي تُنفَّذ عدد معيّن من المرات، فسيستمر تنفيذ حلقات while اعتمادًا على شرطٍ معيّن، لذا لن تحتاج إلى عدد مرات تنفيذ الحلقة قبل إنشائها. حلقة while الشكل العام لحلقات while في لغة بايثون كالآتي: while [a condition is True]: [do something] سيستمر تنفيذ التعليمات البرمجية الموجودة داخل الحلقة إلى أن يصبح الشرط false. لنُنشِئ برنامجًا صغيرًا فيه حلقة while، ففي هذه البرنامج سنطلب من المستخدم إدخال كلمة مرور. وهنالك خياران أمام حلقة التكرار: - إما أن تكون كلمة المرور صحيحة، فعندها سينتهي تنفيذ حلقة while. - أو أن تكون كلمة المرور غير صحيحة، فعندها سيستمر تنفيذ حلقة التكرار. لنُنشِئ ملفًا باسم password.py في محررنا النصي المفضَّل، ولنبدأ بتهيئة المتغير paasword بإسناد سلسلة نصية فارغة إليه: password = '' نستخدم المتغير السابق للحصول على مدخلات المستخدم داخل حلقة التكرار while. علينا بعد ذلك إنشاء حلقة while مع تحديد ما هو الشرط الذي يجب تحقيقه: password = '' while password != 'password': أتبَعنا –في المثال السابق– الكلمة المحجوزة while بالمتغير password، ثم سنتحقق إذا كانت قيمة المتغير password تساوي السلسلة النصية 'password' (لا تنسَ أنَّ قيمة المتغير سنحصل عليها من مدخلات المستخدم)، يمكنك أن تختار أي سلسلة نصية تشاء لمقارنة مدخلات المستخدم بها. هذا يعني أنَّه لو أدخل المستخدم السلسلة النصية password فستتوقف حلقة التكرار وسيُكمَل تنفيذ البرنامج وستُنفَّذ أيّة شيفرات خارج الحلقة، لكن إذا أدخل المستخدم أيّة سلسلة نصية لا تساوي password فسيُكمَل تنفيذ الحلقة. علينا بعد ذلك إضافة الشيفرة المسؤولة عمّا يحدث داخل حلقة while: password = '' while password != 'password': print('What is the password?') password = input() نفَّذ البرنامج عبارة print داخل حلقة while والتي تسأل المستخدم عن كلمة مروره، ثم أسندنا قيمة مدخلات المستخدم (التي حصلنا عليها عبر الدالة input()‎) إلى المتغير password. سيتحقق البرنامج إذا كانت قيمة المتغير password تساوي السلسلة النصية 'password'، وإذا تحقق ذلك فسينتهي تنفيذ حلقة while. لنضف سطرًا آخر إلى البرنامج لنعرف ماذا يحدث إن أصبحت قيمة الشرط مساويةً إلى false: password = '' while password != 'password': print('What is the password?') password = input() print('Yes, the password is ' + password + '. You may enter.') لاحظ أنَّ آخر عبارة print()‎ موجودة خارج حلقة while، لذا عندما يُدخِل المستخدم الكلمة password عند سؤاله عن كلمة مروره، فستُطبَع آخر جملة والتي تقع خارج حلقة التكرار. لكن ماذا يحدث لو لم يدخل المستخدم الكلمة password قط؟ حيث لن يستمر تنفيذ البرنامج ولن يروا آخر عبارة print()‎ وسيستمر تنفيذ حلقة التكرار إلى ما لا نهاية! يستمر تنفيذ حلقة التكرار إلى ما لا نهاية إذا بقي تنفيذ البرنامج داخل حلقة تكرار دون الخروج منها. وإذا أردتَ الخروج من حلقة تكرار نهائية، فاضغط Ctrl+C في سطر الأوامر. احفظ البرنامج ثم شغِّله: python password.py سيُطلَب منك إدخال كلمة المرور، ويمكنك تجربة ما تشاء من الكلمات. هذا مثالٌ عن ناتج البرنامج: What is the password? hello What is the password? sammy What is the password? PASSWORD What is the password? password Yes, the password is password. You may enter. أبقِ في ذهنك أنَّ السلاسل النصية حساسة لحالة الأحرف إلا إذا استعملتَ دالةً من دوال النصوص لتحويل السلسلة النصية إلى حالة الأحرف الصغيرة (على سبيل المثال) قبل التحقق منها. مثال عن برنامج يستخدم حلقة while بعد أن تعلمنا المبدأ الأساسي لحلقة تكرار while، فلنُنشِئ لعبة تعمل على سطر الأوامر لتخمين الأرقام والتي تستعمل الحلقة while . نريد من الحاسوب أن يُنشِئ أرقامًا عشوائيةً لكي يحاول المستخدمون تخمينها، لذا علينا استيراد الوحدة random عبر استخدام العبارة import، وإذا لم تكن هذه الحزمة مألوفةً لك فيمكنك قراءة المزيد من المعلومات عن توليد الأرقام العشوائية في توثيق بايثون. لنُنشِئ بدايةً ملفًا باسم guess.py في محررك النصي المفضَّل: import random علينا الآن إسناد عدد صحيح عشوائي إلى المتغير number، ولنجعل مجاله من 1 إلى 25 (بما فيها تلك الأرقام) كيلا نجعل اللعبة صعبة جدًا. import random number = random.randint(1, 25) يمكننا الآن إنشاء حلقة while، وذلك بتهيئة متغير ثم كتابة الحلقة: import random number = random.randint(1, 25) number_of_guesses = 0 while number_of_guesses < 5: print('Guess a number between 1 and 25:') guess = input() guess = int(guess) number_of_guesses = number_of_guesses + 1 if guess == number: break هيئنا متغيرًا اسمه number_of_guesses قيمته 0، وسوف نزيد قيمته عند كل تكرار للحلقة لكي لا تصبح حلقتنا لا نهائية. ثم سنضيف تعبير while الذي يشترط ألّا تزيد قيمة المتغير number_of_guesses عن 5. وبعد المحاولة الخامسة سيُعاد المستخدم إلى سطر الأوامر، وإذا حاول المستخدم إدخال أيّ شيء غير رقمي فسيحصل على رسالة خطأ. أضفنا داخل حلقة while عبارة print()‎ لطلب إدخال رقم من المستخدم، ثم سنأخذ مدخلات المستخدم عبر الدالة input()‎ ونُسنِدَها إلى المتغير guess، ثم سنحوِّل المتغير guess من سلسلة نصية إلى عدد صحيح. وقبل انتهاء حلقة التكرار، فعلينا زيادة قيمة المتغير number_of_guesses بمقدار 1، لكيلا تُنفَّذ حلقة التكرار أكثر من 5 مرات. وفي النهاية، كتبنا عبارة if شرطية لنرى إذا كان المتغير guess الذي أدخله المستخدم مساوٍ للرقم الموجود في المتغير number الذي ولَّده الحاسوب، وإذا تحقق الشرط فسنستخدم عبارة break للخروج من الحلقة. أصبح البرنامج جاهزًا للاستخدام، ويمكننا تشغيله عبر تنفيذ الأمر: python guess.py صحيحٌ أنَّ البرنامج يعمل عملًا سليمًا، لكن المستخدم لن يعلم إذا كان تخمينه صحيحًا ويمكنه أن يخمِّن الرقم خمس مرات دون أن يعلم إذا كانت إحدى محاولاته صحيحة. هذا مثال عن مخرجات البرنامج: Guess a number between 1 and 25: 11 Guess a number between 1 and 25: 19 Guess a number between 1 and 25: 22 Guess a number between 1 and 25: 3 Guess a number between 1 and 25: 8 لنضف بعض العبارات الشرطية خارج حلقة التكرار لكي يحصل المستخدم على معلومات فيما إذا استطاعوا تخمين الرقم أم لا، وسنضيف هذه العبارات في نهاية الملف: import random number = random.randint(1, 25) number_of_guesses = 0 while number_of_guesses < 5: print('Guess a number between 1 and 25:') guess = input() guess = int(guess) number_of_guesses = number_of_guesses + 1 if guess == number: break if guess == number: print('You guessed the number in ' + str(number_of_guesses) + ' tries!') else: print('You did not guess the number. The number was ' + str(number)) في هذه المرحلة سيُخبِر البرنامجُ المستخدمَ إذا استطاعوا تخمين الرقم، لكن ذلك لن يحدث إلا بعد انتهاء حلقة التكرار وبعد انتهاء عدد مرات التخمين المسموحة. ولمساعد المستخدم قليلًا، فلنضف بعض العبارات الشرطية داخل حلقة while وتلك العبارات ستخبر المستخدم إذا كان تخمينه أعلى من الرقم أو أصغر منه، لكي يستطيعوا تخمين الرقم بنجاح، وسنضيف تلك العبارات الشرطية قبل السطر الذي يحتوي على if guess == number: import random number = random.randint(1, 25) number_of_guesses = 0 while number_of_guesses < 5: print('Guess a number between 1 and 25:') guess = input() guess = int(guess) number_of_guesses = number_of_guesses + 1 if guess < number: print('Your guess is too low') if guess > number: print('Your guess is too high') if guess == number: break if guess == number: print('You guessed the number in ' + str(number_of_guesses) + ' tries!') else: print('You did not guess the number. The number was ' + str(number)) وعندما نُشغِّل البرنامج مرةً أخرى بتنفيذ python guess.py، فيمكننا ملاحظة أنَّ المستخدم سيحصل على بعض المساعدة، فلو كان الرقم المولَّد عشوائيًا هو 12 وكان تخمين المستخدم 18، فسيُخبره البرنامج أنَّ الرقم الذي خمنه أكبر من الرقم العشوائي، وذلك لكي يستطيع تعديل تخمنيه وفقًا لذلك. هنالك الكثير من التحسينات التي يمكن إجراؤها على الشيفرة السابقة، مثل تضمين آلية لمعالجة الأخطاء التي تحدث عندما لا يُدخِل المستخدم عددًا صحيحًا، لكن كان غرضنا هو رؤية كيفية استخدام حلقة while في برنامج قصير ومفيد يعمل من سطر الأوامر. الخلاصة شرحنا في هذا الدرس كيف تعمل حلقات while في بايثون وكيفية إنشائها. حيث تستمر حلقات while بتنفيذ مجموعة من الأسطر البرمجية لطالما كان الشرط مساويًا للقيمة true. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة –وبتصرّف– للمقال How To Construct While Loops in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا الدرس التالي: كيفية إنشاء حلقات تكرار for في بايثون 3 الدرس السابق: كيفية كتابة التعليمات الشرطية في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون
  12. يبدو نوع البيانات tuple في بايثون كما يلي: coral = ('blue coral', 'staghorn coral', 'pillar coral', 'elkhorn coral') tuple (صف وتُجمَع إلى صفوف) هي بنية بيانات تُمثِّل سلسلة مرتبة من العناصر غير القابلة للتبديل، وبالتالي لا يمكن تعديل القيم الموجودة فيها. يستعمل نوع البيانات tuple لتجميع البيانات، فكل عنصر أو قيمة داخل tuple تُشكِّل جزءًا منه. توضع القيم داخل نوع البيانات tuple بين قوسين ( ) ويُفصَل بينها بفاصلة ,، وتبدو القيم الفارغة كما يلي coral = ()‎، لكن إذا احتوى نوع البيانات tuple على قيم –حتى لو كانت قيمةً واحدةً فقط– فيجب وضع فاصلة فيه مثل coral = ('blue coral',). إذا استخدمنا الدالة print()‎ على tuple، فسنحصل على الناتج الآتي الذي يُبيّن أنَّ القيمة الناتجة ستوضع بين قوسين: print(coral) ('blue coral', 'staghorn coral', 'pillar coral', 'elkhorn coral') عند التفكير بنوع tuple وغيره من بنى البيانات التي تُعبَر من أنوع «المجموعات» (collections)، فمن المفيد أن تضع ببالك مختلف المجموعات الموجودة في حاسوبك: تشكيلة الملفات الموجودة عندك، وقوائم التشغيل للموسيقى، والمفضلة الموجودة في متصفحك، ورسائل بريدك الإلكتروني، ومجموعة مقاطع الفيديو التي تستطيع الوصول إليها من التلفاز، والكثير. نوع tuple شبيه بالقوائم (lists)، لكن القيم الموجودة فيه لا يمكن تعديلها، وبسبب ذلك، فأنت تخبر الآخرين أنَّك لا تريد إجراء أيّة تعديلات على هذه السلسلة من القيم عندما تستعمل tuple في شيفرتك. إضافةً إلى ما سبق، ولعدم القدرة على تعديل القيم، فسيكون أداء برنامجك أفضل، حيث ستُنفَّذ الشيفرة بشكل أسرع إذا استعملتَ tuple بدلًا من القوائم (lists). فهرسة نوع البيانات tuple يمكن الوصول إلى كل عنصر من عناصر tuple بمفرده لأنَّه سلسلة مرتبة من العناصر، وذلك عبر الفهرسة. وكل عنصر يرتبط برقم فهرس، الذي هو عدد صحيح يبدأ من الفهرس 0. لمثال coral السابق، ستبدو الفهارس والقيم المرتبطة بها كالآتي: ‘blue coral’ ‘staghorn coral’ ‘pillar coral’ ‘elkhorn coral’ 0 1 2 3 العنصر الأول الذي يُمثِّل السلسلة النصية 'blue coral' تبدأ بالفهرس 0، وتنتهي القائمة بالفهرس رقم 3 المرتبط بالقيمة 'elkhorn coral'. ولأن كل عنصر من عناصر tuple له رقم فهرس مرتبط به، فسنتمكن من الوصول إلى عناصره فرادى. يمكننا الآن الوصول إلى عنصر معيّن في tuple عبر استخدام رقم الفهرس المرتبط به. print(coral[2]) pillar coral تتراوح قيم الفهارس في المتغير coral من 0 إلى 3 كما هو ظاهر في الجدول السابق، لذا يمكننا استدعاء العناصر الموجودة فيه فرادى كما يلي: coral[0] coral[1] coral[2] coral[3] إذا حاولنا استدعاء المتغير coral مع رقم فهرس أكبر من 3، فستظهر رسالة خطأ تشير إلى أنَّ الفهرس خارج المجال: print(coral[22]) IndexError: tuple index out of range إضافةً إلى أرقام الفهارس الموجبة، يمكننا أيضًا الوصول إلى الفهارس باستخدام رقم فهرس سالب، وذلك بالعد بدءًا من نهاية قائمة العناصر وسيرتبط آخر عنصر بالفهرس ‎-1، وهذا مفيدٌ جدًا إذا كان لديك متغير من النوع tuple وكان يحتوي عناصر كثيرة وأردتَ الوصول إلى أحد عناصره انطلاقًا من النهاية. ففي مثالنا السابق عن coral، إذا أردنا استخدام الفهارس السالبة فالناتج كالآتي: ‘elkhorn coral’ ‘pillar coral’ ‘staghorn coral’ ‘blue coral’ -1 -2 -3 -4 إذا أردنا طباعة العنصر 'blue coral' باستخدام الفهارس السالبة، فستبدو التعليمة كما يلي: print(coral[-4]) blue coral يمكننا إضافة العناصر النصية الموجودة في tuple إلى السلاسل النصية الأخرى باستخدام المعامل +: print('This reef is made up of ' + coral[1]) This reef is made up of staghorn coral استطعنا في المثال السابق إضافة عنصر موجود في الفهرس 1 مع السلسلة النصية 'This reef is made up of '، ويمكننا أيضًا استخدام المعامل + لإضافة بنيتَي tuple معًا. الخلاصة: يمكننا الوصول إلى كل عنصر من عناصر tuple على حدة باستخدام أرقام الفهارس (الموجبة أو السالبة) المرتبطة بها. تقطيع قيم tuple يمكننا استخدام الفهارس للوصول إلى عدِّة عناصر من tuple، أما التقطيع فيسمح لنا بالوصول إلى عدِّة قيم عبر إنشاء مجال من أرقام الفهارس المفصولة بنقطتين رأسيتين [x:y]. لنقل أننا نريد عرض العناصر الموجودة في وسط المتغير coral، يمكننا فعل ذلك بإنشاء قطعة جديدة: print(coral[1:3]) ('staghorn coral', 'pillar coral') عند إنشاء قطعة جديدة –كما في المثال السابق– فيمثِّل أوّل رقم مكان بدأ القطعة (متضمنةً هذا الفهرس)، ورقم الفهرس الثاني هو مكان نهاية القطعة (دون تضمين هذا الفهرس بالقطعة)، وهذا هو السبب وراء عرض المثال السابق للقيم المرتبطة بالعناصر الموجودة في الفهرسين 1 و 2. إذا أردتَ تضمين إحدى نهايتَي القائمة، فيمكنك حذف أحد الأرقام في التعبير tuple[x:y]، فمثلًا، لنقل أننا نريد عرض أوّل ثلاثة عناصر من coral، والتي هي 'blue coral' و 'staghorn coral' و 'pillar coral'، فيمكننا فعل ذلك كالآتي: print(coral[:3]) ('blue coral', 'staghorn coral', 'pillar coral') المثال السابق عرض العناصر من بداية القائمة وتوقف قبل العنصر ذي الفهرس 3. لتضمين كل العناصر الموجودة في نهاية tuple، فيمكننا عكس التعبير السابق: print(coral[1:]) ('staghorn coral', 'pillar coral', 'elkhorn coral') يمكننا استخدام الفهارس السالبة أيضًا عند التقطيع، كما فعلنا مع أرقام الفهارس الموجبة: print(coral[-3:-1]) print(coral[-2:]) ('staghorn coral', 'pillar coral') ('pillar coral', 'elkhorn coral') هنالك معاملٌ إضافيٌ يمكننا استعماله ويسمى «الخطوة»، ويُشير إلى عدد العناصر التي يجب تجاوزها بعد الحصول على أوّل عنصر من القائمة. حذفنا في جميع أمثلتنا السابقة معامل الخطوة، حيث القيمة الافتراضية له في بايثون هي 1، لذا سنحصل على جميع العناصر الموجودة بين الفهرسَين المذكورين. شكل هذا التعبير العام هو tuple[x:y:z]، إذ يُشير المعامل z إلى الخطوة. لنُنشِئ قائمةً أكبر، ثم نقسِّمها، ونعطيها القيمة 2 كخطوة: numbers = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) print(numbers[1:11:2]) (1, 3, 5, 7, 9) التعبير numbers[1:11:2] سيطبع القيم الموجودة بين رقمين الفهرسين 1 (بما في ذلك العنصر المرتبط بالفهرس 1) و 11 (دون تضمين ذلك العنصر)، ومن ثم ستخبر قيمةُ الخطوة 2 البرنامجَ أنَّ يتخطى عنصرًا بين كل عنصرين. يمكننا حذف أوّل معاملين واستخدام معامل الخطوة بمفرده بتعبيرٍ برمجيٍ من الشكل tuple[::z]: print(numbers[::3]) (0, 3, 6, 9, 12) طبعنا في المثال السابق عناصر numbers بعد ضبط قيمة الخطوة إلى 3، وبالتالي سيتم تخطي عنصرين. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 الخلاصة: تقطيع tuples باستخدام أرقام الفهارس الموجبة والسالبة واستعمال معامل الخطوة يسمح لنا بالتحكم بالناتج الذي نريد عرضه. إضافة بنى tuple إلى بعضها يمكن أن نُضيف بنى tuple إلى بعضها أو أن «نضربها» (multiply)، تتم عملية الإضافة باستخدام المعامل +، أما عملية الضرب فباستخدام المعامل *. يمكن أن يُستخدَم المعامل + لإضافة بنيتَي tuple أو أكثر إلى بعضها بعضًا. يمكننا إسناد القيم الموجودة في بنيتَي tuple إلى بنية جديدة: coral = ('blue coral', 'staghorn coral', 'pillar coral', 'elkhorn coral') kelp = ('wakame', 'alaria', 'deep-sea tangle', 'macrocystis') coral_kelp = (coral + kelp) print(coral_kelp) الناتج: ('blue coral', 'staghorn coral', 'pillar coral', 'elkhorn coral', 'wakame', 'alaria', 'deep-sea tangle', 'macrocystis') وصحيحٌ أنَّ المعامل + يمكنه إضافة بنى tuple إلى بعضها، لكن يمكن أن يستعمل لإنشاء بنية tuple جديدة ناتجة عن جمع بنى أخرى، لكن لا يمكنه تعديل بنية tuple موجودة مسبقًا. أما المعامل * فيمكن استخدامه لضرب بنى tuple، فربما تريد إنشاء نسخ من الملفات الموجودة في أحد المجلدات إلى الخادوم أو مشاركة قائمة بالمقطوعات الموسيقية التي تحبها مع أصدقائك، ففي هذه الحالات سترغب بمضاعفة مجموعات من البيانات (أو «ضربها»). لنضرب البنية coral بالرقم 2 والبنية kelp بالرقم 3، ثم نسندها إلى بنى tuple جديدة: multiplied_coral = coral * 2 multiplied_kelp = kelp * 3 print(multiplied_coral) print(multiplied_kelp) الناتج: ('blue coral', 'staghorn coral', 'pillar coral', 'elkhorn coral', 'blue coral', 'staghorn coral', 'pillar coral', 'elkhorn coral') ('wakame', 'alaria', 'deep-sea tangle', 'macrocystis', 'wakame', 'alaria', 'deep-sea tangle', 'macrocystis', 'wakame', 'alaria', 'deep-sea tangle', 'macrocystis') يمكننا باستخدام المعامل * أن نُكرِّر (أو نُضاعِف) بنى tuple بأي عدد من المرات نشاء، مما سينُشِئ بنى tuple جديدة اعتمادًا على محتوى البنى الأصلية. الخلاصة هي أنَّ بنى tuple يمكن إضافتها إلى بعضها أو ضربها لتشكيل بنى tuple جديدة عبر استخدام المعاملَين + و *. دوال التعامل مع tuple هنالك دوال مُضمَّنة في لغة بايثون للتعامل مع بنى tuple، لننظر إلى بعضها. len()‎ وكما في السلاسل النصية والقوائم، يمكننا حساب طول (أو عدد عناصر) بنية tuple باستخدام الدالة len()‎ حيث نُمرِّر إليها بنية tuple كمعامل (parameter)، كما يلي: len(coral) هذه الدالة مفيدة إذا أردنا أن نَضمَن أنَّ لبنية tuple عدد عناصر معيّن، فمثلًا يمكننا الاستفادة من ذلك بمقارنة بنيتين مع بعضهما. إذا أردنا طباعة عدد عناصر kelp و numbers، فسيظهر الناتج الآتي: print(len(kelp)) print(len(numbers)) الناتج: 4 13 الناتج أعلاه يشير إلى أنَّ للبنية kelp أربعة عناصر: kelp = ('wakame', 'alaria', 'deep-sea tangle', 'macrocystis') أما البنية numbers فتملك ثلاثة عشر عنصرًا: numbers = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) وصحيحٌ أنَّ هذه الأمثلة عناصرها قليلة نسبيًا، إلا أنَّ الدالة len()‎ تستطيع أن تخبرنا بعدد عناصر بنى tuple الكبيرة. الدالتان max()‎ و min()‎ عندما نتعامل مع بنى tuple مكوَّنة من عناصر رقمية (بما فيها الأعداد الصحيحة والأرقام ذات الفاصلة العشرية)، فيمكننا استخدام الدالتين max()‎ و min()‎ للعثور على أكبر وأصغر قيمة موجودة في بنية tuple معيّنة. تسمح لنا هاتان الدالتان باستخراج معلومات تخص البيانات القابلة للإحصاء، مثل نتائج الامتحانات أو درجات الحرارة أو أسعار المنتجات …إلخ. لننظر إلى بنية tuple مكونة من أعداد عشرية: more_numbers = (11.13, 34.87, 95.59, 82.49, 42.73, 11.12, 95.57) للحصول على القيمة العظمى من بين القيم الآتية فعلينا تمرير بنية tuple إلى الدالة max()‎ كما في max(more_numbers)، وسنستخدم الدالة print()‎ لعرض الناتج: print(max(more_numbers)) 95.59 أعادت الدالة max()‎ أعلى قيمة في بنية more_numbers. وبشكلٍ شبيهٍ بما سبق نستخدم الدالة min()‎: print(min(more_numbers)) 11.12 أُعيدَ هنا أصغر رقم عشري موجودة في البنية. يمكن الاستفادة من الدالتين max()‎ و min()‎ كثيرًا للتعامل مع بنى tuple التي تحتوي الكثير من القيم. كيف تختلف بنى tuple عن القوائم (list) الفرق الرئيسي بين tuple و list هو عدم القدرة على تعديلها، وهذا يعني أنَّنا لا نستطيع إضافة أو حذف أو استبدال العناصر داخل بنية tuple. لكن يمكننا إضافة بنيتَي tuple أو أكثر إلى بعضها بعضًا لتشكيل بنية جديدة كما رأينا في أحد الأقسام السابقة. لتكن لدينا البنية coral الآتية: coral = ('blue coral', 'staghorn coral', 'pillar coral', 'elkhorn coral') لنقل أننا نريد استبدال العنصر 'blue coral' ووضع العنصر 'black coral' بدلًا منه. فلو حاولنا تغيير بنية tuple بنفس الطريقة التي نُعدِّل فيها القوائم بكتابة: coral[0] = 'black coral' فستظهر رسالة خطأ كالآتية: TypeError: 'tuple' object does not support item assignment وذلك بسبب عدم إمكانية تعديل بنى tuple. إذا أنشأنا بنية tuple ثم قررنا أنَّ ما نحتاج له هو بنية list، فيمكننا تحويلها إلى قائمة list، وذلك بالدالة list()‎: list(coral) أصبحت بنية coral قائمةً الآن: coral = ['blue coral', 'staghorn coral', 'pillar coral'] يمكننا أن نلاحظ أنَّ بنية tuple تحوَّلتَ إلى قائمة list لأنَّ الأقواس المحيطة بالقيم أصبح مربعة الشكل. وبشكلٍ شبيهٍ بما سبق، نستطيع تحويل القوائم من النوع list إلى tuple باستخدام الدالة tuple()‎. الخلاصة نوع البيانات tuple هو مجموعةٌ من البيانات المتسلسلة التي لا يمكن تعديلها، ويوفِّر تحسينًا في أداء برامجك لأنه أسرع معالجةً من القوائم في بايثون. وعندما يراجع الآخرون شيفرتك فسيعلمون من استخدامك لبنى tuple أنك لا تريد تعديل هذه القيم. شرحنا في هذا الدرس الميزات الأساسية لبنى tuple بما في ذلك الفهارس وتقطيعها وتجميعها، وعرضنا بعض الدوال المُضمَّنة المتوافرة لهذا النوع من البيانات. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة –وبتصرّف– للمقال Understanding Tuples in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة التالية: فهم القواميس في بايثون 3 المقالة السابقة: فهم كيفية استعمال List Comprehensions في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون
  13. توفر List Comprehensions طريقةً مختصرةً لإنشاء القوائم بناءً على قوائم موجودة مسبقًا. فعند استخدام list comprehensions فيمكن بناء القوائم باستخدام أيّ نوع من البيانات المتسلسلة التي يمكن الدوران على عناصرها عبر حلقات التكرار، بما في ذلك السلاسل النصية و tuples. من ناحية التركيب اللغوي، تحتوي list comprehensions على عنصر يمكن المرور عليه ضمن تعبيرٍ متبوعٍ بحلقة for. ويمكن أن يُتبَع ما سبق بتعابير for أو if إضافية، لذا سيساعدك الفهم العميق لحلقات for والعبارات الشرطية في التعامل مع list comprehensions. توفِّر list comprehensions طريقةً مختلفةً لإنشاء القوائم وغيرها من أنواع البيانات المتسلسلة. وعلى الرغم من إمكانية استخدام الطرائق الأخرى للدوران، مثل حلقات for، لإنشاء القوائم، لكن من المفضَّل استعمال list comprehensions لأنها تقلِّل عدد الأسطر الموجودة في برنامجك. List Comprehensions يمكن بناء list comprehensions في بايثون كالآتي: list_variable = [x for x in iterable] ستُسنَد القائمة، أو أي نوع من البيانات يمكن المرور على عناصره، إلى متغير. المتغيرات الإضافية –التي تُشير إلى عناصر موجودة ضمن نوع البيانات الذي يمكن المرور على عناصره– تُبنى حول عبارة for. والكلمة المحجوزة in تستعمل بنفس استعمالها في حلقات for وذلك لمرور على عناصر iterable. لننظر إلى مثالٍ يُنشِئ قائمةً مبنيةً على سلسلةٍ نصية: shark_letters = [letter for letter in 'shark'] print(shark_letters) أسندنا في المثال السابق قائمةً جديدةً إلى المتغير shark_letters، واستعملنا المتغير letter للإشارة إلى العناصر الموجودة ضمن السلسلة النصية 'shark'. استعملنا بعد ذلك الدالة print()‎ لكي نتأكد من القائمة الناتجة والمُسنَدة إلى المتغير shark_letters، وحصلنا على الناتج الآتي: ['s', 'h', 'a', 'r', 'k'] القائمة التي أنشأناها باستخدام list comprehensions تتألف من العناصر التي تكوِّن السلسلة النصية 'shark'، وهي كل حرف في الكلمة shark. يمكن إعادة كتابة تعابير list comprehensions كحلقات for، لكن لاحظ أنَّك لا تستطيع إعادة كتابة كل حلقة for بصيغة list comprehensions. لنعد كتابة المثال السابق الذي أنشأنا فيه القائمة shark_letters باستخدام حلقة for، وهذا سيساعدنا في فهم كيف تعمل list comprehensions عملها: shark_letters = [] for letter in 'shark': shark_letters.append(letter) print(shark_letters) عند إنشائنا للقائمة عبر استخدام الحلقة for، فيجب تهيئة المتغير الذي سنُسنِد العناصر إليه كقائمة فارغة، وهذا ما فعلناه في أوّل سطر من الشيفرة السابقة. ثم بدأت حلقة for بالدوران على عناصر السلسلة النصية 'shark' مستعملةً المتغير letter للإشارة إلى قيمة العنصر الحالي. ومن ثم أضفنا كل عنصر في السلسلة النصية إلى القائمة ضمن حلقة for وذلك باستخدام الدالة list.append(x). الناتج من حلقة for السابقة يماثل ناتج list comprehension في المثال أعلاه: ['s', 'h', 'a', 'r', 'k'] الخلاصة: يمكن إعادة كتابة List comprehensions كحلقات for، لكن بعض حلقات for يمكن إعادة كتابتها لتصبح List comprehensions لتقليل كمية الشيفرات المكتوبة. استخدام التعابير الشرطية مع List Comprehensions يمكن استخدام التعابير الشرطية في list comprehension لتعديل القوائم أو أنواع البيانات المتسلسلة الأخرى عند إنشاء قوائم جديدة. لننظر إلى مثالٍ عن استخدام العبارة الشرطية if في تعبير list comprehension: fish_tuple = ('blowfish', 'clownfish', 'catfish', 'octopus') fish_list = [fish for fish in fish_tuple if fish != 'octopus'] print(fish_list) استعملنا المتغير fish_tuple الذي من نوع البيانات tuple كأساس للقائمة الجديدة التي سنُنشِئها التي تسمى fish_list. استعملنا for و in كما في القسم السابق، لكننا أضفنا هنا العبارة الشرطية if. ستؤدي العبارة الشرطية if إلى إضافة العناصر غير المساوية للسلسلة النصية 'octopus'، لذا ستحتوي القائمة الجديدة على العناصر الموجودة في بنية tuple والتي لا تُطابِق الكلمة 'octopus'. عند تشغيل البرنامج السابق فسنلاحظ أنَّ القائمة fish_list تحتوي على نفس العناصر التي كانت موجودة في fish_tuple لكن مع حذف العنصر 'octopus': ['blowfish', 'clownfish', 'catfish'] أي أصبحت القائمة الجديدة تحتوي على بنية tuple الأصلية لكن ما عدا السلسلة النصية التي استثنيناها عبر التعبير الشرطي. سنُنشِئ مثالًا آخر يستعمل المعاملات الرياضية والأرقام الصحيحة والدالة range()‎: number_list = [x ** 2 for x in range(10) if x % 2 == 0] print(number_list) القائمة التي ستُنشَأ باسم number_list ستحتوي على مربع جميع القيم الموجودة من المجال 0 إلى 9 لكن إذا كان الرقم قابلًا للقسمة على 2. وستبدو المخرجات كالآتية: [0, 4, 16, 36, 64] دعنا نُفصِّل ما الذي يفعله تعبير list comprehension السابق، ودعنا نفكِّر بالذي سيظهر إذا استعملنا التعبير x for x in range(10) فقط. يجب أن يبدو برنامجنا الصغير كالآتي: number_list = [x for x in range(10)] print(number_list) الناتج: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] لنضف العبارة الشرطية الآن: number_list = [x for x in range(10) if x % 2 == 0] print(number_list) الناتج: [0, 2, 4, 6, 8] أدّت العبارة الشرطية if إلى قبول العناصر القابلة للقسمة على 2 فقط وإضافتها إلى القائمة، مما يؤدي إلى حذف جميع الأرقام الفردية. يمكننا الآن استخدام معامل رياضي لتربيع قيمة المتغير x: number_list = [x ** 2 for x in range(10) if x % 2 == 0] print(number_list) أي ستُربَّع قيم القائمة السابقة ‎[0, 2, 4, 6, 8] وسيُخرَج الناتج الآتي: [0, 4, 16, 36, 64] يمكننا أيضًا استعمال ما يشبه عبارات if المتشعبة في تعابير list comprehension: number_list = [x for x in range(100) if x % 3 == 0 if x % 5 == 0] print(number_list) سيتم التحقق أولًا أنَّ المتغير x قابل للقسمة على الرقم 3، ثم سنتحقق إن كان المتغير x قابل للقسمة على الرقم 5، وإذا حقَّق المتغير x الشرطين السابقين فسيُضاف إلى القائمة، وسيُظهَر في الناتج: [0, 15, 30, 45, 60, 75, 90] الخلاصة: يمكن استخدام عبارات if الشرطية لتحديد ما هي العناصر التي نريد إضافتها إلى القائمة الجديدة. حلقات التكرار المتشعبة في تعابير List Comprehension يمكن استعمال حلقات التكرار المتشعبة لإجراء عدِّة عمليات دوران متداخلة في برامجنا. سننظر في هذا القسم إلى حلقة for متشعبة وسنحاول تحويلها إلى تعبير list comprehension. هذه الشيفرة ستُنشِئ قائمةً جديدةً بالدوران على قائمتين وبإجراء عمليات رياضية عليها: my_list = [] for x in [20, 40, 60]: for y in [2, 4, 6]: my_list.append(x * y) print(my_list) سنحصل على الناتج الآتي عند تشغيل البرنامج: [40, 80, 120, 80, 160, 240, 120, 240, 360] الشيفرة السابقة تضرب العناصر الموجودة في أوّل قائمة بالعناصر الموجودة في ثاني قائمة في كل دورة. لتحويل ما سبق إلى تعبير list comprehension، وذلك باختصار السطرين الموجودين في الشيفرة السابقة وتحويلهما إلى سطرٍ وحيدٍ، الذي يبدأ بإجراء العملية x*y، ثم ستلي هذه العملية حلقة for الخارجية، ثم يليها حلقة for الداخلية؛ وسنضيف تعبير print()‎ للتأكد أنَّ ناتج القائمة الجديدة يُطابِق ناتج البرنامج الذي فيه حلقتين متداخلتين: my_list = [x * y for x in [20, 40, 60] for y in [2, 4, 6]] print(my_list) الناتج: [40, 80, 120, 80, 160, 240, 120, 240, 360] أدى استعمال تعبير list comprehension في المثال السابق إلى تبسيط حلقتَي for لتصبحا سطرًا وحيدًا، لكن مع إنشاء نفس القائمة والتي ستُسنَد إلى المتغير my_list. توفِّر لنا تعابير list comprehension طريقةً بسيطةً لإنشاء القوائم، مما يسمح لنا باختصار عدِّة أسطر إلى سطرٍ وحيد. لكن من المهم أن تبقي في ذهنك أنَّ سهولة قراءة الشيفرة لها الأولوية دومًا، لذا إذا أصبحتَ تعابير list comprehension طويلةً جدًا ومعقدة، فمن الأفضل حينها تحويلها إلى حلقات تكرار عادية. الخلاصة تسمح تعابير list comprehension لنا بتحويل قائمة أو أي نوع من البيانات المتسلسلة إلى سلسلةٍ جديدة، ولها شكلٌ بسيطٌ يُقلِّل عدد الأسطر التي نكتبها. تتبع تعابير list comprehension شكلًا رياضيًا معيّنًا، لذا قد يجدها المبرمجون أولو الخلفية الرياضية سهلة الفهم. وصحيحٌ أنَّ تعابير list comprehension تختصر الشيفرةـ لكن من المهم جعل سهولة قراءة الشيفرة من أولوياتنا، وحاول تجنّب الأسطر الطويلة لتسهيل قراءة الشيفرة. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة –وبتصرّف– للمقال Understanding List Comprehensions in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة التالية: فهم نوع البيانات Tuples في بايثون 3 المقالة السابقة: كيفية استخدام توابع القوائم في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون
  14. الدالة str.format()‎ المتوافرة للسلاسل النصية تسمح لك باستبدال المتغيرات وتنسيق القيم. مما يمنحك القدرة على تجميع العناصر مع بعضها عبر إدخالها في مواضع معينة. سيشرح لك هذا الدرس أشهر الاستخدامات لآلية تنسيق السلاسل النصية في بايثون، والتي ستساعدك في جعل شيفرتك وبرنامجك أسهل قراءةً واستخدامًا. استخدام «المُنسِّقات» تعمل المُنسِّقات (formatters) بوضع حقول قابلة للاستبدال تُعرَّف عبر وضع قوسين معقوفين {} في السلسلة النصية ثم استدعاء الدالة str.format()‎، إذ ستُمرَّر القيمة التي تريد وضعها ضمن السلسلة النصية إلى الدالة format()‎ وستوضع هذه القيمة في نفس مكان الحقل القابل للاستبدال الموجود في السلسلة الأصلية عندما تُشغِّل برنامجك. لنطبع سلسلةً نصيةً تستخدم «مُنسِّقًا» (formatter): print("Sammy has {} balloons.".format(5)) الناتج: Sammy has 5 balloons. أنشأنا في المثال السابق سلسلةً نصيةً تحتوي على قوسين معقوفين: "Sammy has {} balloons." ثم أضفنا الدالة str.format()‎ ومررنا إليها القيمة الرقمية 5 وهذا يعني أنَّ القيمة 5 ستوضع مكان القوسين المعقوفين: Sammy has 5 balloons. يمكننا أيضًا إسناد السلسلة النصية الأصلية التي تحتوي مُنسِّقًا إلى متغير: open_string = "Sammy loves {}." print(open_string.format("open source")) الناتج: Sammy loves open source. أضفنا في المثال السابق السلسلة النصية "open source" إلى سلسلةٍ نصيةٍ أكبر باستبدالها للقوسين المعقوفين الموجودَين في السلسلة الأصلية. تسمح لك المُنسِّقات في بايثون باستخدام الأقواس المعقوفة لحجز أماكن للقيم التي ستمررها مستقبلًا عبر الدالة str.format()‎. استخدام المُنسِّقات لحجز أكثر من مكان يمكنك استخدام أكثر من زوج من الأقواس المعقوفة عند استعمال المُنسِّقات؛ فيمكنك أن تضيف سلسلةً نصيةً أخرى إلى المثال السابق وذلك بإضافة زوج آخر من الأقواس المعقوفة وتمرير قيمة ثانية إلى الدالة كما يلي: new_open_string = "Sammy loves {} {}." # {} مكانين محجوزين عبر print(new_open_string.format("open-source", "software")) # تمرير قيمتين إلى الدالة مفصولٌ بينهما بفاصلة الناتج: Sammy loves open-source software. أضفنا زوجًا آخر من الأقواس المعقوفة إلى السلسلة النصية للسماح بوضع قيمة ثانية، ثم مررنا سلسلتين نصيتين إلى الدالة str.format()‎ مفصولٌ بينهما بفاصلة. سنضيف عمليات استبدال أخرى عبر اتباع نفس الآلية التي شرحناها أعلاه: sammy_string = "Sammy loves {} {}, and has {} {}." print(sammy_string.format("open-source", "software", 5, "balloons")) الناتج: Sammy loves open-source software, and has 5 balloons. إعادة ترتيب المنسقات عبر المعاملات الموضعية عندما نترك الأقواس المعقوفة دون معاملات (parameters) ممررة إليها، فستضع بايثون القيم المُمرَّرة إلى الدالة str.format()‎ بالترتيب. هذا تعبيرٌ فيه زوجين من الأقواس المعقوفة يوضع مكانهما سلسلتان نصيتان شبيهٌ بما رأيناه سابقًا في هذا الدرس: print("Sammy the {} has a pet {}!".format("shark", "pilot fish")) الناتج: Sammy the shark has a pet pilot fish! اُستبدِل أوّل زوجٍ من الأقواس المعقوفة ووضعت مكانه القيمة "shark"، ووضعت القيمة "pilot fish" مكان الزوج الثاني من الأقواس. القيم التي مررناها إلى الدالة str.format()‎ كانت بهذا الترتيب: ("shark", "pilot fish") إن سبق لك دراسة أنواع البيانات المُختلفة على بايثون فقد تلاحظ أنَّ القيمة السابقة هي من النوع tuple، ويمكن الوصول إلى كل قيمة موجودة فيها عبر فهرسٍ رقميٍ تابعٍ لها، والذي يبدأ من الفهرس 0. يمكننا تمرير أرقام الفهارس إلى داخل القوسين المعقوفين: print("Sammy the {0} has a pet {1}!".format("shark", "pilot fish")) سنحصل بعد تنفيذ المثال السابق على نفس الناتج التي ظهر دون تحديد أرقام الفهارس يدويًا، وذلك لأننا استدعينا القيم بالترتيب: Sammy the shark has a pet pilot fish! لكن إن عكسنا أرقام الفهارس في معاملات الأقواس المعقوفة فسنتمكن من عكس ترتيب القيم المُمرَّرة إلى السلسلة النصية الأصلية: print("Sammy the {1} has a pet {0}!".format("shark", "pilot fish")) الناتج: Sammy the pilot fish has a pet shark! لكن إن حاولت استخدام الفهرس ذي الرقم 2 ولم تكن لديك إلا قيمتين موجودتين في الفهرسين 0 و 1، فأنت تستدعي قيمةً خارج المجال المسموح، ولهذا السبب ستظهر رسالة خطأ: print("Sammy the {2} has a pet {1}!".format("shark", "pilot fish")) الناتج: IndexError: tuple index out of range تُشير رسالة الخطأ إلى وجود قيمتين فقط ومكانهما هو 0 و1، لذا كان الفهرس 2 غير مرتبطٍ بقيمةٍ وكان خارج المجال المسموح. لنضف الآن مكانين محجوزين إلى السلسلة النصية ولنمرر بضع قيم إلى الدالة str.format()‎ لكي نفهم آلية إعادة الترتيب فهمًا تامًا. هذه هي السلسلة النصية الجديدة التي فيها أربعة أزواج من الأقواس المعقوفة: print("Sammy is a {}, {}, and {} {}!".format("happy", "smiling", "blue", "shark")) الناتج: Sammy is a happy, smiling and blue shark! ستوضع القيم المُمرَّرة إلى الدالة str.format()‎ بنفس ترتيب ورودها في حال لم نستعمل المعاملات داخل الأقواس المعقوفة. تملك السلاسل النصية المُمرَّرة إلى الدالة str.format()‎ الفهارس الآتية المرتبطة بها: “happy” “smiling” “blue” “shark” 0 1 2 3 لنستخدم الآن أرقام الفهارس لتغيير ترتيب ظهور القيم المرتبطة بها في السلسلة النصية: print("Sammy is a {3}, {2}, and {1} {0}!".format("happy", "smiling", "blue", "shark")) الناتج: Sammy is a shark, blue, and smiling happy! ولمّا كنّا قد بدأنا بالفهرس ذي الرقم 3، فستظهر القيمة "shark" أولًا. أي أنَّ وضع رقم الفهرس بين القوسين كمعامل سيؤدي إلى تغيير ترتيب ظهور القيم في السلسلة النصية الأصلية. نستطيع -بالإضافة إلى المعاملات الموضعية الرقمية- أن نربط بين القيم وبين كلمات محجوزة مخصصة ومن ثم نستدعيها عبر وضع الكلمة المحجوزة بين القوسين المعقوفين كما يلي: print("Sammy the {0} {1} a {pr}.".format("shark", "made", pr = "pull request")) الناتج: Sammy the shark made a pull request. أظهر المثال السابق استخدام كلمة محجوزة كوسيط بالإضافة إلى المعاملات الموضعية، يمكنك استخدام الكلمة المحجوزة pr كوسيط بالإضافة إلى أرقام الفهارس، وتستطيع أيضًا إعادة ترتيب تلك الوسائط كيفما شئت: print("Sammy the {pr} {1} a {0}.".format("shark", "made", pr = "pull request")) الناتج: Sammy the pull request made a shark. استخدام المعاملات الموضعية والكلمات المحجوزة سيمنحنا تحكمًا أكبر بكيفية معالجة السلسلة النصية الأصلية عبر إعادة ترتيب القيم المُمرَّرة إليها. تحديد نوع القيمة يمكنك وضع معاملات أخرى ضمن القوسين المعقوفين، سنستخدم الصيغة الآتية {field_name:conversion} حيث field_name هو الفهرس الرقمي للوسيط الممرَّر إلى الدالة str.format()‎ والذي شرحناه تفصيليًا في القسم السابق، و conversion هو الرمز المستعمل للتحويل إلى نوع البيانات الذي تريده. «رمز التحويل» يعني رمزًا من حرفٍ وحيد الذي تستخدمه بايثون لمعرفة نوع القيمة المُراد «تنسيقها». الرموز التي سنستخدمها في أمثلتنا هي s للسلاسل النصية و d لإظهار الأرقام بنظام العد العشري (ذي الأساس 10) و f لإظهار الأعداد ذات الفاصلة. يمكنك قراءة المزيد من التفاصيل عن رموز التنسيق في بايثون 3 (وغير ذلك من المواضيع المرتبطة بهذا المجال) في التوثيق الرسمي. لننظر إلى مثالٍ نُمرِّر فيه رقمًا صحيحًا عبر الدالة format()‎ لكننا نريد إظهاره كعددٍ ذي فاصلة عبر رمز التحويل f: print("Sammy ate {0:f} percent of a {1}!".format(75, "pizza")) الناتج: Sammy ate 75.000000 percent of a pizza! وضعت القيمة -مكان أوّل ورود للصيغة {field_name:conversion}- كعددٍ ذي فاصلة، أما ثاني ورود للقوسين المعقوفين فكان بالصيغة {field_name}. لاحظ في المثال السابق وجود عدد كبير من الأرقام الظاهرة بعد الفاصلة العشرية، لكنك تستطيع تقليل عددها. فعندما نستخدم الرمز f للقيم ذات الفاصلة نستطيع أيضًا تحديد دقة القيمة الناتجة بتضمين رمز النقطة . متبوعًا بعدد الأرقام بعد الفاصلة التي نود عرضها. حتى لو أكل سامي 75.765367% من قطعة البيتزا فلن نحتاج إلى هذا القدر الكبير من الدقة، إذ يمكننا مثلًا أن نجعل عدد المنازل العشرية ثلاث منازل بعد الفاصلة بوضعنا ‎.3 قبل رمز التحويل f: print("Sammy ate {0:.3f} percent of a pizza!".format(75.765367)) الناتج: Sammy ate 75.765 percent of a pizza! أما إذا أردنا عرض منزلة عشرية وحيدة، فيمكننا إعادة كتابة السلسلة السابقة كالآتي: print("Sammy ate {0:.1f} percent of a pizza!".format(75.765367)) الناتج: Sammy ate 75.8 percent of a pizza! لاحظ كيف أدى تعديل دقة الأرقام العشرية إلى تقريب الرقم (وفق قواعد التقريب الاعتيادية). وصحيحٌ أننا عرضنا رقمًا دون منازل عشرية كعددٍ ذي فاصلة، إلا أننا إذا حاولنا تحويل عدد عشري إلى عدد صحيح باستخدام رمز التحويل d فسنحصل على خطأ: print("Sammy ate {0:.d} percent of a pizza!".format(75.765367)) الناتج: ValueError: Unknown format code 'd' for object of type 'float' إذا لم ترغب بعرض أيّة منازل عشرية، فيمكنك كتابة تعبير كالآتي: print("Sammy ate {0:.0f} percent of a pizza!".format(75.765367)) الناتج: Sammy ate 76 percent of a pizza! لن يؤدي ما سبق إلى تحويل العدد العشري إلى عددٍ صحيح، وإنما سيؤدي إلى تقليل عدد المنازل العشرية الظاهرة بعد الفاصلة. إضافة حواشي لمّا كانت الأماكن المحجوزة عبر القوسين المعقوفين هي حقول قابلة للاستبدال (أي ليست قيمًا فعليةً) فيمكنك إضافة حاشية (padding) أو إضافة فراغ حول العنصر بزيادة حجم الحقل عبر معاملات إضافية، قد تستفيد من هذا الأمر عندما تحتاج إلى تنظيم البيانات بصريًا. يمكننا إضافة حقلٍ بحجمٍ معيّن (مُقاسًا بعدد المحارف) بتحديد ذاك الحجم بعد النقطتين الرأسيتين : كما في المثال الآتي: print("Sammy has {0:4} red {1:16}!".format(5, "balloons")) الناتج: Sammy has 5 red balloons ! أعطينا في المثال السابق حقلًا بحجم 4 محارف للعدد 5، وأعطينا حقلًا بحجم 16 محرفًا للسلسلة النصية balloons (لأنها سلسلة طويلة نسبيًا). وكما رأينا من ناتج المثال السابق، يتم محاذاة السلاسل النصية افتراضيًا إلى اليسار والأعداد إلى اليمين، يمكنك أن تُغيّر من هذا بوضع رمز خاص للمحاذاة بعد النقطتين الرأسيتين مباشرةً. إذ سيؤدي الرمز > إلى محاذاة النص إلى يسار الحقل، أما الرمز ^ فسيوسِّط النص في الحقل، والرمز < سيؤدي إلى محاذاته إلى اليمين. لنجعل محاذاة العدد إلى اليسار ونوسِّط السلسلة النصية: print("Sammy has {0:<4} red {1:^16}!".format(5, "balloons")) الناتج: Sammy has 5 red balloons ! نلاحظ الآن أنَّ محاذاة العدد 5 إلى اليسار، مما يعطي مساحة فارغةً في الحقل قبل الكلمة red، وستظهر السلسلة النصية balloons في منتصف الحقل وتوجد مسافة فارغة على يمينها ويسارها. عندما نُنسِّق الحقل لنجعله أكبر من حجمه الطبيعي فستملأ بايثون الحقل افتراضيًا بالفراغات، إلا أننا نستطيع تغيير محرف الملء إلى محرفٍ آخر بوضعه مباشرةً بعد النقطتين الرأسيتين: print("{:*^20s}".format("Sammy")) الناتج: *******Sammy******** ستضع بايثون السلسلة النصية المُمرَّرة إلى الدالة str.format()‎ ذات الفهرس 0 مكان القوسين المعقوفين لأننا لم نطلب منها عكس ذلك، ومن ثم سنضع النقطتين الرأسيتين ثم سنُحدِّد أننا سنستعمل المحرف * بدلًا من الفراغات لملء الحقل، ثم سنوسِّط السلسلة النصية عبر استعمال الرمز ^ مُحدِّدين أنَّ حجم الحقل هو 20 محرف، ومُشيرين في نفس الوقت إلى أنَّ الحقل هو حقلٌ نصيٌ عبر وضع الرمز s. يمكننا استخدام هذه المعاملات مع المعاملات التي استخدمناها وشرحناها في الأقسام السابقة: print("Sammy ate {0:5.0f} percent of a pizza!".format(75.765367)) الناتج: Sammy ate 76 percent of a pizza! حدِّدنا داخل القوسين المعقوفين رقم فهرس القيمة العددية ثم وضعنا النقطتين الرأسيتين ثم اخترنا حجم الحقل ثم وضعنا نقطةً . لنضبط عدد المنازل العشرية الظاهرة، ثم اخترنا نوع الحقل عبر رمز التحويل f. استخدام المتغيرات مرَّرنا منذ بداية هذا الدرس وإلى الآن الأعداد والسلاسل النصية إلى الدالة str.format()‎ مباشرةً، لكننا نستطيع تمرير المتغيرات أيضًا، وذلك بنفس الآلية المعتادة: nBalloons = 8 print("Sammy has {} balloons today!".format(nBalloons)) الناتج: Sammy has 8 balloons today! يمكننا أيضًا استخدام المتغيرات لتخزين السلسلة النصية الأصلية بالإضافة إلى القيم التي ستُمرَّر إلى الدالة: sammy = "Sammy has {} balloons today!" nBalloons = 8 print(sammy.format(nBalloons)) الناتج: Sammy has 8 balloons today! ستُسهِّل المتغيرات من التعامل مع تعبيرات التنسيق وتُبسِّط عملية إسناد مدخلات المستخدم وإظهارها مُنسَّقةً في السلسلة النصية النهائية. استخدام المُنسِّقات لتنظيم البيانات يسطع نجم آلية التنسيق التي نشرحها في هذا الدرس عندما تُستخدَم لتنظيم البيانات بصريًا، فلو أردنا إظهار نتائج قاعدة البيانات إلى المستخدمين، فيمكننا استعمال المُنسِّقات لزيادة حجم الحقل وتعديل المحاذاة لجعل الناتج أسهل قراءةً. لننظر إلى حلقة تكرار تقليدية في بايثون التي تطبع i و i*i و i*i*i لمجالٍ من الأعداد من 3 إلى 13: for i in range(3,13): print(i, i*i, i*i*i) الناتج: 3 9 27 4 16 64 5 25 125 6 36 216 7 49 343 8 64 512 9 81 729 10 100 1000 11 121 1331 12 144 1728 صحيحٌ أنَّ الناتج مُنظَّمٌ قليلًا، إلا أنَّ الأعداد تتداخل مع بعضها بصريًا مما يُصعِّب قراءة الأسطر الأخيرة من الناتج، وإذا كنتَ تتعامل مع مجموعة أكبر من البيانات التي يتواجد فيها أعداد أكبر (أو أصغر) مما عرضناه في مثالنا، فقد تبدو لك المشكلة جليةً حينها. لنحاول تنسيق الناتج السابق لإعطاء مساحة أكبر لإظهار الأعداد: for i in range(3,13): print("{:3d} {:4d} {:5d}".format(i, i*i, i*i*i)) لم نُحدِّد في المثال السابق ترتيب الحقل وبدأنا مباشرةً بكتابة النقطتين الرأسيتين متبوعةً بحجم الحقل ورمز التحويل d (لأننا نتعامل مع أعداد صحيحة). أعطينا في المثال السابق حجمًا للحقل مساويًا لعدد أرقام العدد الذي نتوقع طباعته في الحقل المعني مضافًا إليه 2، لذا سيبدو الناتج كالآتي: 3 9 27 4 16 64 5 25 125 6 36 216 7 49 343 8 64 512 9 81 729 10 100 1000 11 121 1331 12 144 1728 يمكننا أيضًا تحديد حجم ثابت للحقل لنحصل على أعمدة متساوية العرض، مما يضمن إظهار الأعداد الكبيرة بصورة صحيحة: for i in range(3,13): print("{:6d} {:6d} {:6d}".format(i, i*i, i*i*i)) الناتج: 3 9 27 4 16 64 5 25 125 6 36 216 7 49 343 8 64 512 9 81 729 10 100 1000 11 121 1331 12 144 1728 يمكننا أيضًا تعديل محاذاة النص الموجود في الأعمدة باستخدام الرموز > و ^ و <، وتبديل d إلى f لإظهار منازل عشرية، وغير ذلك مما تعلمناه في هذا الدرس لإظهار البيانات الناتجة كما نرغب. الخلاصة قد يكون استخدام المنسقات لوضع القيم في سلسلةٍ نصيةٍ أمرًا مفيدًا لتسهيل تنظيم البيانات والقيم، فالمنسقات هي آليةٌ تساعد في جعل الناتج مقروءًا للمستخدم. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال How To Use String Formatters in Python 3 لصاحبته Lisa Tagliaferri. اقرأ أيضًا المقالة التالية: كيفية إجراء العمليات الحسابية المقالة السابقة: كيفية استخدام المتغيرات المرجع الشامل إلى تعلم لغة بايثون
  15. السلاسل النصية في بايثون هي مجموعةٌ مُنشأةٌ من المحارف المنفصلة والتي تُمثِّل الأحرف والأرقام والفراغات والرموز. ولأنَّ السلسلة النصية هي «سلسلة»، فيمكن الوصول إليها كغيرها من أنواع البيانات المتسلسلة، عبر الفهارس والتقسيم. سيشرح لك هذا الدرس كيفية الوصول إلى السلاسل النصية بفهارسها، وتقسيمها إلى مجموعات من المحارف، وسيُغطي طرائق إحصاء المحارف وتحديد مواضعها. آلية فهرسة السلاسل النصية وكما في نوع البيانات list الذي فيه عناصر مرتبطة بأرقام، فإنَّ كل محرف في السلسلة النصية يرتبط بفهرس معيّن، بدءًا من الفهرس 0. فللسلسلة النصية Sammy Shark!‎ ستكون الفهارس والمحارف المرتبطة بها كالآتي: وكما لاحظت، سيرتبط المحرف S بالفهرس 0، وستنتهي السلسلة النصية بالفهرس 11 مع الرمز !. لاحظ أيضًا أنَّ الفراغ بين كلمتَي Sammy و Shark له فهرسٌ خاصٌ به، وفي مثالنا سيكون له الفهرس 5. علامة التعجب (!) لها فهرسٌ خاصٌ بها أيضًا، وأيّة رموز أو علامات ترقيم أخرى مثل *#$&.;? سترتبط بفهرسٍ مخصصٍ لها. وحقيقة أنَّ المحارف في بايثون لها فهرسٌ خاصٌ بها ستعني أنَّ بإمكاننا الوصول إلى السلاسل النصية وتعديلها كما نفعل مع أنواع البيانات المتسلسلة الأخرى. الوصول إلى المحارف عبر أرقام الفهارس الموجبة يمكننا الحصول على محرف من سلسلة نصية بالإشارة إليه عبر فهرسه. يمكننا فعل ذلك بوضع رقم الفهرس بين قوسين مربعين. سنُعرِّف في المثال الآتي سلسلةً نصيةً ونطبع المحرف المرتبط بالفهرس المذكور بين قوسين مربعين: ss = "Sammy Shark!" print(ss[4]) الناتج: y عندما نُشير إلى فهرسٍ معيّنٍ في سلسلةٍ نصيةٍ، فستُعيد بايثون المحرف الموجود في ذاك الموضع، ولمّا كان المحرف y موجودًا في الفهرس الرابع في السلسلة النصية ss = "Sammy Shark!"‎ فعندما طبعنا ss[4] فظهر الحرف y. الخلاصة: أرقام الفهارس ستسمح لنا بالوصول إلى محارف معيّنة ضمن سلسلة نصية. الوصول إلى المحارف عبر أرقام الفهارس السالبة إذا كانت لديك سلسلةٌ طويلةٌ وأردنا تحديد أحد محارفها لكن انطلاقًا من نهايتها، فعندئذٍ نستطيع استخدام الأرقام السالبة للفهارس، بدءًا من الفهرس ‎-1. لو أردنا أن نستخدم الفهارس السالبة مع السلسلة النصية Sammy Shark!‎، فستبدو كما يلي: يمكننا أن نطبع المحرف r في السلسلة السابقة في حال استخدمنا الفهارس السالبة بالإشارة إلى المحرف الموجود في الفهرس ‎-3 كما يلي: print(ss[-3]) الخلاصة: يمكننا الاستفادة من الفهارس السالبة لو أردنا الوصول إلى محرف في آخر سلسلة نصية طويلة. تقسيم السلاسل النصية يمكننا أن نحصل على مجال من المحارف من سلسلة نصية، فلنقل مثلًا أننا نريد أن نطبع الكلمة Shark فقط، يمكننا فعل ذلك بإنشائنا «لقسم» من السلسلة النصية، والذي هو سلسلةٌ من المحارف الموجودة ضمن السلسلة الأصلية. فالأقسام تسمح لنا بالوصول إلى عدِّة محارف دفعةً واحدة باستعمال مجال من أرقام الفهارس مفصولة فيما بينها بنقطتين رأسيتين [x:y]: print(ss[6:11]) الناتج: Shark عند إنشائنا لقسم مثل [6:11] فسيُمثِّل أوّل رقم مكان بدء القسم (متضمنًا المحرف الموجود عند ذاك الفهرس)، والرقم الثاني هو مكان نهاية القسم (دون تضمين ذاك المحرف)، وهذا هو السبب وراء استخدمنا لرقم فهرس يقع بعد نهاية القسم الذي نريد اقتطاعه في المثال السابق. نحن نُنشِئ «سلسلةً نصيةً فرعيةً» (substring) عندما نُقسِّم السلاسل النصية، والتي هي سلسلةٌ موجودةٌ ضمن سلسلةٍ أخرى. وعندما نستخدم التعبير ss[6:11] فنحن نستدعي السلسلة النصية Shark التي تتواجد ضمن السلسلة النصية Sammy Shark!‎. إذا أردت تضمين نهاية السلسلة (أو بدايتها) في القسم الذي ستُنشِئه، فيمكننا ألّا نضع أحد أرقام الفهارس في string[n:n]. فمثلًا، نستطيع أن نطبع أوّل كلمة من السلسلة ss –أي Sammy– بكتابة ما يلي: print(ss[:5]) فعلنا ذلك بحذف رقم الفهرس قبل النقطتين الرأسيتين، ووضعنا رقم فهرس النهاية فقط، الذي يُشير إلى مكان إيقاف اقتطاع السلسلة النصية الفرعية. لطباعة منتصف السلسلة النصية إلى آخرها، فسنضع فهرس البداية فقط قبل النقطتين الرأسيتين، كما يلي: print(ss[7:]) الناتج: hark! بكتابة فهرس البداية فقط قبل النقطتين الرأسيتين وترك تحديد الفهرس الثاني، فإنَّ السلسلة الفرعية ستبدأ من الفهرس الأول إلى نهاية السلسلة النصية كلها. يمكنك أيضًا استخدام الفهارس السالبة في تقسيم سلسلة نصية، فكما ذكرنا سابقًا، تبدأ أرقام الفهارس السلبية من الرقم ‎-1، ويستمر العد إلى أن نصل إلى بداية السلسلة النصية. وعند استخدام الفهارس السالبة فسنبدأ من الرقم الأصغر لأنه يقع أولًا في السلسلة. لنستخدم فهرسين ذوي رقمين سالبين لاقتطاع جزء من السلسلة النصية ss: print(ss[-4:-1]) الناتج: ark السلسلة النصية «ark» مأخوذة من السلسلة النصية «Sammy Shark!‎» لأنَّ الحرف «a» يقع في الموضع ‎-4 والمحرف k يقع قبل الفهرس ‎-1 مباشرةً. تحديد الخطوة عند تقسيم السلاسل النصية يمكن تمرير معامل ثالث عند تقسيم السلاسل النصية إضافةً إلى فهرسَي البداية والنهاية، وهو الخطوة، التي تُشير إلى عدد المحارف التي يجب تجاوزها بعد الحصول على المحرف من السلسلة النصية. لم نُحدِّد إلى الآن الخطوة في أمثلتنا، إلا أنَّ قيمته الافتراضية هي 1، لذا سنحصل على كل محرف يقع بين الفهرسين. لننظر مرةً أخرى إلى المثال السابق الذي يطبع السلسلة النصية الفرعية «Shark»: print(ss[6:11]) Shark سنحصل على نفس النتائج بتضمين معامل ثالث هو الخطوة وقيمته 1: print(ss[6:11:1]) Shark إذًا، إذا كانت الخطوة 1 فهذا يعني أنَّ بايثون ستُضمِّن جميع المحارف بين فهرسين، وإذا حذفتَ الخطوة فستعتبرها بايثون مساويةً للواحد. أما لو زدنا الخطوة، فسنرى أنَّ بعض المحارف ستهمل: print(ss[0:12:2]) SmySak تحديد الخطوة بقيمة 2 كما في ss[0:12:2] سيؤدي إلى تجاوز حرف بين كل حرفين، ألقِ نظرةً على المحارف المكتوبة بخطٍ عريضٍ: Sammy Shark! لاحظ أنَّ الفراغ الموجود في الفهرس 5 قد أُهمِل أيضًا عندما كانت الخطوة 2. إذا وضعنا قيمةً أكبر للخطوة، فسنحصل على سلسلةٍ نصيةٍ فرعيةٍ أصغر بكثير: print(ss[0:12:4]) Sya حذف الفهرسين وترك النقطتين الرأسيتين سيؤدي إلى إبقاء كامل السلسلة ضمن المجال، لكن إضافة معامل ثالث وهو الخطوة سيُحدِّد عدد المحارف التي سيتم تخطيها. إضافةً إلى ذلك، يمكنك تحديد رقم سالب كخطوة، مما يمكِّنك من كتابة السلسلة النصية بترتيبٍ معكوس إذا استعملتَ القيمة ‎-1: print(ss[::-1]) الناتج: !krahS ymmaS لنجرِّب ذلك مرةً أخرى لكن إذا كانت الخطوة ‎-2: print(ss[::-2]) الناتج: !rh ma في المثال السابق (ss[::-2])، سنتعامل مع كامل السلسلة النصية لعدم وجود أرقام لفهارس البداية والنهاية، وسيتم قلب اتجاه السلسلة النصية لاستخدامنا لخطوةٍ سالبة. بالإضافة إلى أنَّ الخطوة ‎-2 ستؤدي إلى تخطي حرف بين كل حرفين بترتيبٍ معكوس: !krahS[whitespace]ymmaS سيُطبَع الفراغ في المثال السابق. الخلاصة: تحديد المعامل الثالث عند تقسيم السلاسل النصية سيؤدي إلى تحديد الخطوة التي تُمثِّل عدد المحارف التي سيتم تخطيها عند الحصول على السلسلة الفرعية من السلسلة الأصلية. دوال الإحصاء بعد أن تعرفنا على آلية فهرسة المحارف في السلاسل النصية، حان الوقت لمعاينة بعض الدوال التي تُحصي السلاسل النصية أو تعيد أرقام الفهارس. يمكننا أن نستفيد من ذلك بتحديد عدد المحارف التي نريد استقبالها من مدخلات المستخدم، أو لمقارنة السلاسل النصية. ولدى السلاسل النصية –كغيرها من أنواع البيانات– عدِّة دوال تُستخدم للإحصاء. لننظر أولًا إلى الدالة len()‎ التي تُعيد طول أيّ نوع متسلسل من البيانات، بما في ذلك string و lists و tuples و dictionaries. لنطبع طول السلسلة النصية ss: print(len(ss)) 12 طول السلسلة النصية «Sammy Shark!‎» هو 12 محرفًا، بما في ذلك الفراغ وعلامة التعجب. بدلًا من استخدام متغير، فلنحاول مباشرةً تمرير سلسلة نصية إلى الدالة len()‎: print(len("Let's print the length of this string.")) 38 الدالة len()‎ تُحصي العدد الإجمالي من المحارف في سلسلة نصية. إذا أردنا إحصاء عدد مرات تكرار محرف أو مجموعة من المحارف في سلسلة نصية، فيمكننا استخدام الدالة str.count()‎، لنحاول إحصاء الحرف «a» في السلسلة النصية ss = "Sammy Shark!"‎: print(ss.count("a")) 2 يمكننا البحث عن محرفٍ آخر: print(ss.count("s")) 0 صحيحٌ أنَّ الحرف «S» قد ورد في السلسلة النصية، إلا أنَّه من الضروري أن تبقي بذهنك أنَّ بايثون حساسةٌ لحالة الأحرف، فلو أردنا البحث عن حروفٍ معيّنة بغض النظر عن حالتها، فعلينا حينها استخدام الدالة str.lower()‎ لتحويل حروف السلسلة النصية إلى حروفٍ صغيرة أولًا. يمكنك قراءة المزيد من المعلومات عن دالة str.lower()‎ في درس «مقدمة إلى دوال التعامل مع السلاسل النصية في بايثون 3» لنحاول استخدام الدالة str.count()‎ مع سلسلة من المحارف: likes = "Sammy likes to swim in the ocean, likes to spin up servers, and likes to smile." print(likes.count("likes")) الناتج هو 3، حيث تتواجد مجموعة المحارف «likes» ثلاث مرات في السلسلة النصية الأصلية. يمكننا أيضًا معرفة موقع الحرف أو مجموعة الحروف في السلسلة النصية، وذلك عبر الدالة str.find()‎، وسيُعاد موضع المحرف بناءً على رقم فهرسه. يمكننا أن نعرف متى يقع أوّل حرف «m» في السلسلة النصية ss كالآتي: print(ss.find("m")) 2 أوّل مرة يقع فيها الحرف «m» في الفهرس 2 من السلسلة «Sammy Shark!‎»، يمكنك أن تراجع بداية هذا الدرس لرؤية جدول يبيّن ارتباطات المحارف مع فهارسها في السلسلة السابقة. لنرى الآن مكان أوّل ظهور لمجموعة المحارف «likes» في السلسلة likes: print(likes.find("likes")) 6 أوّل مرة تظهر فيها السلسلة «likes» هي في الفهرس 6، أي مكان وجود الحرف l من likes. ماذا لو أردنا أن نعرف موضع ثاني تكرار للكلمة «likes»؟ نستطيع فعل ذلك بتمرير معاملٍ ثانٍ إلى الدالة str.find()‎ الذي سيجعلها تبدأ بحثها من ذاك الفهرس، فبدلًا من البحث من أوّل السلسلة النصية سنبحث انطلاقًا من الفهرس 9: print(likes.find("likes", 9)) 34 بدأ البحث في هذا المثال من الفهرس 9، وكانت أوّل مطابقة للسلسلة النصية «likes» عند الفهرس 34. إضافةً إلى ذلك، يمكننا تحديد نهاية إلى مجال البحث بتمرير معامل ثالث. وكما عند تقسيم السلاسل النصية، يمكننا استخدام أرقام الفهارس السالبة للعد عكسيًا: print(likes.find("likes", 40, -6)) 64 يبحث آخر مثال عن موضع السلسلة النصية «likes» بين الفهرس 40 و ‎-6، ولمّا كان المعامل الأخير هو رقم سالب، فسيبدأ العد من نهاية السلسلة الأصلية. دوال الإحصاء مثل len()‎ و str.count()‎ و str.find()‎ مفيدةٌ في تحديد طول السلسلة النصية وعدد حروفها وفهارس ورود محارف معيّنة فيها. الخلاصة ستمنحنا القدرة على تحديد محارف بفهارسها أو تقسيم سلسلة نصية أو البحث فيها مرونةً عاليةً عند التعامل مع السلاسل النصية. ولأنَّ السلاسل النصية هي نوعٌ من أنواع البيانات المتسلسلة (كنوع البيانات list)، فيمكننا الوصول إلى عناصرها عبر فهارس خاصة. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال How To Index and Slice Strings in Python 3 لصاحبته Lisa Tagliaferri. اقرأ أيضًا المقال التالي: كيفية التحويل بين أنواع البيانات المقال السابق: مقدمة إلى دوال التعامل مع السلاسل النصية المرجع الشامل إلى تعلم لغة بايثون