FullStackReact

نماذج React.js: المركبات التي يتم التحكم فيها (controlled component)

كتب بواسطة: 15/12/2019 لا يوجد تعليقات

المقال يغطي المركبات التي يتم التحكم فيها (controlled component) التالية: 

  • المدخلات النصية (text inputs).
  • المدخلات الرقمية (number inputs).
  • الأزرار الاختيارية (radio buttons) 
  • مدخلات صندوق الاختيار (checkbox inputs)
  • مدخلات المساحة النصية (textarea) 
  • الاختيارات (selects)

وتم تغطية أيضاً: 

  • إعادة تعيين بيانات النموذج
  • إعتماد البيانات 
  • كود التحقق (validation)

فقط تريد الكود؟ إنه هنا 

راجع النموذج التجريبي

تأكد من أن لوحة الأوامر في متصفحك مفتوحة عند تشغيل النموذج التجريبي. 

المقدمة 

المشكلة التي واجهتني عند عند تعلمي لـ React.js هو إيجاد أمثلة واقعية للمركبات التي يتم التحكم بها (controlled component)، أمثلة المدخلات النصية التي يتم التحكم بها كثيرة، لكن ماذا عن checkbox؟ Radios؟ selects؟

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

لتتعلم عن استخدام refs مع عناصر نموذج الإدخال، إنظر إلى المقال الذي قمت بكتابته في هذا الموضوع.

لتسريع وقت التطوير، أحياناً يكون من المغري إستيراد مكتبة لشيء مثل عناصر الإدخال في النموذج. عندما يتعلق الأمر بشيء مثل نماذج الإدخال، وجدت أن استخدام مكتبة يجعل الأمر أكثر صعوبة وذلك عندما أحتاج إلى إضافة سلوك (behaviour)  أو كود تحقق (validation) مخصص. بمجرد أن تعرف نمط React المناسب، فإن إنشاء مكونات نموذج الإدخال ليس صعباً، وهو شيء يجب أن نقوم به جميعًا على الأرجح. فضلاً استخدام الكود في هذا المقال كتحفيز لك أو كنقطة بداية لمركبات نموذج الإدخال الخاصة بك.

بالإضافة إلى الكود الخاص بكل مركب، قمت بوضعهم كلهم في نموذج إدخال حتى تتمكن من رؤية كيف تحدّث المركبات الابن (child component) حالة المركب الأب (parent component)، وكيف يقوم الأب بعد ذلك بتحديث المركب الابن من خلال  الخصائص (props) (البيانات باتجاه واحد unidirectional data flow).

ملاحظة: نموذج الإدخال هذا تم بناءه باستخدام الإعدادات الرائعة من create-react-app . إذا لم تقم بتحميلة حتى الآن، أنا أنصحك بشدة أن تقوم بذلك (npm install -g create-react-app).إنها أسهل طريقة للحصول على إعدادات لبناء تطبيقات React.

ما هي المركبات التي يتم التحكم بها (controlled component)؟

المركبات التي يتم التحكم بها (controlled component) لها جانبان:

  1. إن المركبات التي يتم التحكم (controlled component) لها وظائف للتحكم في البيانات التي يتم إدخالها إليها في كل حدث (event)  onChange ، بدلاً من الحصول على البيانات مرة واحدة فقط، على سبيل المثال، عندما يقوم المستخدم بنقر زر إرسال. يتم حفظ البيانات “المتحكم بها” إلى الحالة (state) (في هذه الحالة، حالة (state) العنصر الأب/الحاوية (container)).
  2. يتم إستلام البيانات التي يتم عرضها من خلال المركب الذي يتم التحكم به (controlled component) من خلال props التي تم تمريرها من المركب الأب/الحاوية (container).

هذه حلقة تكرار باتجاه واحد (one-way loop) – من (1) مركب الإدخال الإبن (2) إلى حالة (state) المركب الأب و (3) ورجوعاً إلى للمركب الإبن عن طريق props – هذا ما يعنيه البينات باتجاه واحد (unidirectional data flow) في هيكلية  تطبيقات React.js.

هيكلية نموذج الإدخال 

المركب في المستوى الأعلى سمي بـ App وها هو:

App لا تقوم بعمل شي، فقط يتم معالجتها في صفحتنا index.html. الجزء الممتع من App في السطر 13 FormContainer.

