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

مكونات React الأساسية (React Components)


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

يبيّن هذا الدرس كيفية استخدام عقد 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.
  • ترتيب مرحلة التركيب:
  • التهيئة الابتدائية
    1. getDefaultProps()‎ ‏(React.createClass) أو MyComponent.defaultProps (صنف ES6)
    2. getInitialState()‎ ‏(React.createClass) أو this.state (دالة بانية في ES6)
    3. componentWillMount()‎
    4. render()‎
  • عملية تهيئة الأبناء وضبط دورة حياتهم
    1. componentDidMount()‎
  • ترتيب مرحلة التحديث:
    1. componentWillReceiveProps()‎
    2. shouldComponentUpdate()‎
    3. render()‎
  • توابع دورة الحياة للأبناء
    1. componentWillUpdate()‎
  • ترتيب مرحلة الإزالة:
    1. componentWillUnmount()‎
    2. توابع دورة الحياة للأبناء
    3. إزالة نسخة المكوِّن

الوصول إلى المكونات أو العقد الأبناء

إذا احتوى مكوِّنٌ على مكونات أبناء أو عقد 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()‎ هو عملية التحميل الابتدائية للمكوِّن وجميع المكونات الفرعية له. وبعد عملية التركيب الابتدائية للمكوِّنات، ستحدث إعادة التصيير عندما:

  1. استدعي التابع setState()‎ في المكوِّن
  2. استدعي التابع 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


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...