ستختلف التمارين في هذا القسم عن تمارين القسم السابق. فستجد في هذا الفصل وفي الفصل الذي سبقه تمارين تتعلق بالأفكار التي سنقدمها في هذا الفصل، بالإضافة إلى سلسلة من التمارين التي سنراجع من خلالها ما تعلمناه خلال تقدمنا في مادة المنهاج، وذلك بتعديل التطبيق Bloglist الذي عملنا عليه في القسم 4 والقسم 5 ومراجعته وتطبيق المهارات التي تعلمناها.
الخطافات
تقدم المكتبة React 10 أنواع من الخطافات المدمجة، من أكثرها شهرة useState وuseEffect والتي استخدمناها كثيرًا. كما استخدمنا في القسم 5 الخطاف useImperativeHandle والذي يسمح للمكوّنات بتقديم الدوال الخاصة بها إلى مكوّنات أخرى.
بدأت مكتبات React خلال السنتين الماضيتين بتقديم واجهات برمجية مبنية على الخطافات. فقد استخدمنا في القسم السابق الخطافين useSelector وuseDispatch من المكتبة React-Redux لمشاركة مخزن Redux وإيفاد الدوال إلى المكوّنات. فواجهة Redux البرمجية المبنية على الخطافات أسهل استعمالًا من سابقتها التي لازالت قيد الاستعمال connect.
تعتبر الواجهة البرمجية React-router التي قدمناها في الفصل السابق مبنية جزئيًا على أساس الخطافات. حيث تُستعمل خطافاتها للولوج إلى معاملات العناوين وكائن المحفوظات، وتعديل العنوان على المتصفح برمجيًا.
لا تعتبر الخطافات دوال عادية كما ذكرنا في القسم 1، ولابد من الخضوع إلى مجموعة من القواعد. لنستحضر تلك القوانين التي نسخناها حرفيًا من توثيق React:
لا تستدعي الخطافات من داخل الحلقات أو العبارات الشرطية أو الدوال المتداخلة، بل استخدمها دائمًا عند أعلى مستويات دالة React.
لا تستدعي الخطافات من داخل دول JavaScript النظامية، بل يمكنك أن:
-
استدعاء الخطافات من مكوّنات دوال React
- استدعاء الخطافات من قبل خطافات مخصصة.
هنالك قاعدة يقدمها المدقق ESlint للتأكد من استدعاء الخطافات بالشكل الصحيح، حيث تُهيئ التطبيقات المبنية بواسطة create-react-app القاعدة eslint-plugin-react-hooks بشكل دائم، لكي تنبهك عند استعمال الخطاف بطريقة غير مشروعة:
خطافات مخصصة
تقدم لك Recat ميزة إنشاء خطافات خاصة بك. وتعتبر React أن الغرض الرئيسي من هذه الخطافات هو تسهيل إعادة استخدام شيفرة مكوّن في مكان آخر.
اقتباسيمكّنك بناء خطافاتك الخاصة من نقل منطق المكوّن إلى دوال قابلة للاستخدام مرة أخرى.
إنّ الخطافات المخصصة هي دوال JavaScript نظامية يمكنها استعمال أية خطافات أخرى طالما أنها تخضع لقواعد استخدام الخطافات. بالإضافة إلى ضرورة أن يبدأ اسم الخطاف بالكلمة "use".
لقد أنشأنا في القسم 1 تطبيق عداد يمكن زيادة قيمته أو إنقاصها أو تصفيره. وكانت شيفرته كالتالي:
import React, { useState } from 'react' const App = (props) => { const [counter, setCounter] = useState(0) return ( <div> <div>{counter}</div> <button onClick={() => setCounter(counter + 1)}> plus </button> <button onClick={() => setCounter(counter - 1)}> minus </button> <button onClick={() => setCounter(0)}> zero </button> </div> ) }
لننقل منطق العداد ونضعه ضمن خطاف مخصص. ستبدو شيفرة الخطاف كالتالي:
const useCounter = () => { const [value, setValue] = useState(0) const increase = () => { setValue(value + 1) } const decrease = () => { setValue(value - 1) } const zero = () => { setValue(0) } return { value, increase, decrease, zero } }
يستخدم الخطاف المخصص خطافًا آخر هو useState
. يعيد الخطاف كائنًا تتضمن خصائصه قيمة العداد بالإضافة إلى دوال لتغيير هذه القيمة.
يمكن أن يستخدم تطبيق React هذا الخطاف كالتالي:
const App = (props) => { const counter = useCounter() return ( <div> <div>{counter.value}</div> <button onClick={counter.increase}> plus </button> <button onClick={counter.decrease}> minus </button> <button onClick={counter.zero}> zero </button> </div> ) }
وهكذا يمكننا نقل حالة المكون App
ومنطق التعامل معه إلى الخطاف useCounterhook
. وسيتولى هذا الخطاف التعامل مع حالة ومنطق التطبيق. يمكن إعادة استخدام هذا الخطاف في التطبيق الذي يراقب عدد نقرات الزر الأيمن والأيسر للفأرة:
const App = () => { const left = useCounter() const right = useCounter() return ( <div> {left.value} <button onClick={left.increase}> left </button> <button onClick={right.increase}> right </button> {right.value} </div> ) }
يُنشئ التطبيق عدادين منفصلين تمامًا. يُسند الأول إلى المتغيّر left
ويُسند الثاني إلى المتغير right
.
يصعب التعامل أحيانًا مع نماذج React. يستخدم التطبيق التالي نموذجًا يطلب من المستخدم أن يُدخل اسمه وتاريخ ميلاده وطوله:
const App = () => { const [name, setName] = useState('') const [born, setBorn] = useState('') const [height, setHeight] = useState('') return ( <div> <form> name: <input type='text' value={name} onChange={(event) => setName(event.target.value)} /> <br/> birthdate: <input type='date' value={born} onChange={(event) => setBorn(event.target.value)} /> <br /> height: <input type='number' value={height} onChange={(event) => setHeight(event.target.value)} /> </form> <div> {name} {born} {height} </div> </div> ) }
لكل حقل من النموذج حالته الخاصة. ولكي نبقي حالة النموذج متزامنة مع ما يدخله المستخدم، لابدّ من تعريف معالجًا مناسبًا للحدث onChange
للتعامل مع التغيرات في عناصر الدخل.
لنعرّف الخطاف المخصص useFieldhook
الذي سيسهل إدارة حالة النموذج:
const useField = (type) => { const [value, setValue] = useState('') const onChange = (event) => { setValue(event.target.value) } return { type, value, onChange } }
تتلقى دالة الخطاف نوع حقل الدخل (مربع النص) كمعامل. وتعيد الدالة كل الصفات التي يتطلبها عنصر الدخل، وهي نوعه وقيمته ومعالج الحدث onChange
.
يمكن استخدام الخطاف بالطريقة التالية:
const App = () => { const name = useField('text') // ... return ( <div> <form> <input type={name.type} value={name.value} onChange={name.onChange} /> // ... </form> </div> ) }
نشر الصفات
يمكننا تسهيل الأمور أكثر. يحتوي الكائن name
على كل الصفات التي يحتاجها عنصر الدخل من خصائص هذا الكائن، وبالتالي يمكننا تمرير هذه الخصائص إلى العنصر باستخدام عبارة النشر كالتالي:
<input {...name} />
وكما ستجد في المثال الموجود ضمن توثيق React، سيعطي استخدام أي من الطريقتين التاليتين في تمرير الخصائص النتيجة نفسها:
<Greeting firstName='Arto' lastName='Hellas' /> const person = { firstName: 'Arto', lastName: 'Hellas' } <Greeting {...person} />
يمكن تبسيط التطبيق إلى الشكل التالي:
const App = () => { const name = useField('text') const born = useField('date') const height = useField('number') return ( <div> <form> name: <input {...name} /> <br/> birthdate: <input {...born} /> <br /> height: <input {...height} /> </form> <div> {name.value} {born.value} {height.value} </div> </div> ) }
سيسهل استخدام النماذج عندما نستخدم الخطافات المخصصة في تغليف بعض التفاصيل المزعجة المتعلقة بمزامنة الحالة.
ومن الواضح أنّ الخطافات المخصصة ليست فقط أداة لإعادة استخدام الشيفرة، بل تقدم طرق أفضل في تقسيم شيفرتنا إلى وحدات أصغر.
المزيد حول الخطافات
يمكنك الرجوع إلى قسم الخطافات في موسوعة حسوب ففيه كل ما تحتاج إلى معرفته حول الخطافات بلغة عربية ومع أمثلة عملية ويمكنك الاستزادة من هذه المصادر الأجنبية التي تستحق الاطلاع:
- Awesome React Hooks Resources
- Easy to understand React Hook recipes by Gabe Ragland
- Why Do React Hooks Rely on Call Order
التمارين 7.4 - 7.8
سنستمر في العمل على التطبيق الذي ورد في تمارين الفصل السابق React-Router من هذا القسم.
7.4 تطبيق الطرائف باستعمال الخطافات: الخطوة 1
بسط عملية إنشاء طرفة جديدة من خلال النموذج في تطبيقك باستعمال الخطاف المخصص useFieldhook
الذي عرفناه سابقًا.
المكان الطبيعي لحفظ الخطاف المخصص في تطبيقك قد يكون الملف "src/hooks/index.js/".
وانتبه عند استخدام التصدير المحدد بدلًا من الافتراضي:
import { useState } from 'react' export const useField = (type) => { const [value, setValue] = useState('') const onChange = (event) => { setValue(event.target.value) } return { type, value, onChange } } // modules can have several named exports export const useAnotherHook = () => { // ... }
فستتغير طريقة إدراج الخطاف إلى الشكل:
import { useField } from './hooks' const App = () => { // ... const username = useField('text') // ... }
7.5 تطبيق الطرائف باستعمال الخطافات: الخطوة 2
أضف زرًا إلى النموذج بحيث يمكنك مسح كل المعلومات في عناصر الإدخال:
وسع وظيفة الخطاف useFieldhook
لكي يقدم طريقة لمسح كل بيانات حقول النموذج.
وتبعًا لحلك المقترح قد تجد نفسك أمام التحذير التالي على الطرفية:
سنعود إلى هذا التحذير في التمرين التالي.
7.6 تطبيق الطرائف باستعمال الخطافات: الخطوة 3
إن لم يظهر التحذير السابق في حلك فقد أنجزت بالفعل حل هذا التمرين.
إن ظهر معك ذلك التحذير فعليك إجراء التعديلات المناسبة للتخلص من القيمة غير المناسبة للخاصية reset
ضمن المعرّف <input>.
إنّ سبب هذا التحذير هو أن تعديل التطبيق ليستعمل نشر الصفات كالتالي:
<input {...content}/>
مطابق تمامًا للشيفرة التالية:
<input value={content.value} type={content.type} onChange={content.onChange} reset={content.reset} />
لكن لا يجب أن نسند الصفة reset
إلى عنصر الإدخال input
. إنّ أحد الحلول المقترحة هو عدم استعمال صيغة النشر وكتابة الصفات كالتالي:
<input value={username.value} type={username.type} onChange={username.onChange} />
إن كنت ستستخدم هذا الحل فستخسر العديد من الميزات التي يقدمها الخطاف useFieldhook
. فكّر بدلًا من ذلك بحل يستخدم طريقة النشر.
7.7 خطاف لتطبيق الدول:
لنعد إلى التمارين 12 إلى 14 من القسم 2.
استعمل الشيفرة الموجودة على GitHub كقاعدة انطلاق.
يُستخدم التطبيق للبحث عن تفاصيل دولة محددة بالاستعانة بالواجهة https://restcountries.eu. فإن عُثر على الدولة ستُعرض معلوماتها على الشاشة.
وإن لم يُعثر على الدولة ستظهر رسالة تبلغ فيها المستخدم بذلك.
إنّ التطبيق السابق مكتمل وجاهز للعمل، لكن عليك في هذه التمرين إنشاء خطاف مخصص باسم useCountry
يستخدم للبحث عن تفاصيل الدولة التي تُمرر إلى الخطاف كمعامل.
استخدم الوصلة full name من الواجهة البرمجية السابقة لإحضار بيانات الدولة مستخدمًا الخطاف useEffect
ضمن خطافك المخصص. ولاحظ أنه من الضروري استخدام مصفوفة المعامل الثاني للخطاف useEffect
في هذا التمرين للتحكم بتوقيت تنفيذ دالة المؤثر.
7.8 الخطافات الكاملة
ستبدو الشيفرة المسؤولة عن الاتصال مع الواجهة الخلفية لتطبيق الملاحظات من القسم السابق على النحو التالي:
import axios from 'axios' const baseUrl = '/api/notes' let token = null const setToken = newToken => { token = `bearer ${newToken}` } const getAll = () => { const request = axios.get(baseUrl) return request.then(response => response.data) } const create = async newObject => { const config = { headers: { Authorization: token }, } const response = await axios.post(baseUrl, newObject, config) return response.data } const update = (id, newObject) => { const request = axios.put(`${ baseUrl } /${id}`, newObject) return request.then(response => response.data) } export default { getAll, create, update, setToken }
لا توحي الشيفرة السابقة أبدًا بأنّ تطبيقنا هو للتعامل مع الملاحظات. وباستثناء قيمة المتغير baseUrl
يمكن استخدام الشيفرة السابقة مع تطبيق قائمة المدونات للاتصال مع الواجهة الخلفية.
افصل شيفرة الاتصال مع الواجهة الخلفية ضمن خطاف مخصص لها باسمuseResource
. يكفي إحضار كل الموارد بالإضافة إلى إنشاء مورد جديد.
يمكنك تنفيذ التمرين من أجل المشروع الموجود في المستودع https://github.com/fullstack-hy2020/ultimate-hooks. للمكوّن App
لهذا المشروع الشيفرة التالية:
const App = () => { const content = useField('text') const name = useField('text') const number = useField('text') const [notes, noteService] = useResource('http://localhost:3005/notes') const [persons, personService] = useResource('http://localhost:3005/persons') const handleNoteSubmit = (event) => { event.preventDefault() noteService.create({ content: content.value }) } const handlePersonSubmit = (event) => { event.preventDefault() personService.create({ name: name.value, number: number.value}) } return ( <div> <h2>notes</h2> <form onSubmit={handleNoteSubmit}> <input {...content} /> <button>create</button> </form> {notes.map(n => <p key={n.id}>{n.content}</p>)} <h2>persons</h2> <form onSubmit={handlePersonSubmit}> name <input {...name} /> <br/> number <input {...number} /> <button>create</button> </form> {persons.map(n => <p key={n.id}>{n.name} {n.number}</p>)} </div> ) }
يعيد الخطاف المخصص مصفوفة من عنصرين كخطاف الحالة. يحتوي العنصر الأول على كل الموارد، بينما يمثل الآخر كائنًا يمكن استخدامه من أجل التغيير في مجموعة الموارد، كإنشاء مورد جديد.
إن أضفت الخطاف بشكله الصحيح يمكنك إعادة استخدامه في تطبيق الملاحظات ودليل الهاتف (شغل الخادم باستعمال الأمر npm run server
على المنفذ 3005).
ترجمة -وبتصرف- للفصل Custom hooks من سلسلة Deep Dive Into Modern Web Development
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.