الحاجز: مركبات الحاوية (الذكية) مقابل المركبات (الخفية)

هذا هو الوقت المناسب لذكر مركبات الحاوية (container components) (الذكية) مقابل المركبات (dump components) (الخفية). مركبات الحاوية (container) هي ملجأ للعمل المنطقي، وتقوم باستدعاء  البيانات، إلخ. تتلقى المركبات العادية أو الخفية البيانات من المركب الأصلي أو الأب (Container). من الممكن أن تتبع المكونات الخفية المنطق، مثل تحديث الحالة (state)، ولكن فقط عن طريق الدوال التي يتم تمريرها من المركب الأصلي أو الأب (container).

ملاحظة: يجب أن أشير إلى أن ليس كل المركبات الأصل أو الأب هي مركبات حاوية (container)، لكن هذا ما تم إعداده في النموذج الخاص بنا. من الرائع أن يكون هناك تسلسل هرمي للمركبات الخفية خلال المركبات الخفية.

العودة إلى الهيكلية

FormContainer يحوي مكونات عنصر نموذج الإدخال، ويستدعي البيانات في دورة حياة المشبك (lifecycle hook )  componentDidMount ويحتوي على المنطق (logic) لتحديث حالة (state) النموذج. ولتبسيط ذلك، لقد تركت  الخصائص (props) وغيرت معالجات مركبات عنصر النموذج في المخطط الخارجي أدناه. (انتقل إلى نهاية المقالة للحصول على الكود الكامل.)

الآن بعد أن تم وضع البنية الأساسية، دعونا نلقي نظرة على كل عنصر من عناصر الابن (child).

<SingleInput />

هذا المكون يمكن أن يكون  مُدخل نص text أو رقم number، يعتمد ذلك على الخصائص (props) التي تم تمريرها له. طريقة رائعة لتوثيق  الخصائص (props) التي يستعملها المركب هي من خلال React’s PropTypes. في حالة عدم وجود أية خصائص (props)، أو إذا كان الـprop هو نوع بيانات خاطئ، سيظهر تحذير في لوحة التحكم (console) بالمتصفح.

يوجد أدناه الـPropTypes للمركب <SingleInput />

تشير PropTypes إلى نوع الخاصية prop (string, number, array, object, etc.))، وما إذا كان الحقل مطلوباً (isRequired)وغيرها الكثير، (انظر إلى React docs لمزيد من التفاصيل).

دعونا ننتقل إليهم واحد تلو الآخر.

  1. inputType تقبل نوعين مختلفين من النصوص: ‘text’ أو ‘number’. هذه الخيارات تحدد ما إذا كان <input type=”text” /> أو <input type=”number” /> هو الذي تم معالجته.
  2. title: يقبل سلسلة نصية يتم معالجتها كعنوان (label).
  3. Name: سمة الاسم (name) للمُدخل (input).
  4. controlFunc: هي الدالة التي يتم تمريرها من المركب الأب/الحاوية (parent/container)، هذه الدالة ستقوم بتحديث حالة المركبات للأب/الحاوية (parent/container)، في كل مرة يكون هناك تغيير، وذلك لأنه تم ربطها بمعالج React onChange
  5. Content: المحتوى للمُدخل (input). المُدخل الذي يتم التحكم به (controlled)، سوف يعرض البيانات التي تم تمريرها له عن طريق الـprops.
  6. Placeholder: وهي سلسلة نصية، تنوب عن النص في المُدخل (input).

وبما أننا لا نحتاج إلى أي منطق أو حالة (state) داخلية  للمُدخل (input) الخاص بنا، فإنه يمكن أن يكون مركب وظيفي (functional component) خالص. يتم إرفاق المكونات الوظيفية (functional component) الخالصة بـ const. هنا الكود الكامل لـ  <SingleInput />. جميع  عناصر مركبات النموذج في هذا المقال هي مركبات وظيفية (functional component).

الدالة handleFullNameChange  (يتم تمريرها في controlFunc prop) تقوم بتحديث حالة <FormContainer />

حالة(state)الحاوية (container) الجديدة، يتم تمريرها مرة أخرى لـ <SingleInput /> عن طريق الـcontent prop

<Select />

