الوحدة 3 · الـ State والأحداث
التعامل مع الأحداث
15 دقراءة 4 دبيئة تجريب الكود
الدرس في لمحة
اربط الـ event handlers بالطريقة الصحيحة، ومرّر إليها الوسائط، واقرأ كائن الحدث، وتحكّم في انتشار الأحداث والسلوك الافتراضي، ومرّر المعالجات إلى الـ components الأبناء كـ props.
- 1مرّر الدالة، لا تستدعها
- 2تمرير الوسائط
- 3المعالجات كـ props
- 4كائن الحدث
- 5الانتشار والسلوك الافتراضي
- 6المعالجات هي مكان الآثار الجانبية
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
يتغيّر الـ state لأن شيئًا ما يحدث: نقرة، أو ضغطة مفتاح، أو إرسال نموذج. والتعامل مع الأحداث في React قريب من الـ DOM، مع بعض الأعراف التي تُبقي الـ components مقروءة، وفخّين يقع فيهما الجميع مرة على الأقل.
مرّر الدالة، لا تستدعها
تربط event handler (معالج الحدث) بتمرير دالة إلى prop مثل onClick:
export default function ClearButton({ onClear }) {
function handleClick() {
onClear();
}
return <button onClick={handleClick}>Clear watched</button>;
}الأقواس المعقوصة تحمل handleClick، أي الدالة نفسها. تحتفظ بها React وتستدعيها حين تقع النقرة. قارن:
// Right: passes the function. React calls it on click.
<button onClick={handleClick}>Clear watched</button>
// Wrong: calls it immediately, during render.
<button onClick={handleClick()}>Clear watched</button>النسخة الثانية تنفّذ handleClick في كل مرة يُرسم فيها الـ component، وتمرّر قيمتها المُرجعة (undefined) كمعالج. وإذا كانت الدالة تضبط state، فستحصل على حلقة لا نهائية توقفها React بالخطأ «Too many re-renders». حين ترى هذا الخطأ، ابحث عن أقواس استدعاء داخل onSomething={…}.
تمرير الوسائط
أغلب المعالجات في القوائم تحتاج إلى معرفة العنصر الذي نُقر عليه. غلّف الاستدعاء بـ arrow function:
{movies.map((movie) => (
<li key={movie.id}>
{movie.title}
<button onClick={() => onRemove(movie.id)}>Remove</button>
</li>
))}تُنشأ الـ arrow function أثناء الـ render لكنها لا تُنفَّذ إلا عند النقر، وحين تُنفَّذ تستدعي onRemove بالمعرّف الصحيح. إنشاء دالة صغيرة لكل عنصر أمر طبيعي ورخيص؛ لا تلوِ كودك لتتجنّبه.
المعالجات كـ props
الـ components التي تملك state تمرّر المعالجات إلى الأسفل، ويستدعيها الأبناء ليبلّغوا بما حدث. والأعراف تجعل هذا مقروءًا:
- داخل الـ component، سمِّ المعالجات بالشكل
handleSomething:handleRemoveوhandleSubmit. - والـ props التي تستقبل المعالجات تُسمّى بالشكل
onSomething:onRemoveوonToggle، تماشيًا مع الأحداث المدمجة مثلonClick.
export default function App() {
const [movies, setMovies] = useState(INITIAL_MOVIES);
function handleRemove(id) {
setMovies(movies.filter((m) => m.id !== id));
}
return <MovieList movies={movies} onRemove={handleRemove} />;
}لا يعرف MovieList شيئًا عن طريقة الحذف. كل ما يعرفه أنه حين ينقر المستخدم Remove، يستدعي onRemove(id). ويبقى الـ state، وقواعد تغييره، في مكان واحد.
كائن الحدث
تستدعي React معالجك مع كائن حدث. إنه يغلّف الحدث الأصلي في المتصفّح، وله الواجهة المألوفة نفسها في كل متصفّح:
function handleKeyDown(e) {
if (e.key === 'Escape') {
setSelectedId(null);
}
}
<main onKeyDown={handleKeyDown}>…</main>الخصائص التي ستستخدمها أكثر من غيرها هي e.target (العنصر الذي بدأ منه الحدث)، وe.currentTarget (العنصر الذي يعمل معالجه الآن)، وe.key لأحداث لوحة المفاتيح، ودالتان: e.preventDefault() وe.stopPropagation().
الانتشار والسلوك الافتراضي
الأحداث تنتشر صعودًا (bubbling): النقرة على زر داخل بطاقة تُطلق معالج الزر، ثم معالج البطاقة، ثم معالج أي سلف. ولهذا يلتقط onKeyDown الموجود على <main> أعلاه ضغطة Escape حين يكون التركيز على أي زر بداخله. في الغالب يكون الانتشار ما تريده، لكن ليس دائمًا:
<article onClick={() => onOpen(movie.id)}>
<h2>{movie.title}</h2>
<button
onClick={(e) => {
e.stopPropagation();
onRemove(movie.id);
}}
>
Remove
</button>
</article>من دون stopPropagation، سيفتح النقر على Remove تفاصيل الفيلم أيضًا. (جعل البطاقة كلها هدفًا للنقر راحة لمستخدمي الفأرة؛ فاحتفظ بزر أو رابط حقيقي لفتح التفاصيل أيضًا، حتى يصل إليها مستخدمو لوحة المفاتيح.) احتفظ بهذا النمط للحالات التي تحتاجه؛ فاللجوء إليه في كل مكان يجعل تدفّق الأحداث صعب المتابعة.
أمّا preventDefault فمختلفة: إنها توقف السلوك المدمج في المتصفّح، مثل انتقال رابط، أو إعادة تحميل الصفحة عند إرسال نموذج. ولا توقف الانتشار. ستستخدمها في نموذج Watchlist في الدرس التالي.
المعالجات هي مكان الآثار الجانبية
يجب أن تكون الـ components نقية أثناء الـ render، لكن معالجات الأحداث لا تعمل أثناء الـ render. إنها تعمل لأن المستخدم فعل شيئًا، فهي المكان الصحيح للآثار الجانبية (side effects): ضبط الـ state، والكتابة في localStorage، وإرسال طلب، وتسجيل التحليلات. وحين لا تكون متأكّدًا أين يجب أن يعيش كود ما، اسأل: «ما الذي تسبّب فيه؟» إذا كان الجواب إجراءً محدّدًا من المستخدم، فمكانه معالج ذلك الإجراء.
في الدرس التالي تتعامل مع أهم حدث في Watchlist، وهو إرسال النموذج الذي يضيف فيلمًا، وتتعلّم كيف تُبقي الـ controlled inputs حقول النموذج والـ state متزامنين.
أهم النقاط
- مرّر دالة إلى
onClick، مثلonClick={handleClick}؛ أمّا كتابةhandleClick()فتستدعيها أثناء الـ render. - لتمرير وسيط، غلّف الاستدعاء بـ arrow function، مثل
onClick={() => onRemove(movie.id)}. - تستقبل المعالجات كائن حدث فيه
targetوkeyوpreventDefault()وstopPropagation(). - سمِّ المعالجات داخل الـ component بالشكل
handleX، وسمِّ الـ callback props بالشكلonX، ليُقرأ تدفّق البيانات بوضوح. - استخدم عناصر
<button>حقيقية للأشياء القابلة للنقر، حتى يستطيع مستخدمو لوحة المفاتيح والـ screen reader استخدامها أيضًا.