المركب select ( مثال: القائمة المنسدلة (dropdown))، تأخذ الخصائص (props)  التالية:

  1. Name: سلسلة نصية سوف تزود سمة الاسم (name) لعنصر نموذج الإدخال.
  2. Options: مصفوفة (من النصوص في حالتنا هذه)، بحيث سيكون بند خيار من الخيارت باستخدام props.options.map() في دالة المعالجة للمركبات.
  3. selectedOption: عندما نقوم بتزويد نموذج الإدخال، إما ببيانات إفتراضية، أو بينات قام المسنخدم بإضافتها في الماضي (على سبيل المثال: يتم استخدام هذا حين يقوم المستخدم بتعديل بيانات قام باعتمادها في وقت سابق). 
  4. controlFunc:  هي الدالة التي يتم تمريرها من المركب الأب/الحاوية (parent/container)، هذه الدالة ستقوم بتحديث حالة المركبات للأب/الحاوية (parent/container)، في كل مرة يكون هناك تغيير، وذلك لأنه تم ربطها بمعالج React onChange
  5. Placeholder: سلسلة نصية (string) تقوم بتزويد الخيار الأول لـ <option>، وتعمل كـ placeholder للنص. قمنا بترك هذا الخيار فارغاً، في المركب ( انظر إلى السطر 10 بالاسفل).

لاحظ أن سمة key في الخيار الخاص بنا (سطر 14). تتطلب React مفتاحاً (key) فريداً لكل عنصر يتم معالجته من خلال عمليات مكررة، مثل دالة .map(). وبما أن كل عنصر في مصفوفة الخيارات الخاصة بنا مختلفة، نستطيع استخدامها كـkey prop. هذا المفتاح (key) يساعد React على تتبع تغيرات الـDOM. التطبيق لن ينكسر أو يفشل في المعالجة إذا لم تقم بوضع سمة key في دالة التكرار (repeater/mapping) الخاصة بك، لكن سوف يكون لديك تحذيرات في لوحة التحكم (console) بالمتصفح، وسيكون الأداء عرضة للخطر.

بالأسفل دالة المعالجة ( التي يتم تمريرها في controlFun prop من <FormContainer />) والتي تتحكم بالـ select الخاص بنا( تذكر: إنها تكون موجودة في <FormContainer />).

<CheckboxOrRadioGroup />

خلافاً عن المركبات الأخرى، المركب <CheckboxOrRadioGroup /> يتم أخذه في مصفوفة من خلال الـprops الخاصة به، يقوم بعمل خريطة للمصفوفة ( تماماً مثل الخيارات في مركب <Select /> بالأعلى)، وتعالج مجموعة من عناصر نموذج الإدخال، إما مجموعة من خانات الاختيار (checkboxs) أو مجموعة من الـ Radios.

دعنا نغوص في PropTypes لنفهم أكثر <CheckboxOrRadioGroup />.

  1. Title: سلسلة نصية (string) لتزيد العنوان (label) لمجموعة من خانات الاختيار (checkboxs) أو مجموعة من الـ radios.
  2. Type: تأخذ واحد من اثنين من الخيارات الممكنة ‘checkbox’ أو ‘radio’، وتقوم بمعالجة المُدخل (input) بالنوع المحدد.
  3. setName: سلسلة نصية (string) سوف تقوم بتزويد سمة الاسم (name) لكل checkbox/radio.
  4. Options: مصفوفة، في حالتنا هذه هي مصفوفة من الـstrings، تحدد العنوان (label) و القيمة لكل checkbox/radio. على سبيل المثال [‘dog’, ‘cat’, ‘pony’]، سوف تعالج ثلاثة checkboxes/radios، واحدة لكل عنصر في المصفوفة.
  5. selectedOptions: مصفوفة، وفي حالتنا هذه هي مصفوفة من الـstrings، لخيارات لم يتم اختياراها بعد (pre-selected). في المثال المستخدم في رقم 4 أعلاه، لو كان selectedOptions يحتوي على ‘dog’ و ‘pony’، فهاذين الخيارين سوف يتم معالجتهما كتم اختيارهما (checked)، و ‘cat’ سوف يتم معالجتها كـ لم يتم اختياره (unchecked). هذه المصفوفة سوف يتم اعتمادها حسب اختيارات المستخدم.
  6. controlFunc: الدالة التي تقوم بإضافة وإزالة السلاسل النصية (strings) من الاستخدام كـ selectedOptions prop.

هذا هو المركب الأكثر متعبة في نموذج الإدخال الخاص بنا، ها هو الكود :

المنطق الذي يحدد ما إذا كان الـ radio/checkbox تم اختيارهمها أم لا موجودة في السطر:

checked={ props.selectedOptions.indexOf(option) > -1 }

سمة checked الموجودة في المدخل (input)تأخذ قيمة منطقية (Boolean)، لتحدد ما إذا كان المُدخل (input) يجب أن يكون محدد (checked) أم لا. نقوم بإنشاء هذه المنطقية (Boolean)عن طريق التحقق من قيمة المُدخل وما إذا كان العنصر موجود في مصفوفة props.selectedOptions.
myArray.indexOf(item) تٌرجع فهرس (index) العنصر في مصفوفة.إذا كان العنصر ليس موجوداً في المصفوفة سوف تُرجع -1. أي أن لدينا < -1.

تذكر أن الفهرس (index) ذو القيمة 0 شرعي أو متاح، إذا تحتاج إلى < -1 ، أو سيكون الكود الخاص بك متاحاً؛ وبدونها العنصر الأول في مصفوفة selectedOptions – والذي له الفهرس (index) 0 – لن يكون checked أبداً، لأن 0 تعطي دائماً خطأ (false).

الدالة المعالجة لهذا المركب أيضاً أكثر متعة من غيرها.

كما في كل دالة معالجة، كائن الحدث (event object)، يتم تمريره في الدالة، فنستطيع أن نستخرج قيمته. نربط هذه القيمة للثابت newSelection. ثم نقوم بتعريف المتغير newSelectionArray بالقرب من أعلى الدالة. يوجد المتغير let  بدلاً من const، لأنه سيتم تعيينه خلال واحد من بلوكات if/else. نقوم بتعريفه خارج هذه البلوكات، فتكون خارج النطاق (scope)، وتكون متاحة لجميع البلوكات خلالها.

هذه الدالة تعالج حالتين: 

  1. إذا كانت قيمة المُدخل (input) ليست في مصفوفة selectedOptions، و تحتاج إلى أن يتم إضافتها.
  2. إذا كانت قيمة المُدخل (input) موجودة في مصفوفة selectedOptions، وتحتاج أن يتم إزالتها.

الإضافة ( من السطر 8 – 10): لإضافة قيمة جديدة للمصفوفة من الخيارات (selections)، نقوم بإنشاء مصفوفة جديدة عن طريق إتلاف المصفوفة الأصلية ( تحدد عن طريق ثلاث نقاط … مقابل المصفوفة)وتضيف القيمة الجديدة للنهاية.
newSelectionArray = […this.state.selectedPets, newSelection];

لاحظ أن المصفوفة الأصلية ليست متحولة عن دالة (method) مثل  .push()، ولكن بدلاً من ذلك يتم إنشاء مصفوفة جديدة. هذه الممارسة في إنشاء كائنات (objects) جديدة ومصفوفات بدلاً من استخدام الدوال الموجودة وتعديلها هي أفضل ممارسة في React.وهذا يسمح للمطورين بمتابعة تغيير الحالة بسهولة أكبر، ويسمح لمكتبات إدارة الدولة التابعة لجهات أخرى مثل Redux بإجراء فحص سطحي عالي الأداء لأنواع البيانات بدلاً من إعاقة الأداء في التدقيق العميق.

الإزالة ( من السطر 6- 8) بلوك if يتحقق لمعرفة ما إذا الـselection  في المصفوفة عن طريق استخدام خدعة .indexOf() بالأعلى. إذا كان الـ selection موجود بكل الأحوال في المصفوفة، يتم إزالته عن طريق دالة المصفوفة في JavaScript .filter(). هذه الدالة تُرجع مصفوفة جديدة ( تذكر لمنع التغيير في React!)، باحتواء كل العناصر التي تستوفي شرط الـ filter.

في هذه الحالة كل الـselections يتم إرجاعهم ما عدا التي تم تمريرها في الدالة. 

<TextArea />

مركب <TextArea /> شبيه جداً للمركبات التي تم تغطيتها. خصائصه (props) يجب أن تكون مألوفة لك الآن، ما عدا خاصية resize و rows.

  1. Title: تقبل سلسلة نصية (string) يتم معالجتها في عنوان (label) textarea
  2. Rows: تقبل رقم صحيح والذي يحدد عدد ارتفاع الصفوف في الـ textarea.
  3. Name: سمة الاسم (name) في  الـtextarea.
  4. Content: المحتوى في الـtextarea. المُدخل الذي يتم التحكم به (controlled)، سوف يعرض البيانات التي التي تمريرها له عن طريق الـprops.
  5. Resize: تقبل المنطقية (Boolean)، والتي يتم تحديدها إذا كان الـtextarea يمكن تغيير حجمه (resizable).
  6. Placeholder: سلسلة نصية (string) والتي تكون placeholder للنص في الـtextarea.
  7. controlFunc: هي الدالة التي يتم تمريرها من المركب الأب/الحاوية (parent/container)، هذه الدالة ستقوم بتحديث حالة المركبات للأب/الحاوية (parent/container)، في كل مرة يكون هناك تغيير، وذلك لأنه تم ربطها بمعالج React onChange

الكود الكامل لـ <TextArea /> : 

تعمل دالة التحكم في <TextAreas/> بنفس طريقة <SingleInput/>. الرجاء الرجوع إلى <SingleInput/> للحصول على مزيد من التفاصيل.

Form Actions (إجراءات النموذج)

هناك دالتين تعملان في نموذج الإدخال (form) بالمجمل، handleClearForm و handleFormSubmit

  1. handleClearForm

بما أننا نستخدم اتجاه واحد للبيانات (unidirectional data flow) خلال نموذج الإدخال الخاص بنا. مسح خيارات النموذج هو شيء خفيف. يتم التحكم في كل قيمة لكل عنصر من خلال حالة  <FormContainer />. يتم تمرير حالة )state) الحاوية (container) إلى المركبات الفرعية أو الابن (child) من خلال الـ props. تتغير القيم التي يتم عرضها بواسطة مكونات النموذج فقط عندما تتغير حالة <FormContainer />.

يكون مسح البيانات المعروضة في المركبات الفرعية  أو الابن (child) للنموذج سهلاً بسهولة إعداد حالة (state) الحاوية (container) إلى  مصفوفة فارغة أو سلسلة نصية فارغة (و0 في حالة الإدخال لدينا والتي هي أرقام).

يمنع e.preventDefault() الصفحة من إعادة التحميل، وتزيل الدالة setState() بيانات  النموذج.

  1. handleFormSubmit

لاعتماد بيانات هذا النموذج، نقوم ببناء كائن (object)  من خصائص الحالة (state) المناسبة. ثم نستخدم مكتبة AJAX أو تقنية لإرسال هذه البيانات إلى API (والتي لا يغطيها هذا المقال).

لاحظ أن النموذج تم مسح بياناته بعد الاعتماد، عن طريق استدعاء this.handleClearForm(e).

التحقق من الصحة (Validation)

تعتبر مركبات نموذج الإدخال التي يتم التحكم بها (controlled component) أساساً جيدا لكود التحقق المخصص. لنفترض أنك تريد استبعاد الحرف ‘e’ من مركب <TextArea />.


يتم إنشاء textArray أعلاه بتقسيم السلسلة النصية  e.target.value إلى مصفوفة من الأحرف الفردية. ثم يتم تصفية الحرف ‘e’ (أو أي حرف تريد إستبعاده). يتم ضم مجموعة الأحرف مرة أخرى، ويتم تعيين السلسلة النصية (string) الجديدة إلى حالة (state) المركب. ليس سيئاً!

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

<FormContainer />

كما وعدتكم هذا الكود الكامل لمركب <FormContainer />:

الخاتمة

مما لا شك فيه أن بناء المركبات التي يتم التحكم فيها (controlled component) في نموذج الإدخال باستخدام React  يتطلب بعض الممارسة والتكرار (على سبيل المثال،الدالة المعالجة في الحاوية (container))، لكن التحكم الذي تملكه على التطبيق الخاص بك وقدرة تغيير الحالة يستحق هذا الجهد. سيكون الكود الخاص بك قابل للصيانة، و له أداء فائق.

إذا كنت ترغب في أن يصلك تنبيهات عندما أنشر مقالة جديدة، يمكنك التسجيل على قائمتي البريدية في navbar  المدونة.

اترك تعليق