پاسخگویی به رویدادها
ریاکت به شما اجازه میدهد که به JSX خود event handlerها را اضافه. Event handlerها توابع خود شما هستند که در زمان پاسخگویی به تعاملاتی مانند کلیک کردن، hover کردن، تمرکز کردن روی input های فرم و دیگر چیزها فعال میشوند.
یاد خواهید گرفت
- روش های مختلف برای نوشتن یک event handler
- نحوه انتقال منطق مدیریت رویداد از کامپوننت پدر
- نحوه انتشار رویدادها و چگونگی توقف آنها
اضافه کردن event handlerها
برای افزودن یک event handler، ابتدا یک تابع تعریف میکنید و سپس آن را به عنوان props به تگ JSX مناسب پاس میدهید. به عنوان مثال، در اینجا دکمه ای وجود دارد که هنوز هیچ کاری انجام نمی دهد:
export default function Button() { return ( <button> I don't do anything </button> ); }
با دنبال کردن این سه مرحله می توانید کاری کنید که وقتی کاربر کلیک می کند پیامی نشان دهد:
- تابعی به نام
handleClickدر داخل کامپوننتButtonخود تعریف کنید. - منطق را در داخل آن تابع پیاده سازی کنید (از
alertبرای نمایش پیام استفاده کنید). onClick={handleClick}را به JSX المنت<button>اضافه کنید.
export default function Button() { function handleClick() { alert('You clicked me!'); } return ( <button onClick={handleClick}> Click me </button> ); }
شما تابع handleClick را تعریف کردید و سپس آن را به عنوان یک prop به <button> پاس دادید. handleClick یک event handler است. عملکردهای event handler:
- معمولاً داخل کامپوننت شما تعریف می شوند.
- نام هایی دارند که با
handleشروع می شوند و به دنبال آن، نام رویداد می آید.
طبق قرارداد، نامگذاری event handlerها به این صورت رایج است که ابتدا handle و به دنبال آن نام رویداد میآید. اغلب onClick={handleClick}، onMouseEnter={handleMouseEnter}و غیره را خواهید دید.
از طرف دیگر، می توانید یک event handler به صورت درخط (inline) در JSX تعریف کنید:
<button onClick={function handleClick() {
alert('You clicked me!');
}}>یا به طور خلاصه تر، استفاده از یک arrow function:
<button onClick={() => {
alert('You clicked me!');
}}>همه این مدلها معادل هستند. event handler های درخط (inline) برای عملکردهای کوتاه مناسب هستند.
خواندن props در event handlerها
از آنجایی که event handlerها در داخل یک کامپوننت تعریف میشوند، به propهای آن کامپوننت دسترسی دارند. در اینجا دکمهای وجود دارد که با کلیک روی آن، یک هشدار با prop message خود نشان می دهد:
function AlertButton({ message, children }) { return ( <button onClick={() => alert(message)}> {children} </button> ); } export default function Toolbar() { return ( <div> <AlertButton message="Playing!"> Play Movie </AlertButton> <AlertButton message="Uploading!"> Upload Image </AlertButton> </div> ); }
این به دو دکمه اجازه می دهد پیام های متفاوتی را نشان دهند. امتحان کنید پیام های پاس داده شده به آنها را تغییر دهید.
پاس دادن event handlerها به عنوان props
اغلب شما می خواهید که کامپوننت پدر، event handler فرزند را مشخص کند. دکمهها را در نظر بگیرید: بسته به جایی که از کامپوننت Button استفاده میکنید، ممکن است بخواهید عملکرد متفاوتی را اجرا کنید—شاید یکی فیلمی را پخش کند و دیگری تصویری را آپلود کند.
برای انجام این کار، propی را که کامپوننت از پدر خود به عنوان event handler دریافت میکند، پاس دهید:
function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); } function PlayButton({ movieName }) { function handlePlayClick() { alert(`Playing ${movieName}!`); } return ( <Button onClick={handlePlayClick}> Play "{movieName}" </Button> ); } function UploadButton() { return ( <Button onClick={() => alert('Uploading!')}> Upload Image </Button> ); } export default function Toolbar() { return ( <div> <PlayButton movieName="Kiki's Delivery Service" /> <UploadButton /> </div> ); }
در اینجا، کامپوننت Toolbar یک PlayButton و یک UploadButton را رندر میکند:
handlePlayClick،PlayButtonرا بهعنوانonClickprop بهButtonداخل پاس میدهد.UploadButtonهمalert('Uploading!') <= ()را به عنوانonClickprop بهButtonداخل پاس میدهد.
در نهایت، کامپوننت Button شما یک prop به نام onClick را میگیرد. آن را مستقیماً با onClick={onClick} به <button> داخلی مرورگر پاس میدهد. این به ریاکت میگوید که با کلیک، تابع پاس داده شده را فراخوانی کند.
اگر از design system استفاده می کنید، معمولاً کامپوننتهایی مانند دکمه ها دارای استایل هستند، اما رفتار را مشخص نمی کنند. در عوض، کامپوننتهایی مانند PlayButton و event handler UploadButton ها را به پایین پاس میدهند.
نامگذاری propهای event handler
اجزای داخلی مانند <button> و <div> فقط از نام رویدادهای مرورگر مانند onClick پشتیبانی میکنند. با این حال، زمانی که کامپوننتهای خود را میسازید، میتوانید به هر نحوی که دوست دارید، propهای event handler را نامگذاری کنید.
طبق قرارداد، propهای event handler باید با on شروع شوند و به دنبال آن یک حرف بزرگ بیاید.
به عنوان مثال، onClick prop کامپوننت Button را میتوان onSmash نامید:
function Button({ onSmash, children }) { return ( <button onClick={onSmash}> {children} </button> ); } export default function App() { return ( <div> <Button onSmash={() => alert('Playing!')}> Play Movie </Button> <Button onSmash={() => alert('Uploading!')}> Upload Image </Button> </div> ); }
در این مثال، <button onClick={onSmash}> نشان می دهد که <button> (حروف کوچک) مرورگر همچنان به یک prop به نام onClick نیاز دارد، اما نام prop دریافت شده توسط کامپوننت Button سفارشی شما، در اختیار شما است.
هنگامی که کامپوننت شما از تعاملات متعدد پشتیبانی می کند، ممکن است براساس مفاهیم خاص برنامه، propهای event handler را نامگذاری کنید. برای مثال، این کامپوننت event handler ،Toolbarهای onPlayMovie و onUploadImage را دریافت میکند:
export default function App() { return ( <Toolbar onPlayMovie={() => alert('Playing!')} onUploadImage={() => alert('Uploading!')} /> ); } function Toolbar({ onPlayMovie, onUploadImage }) { return ( <div> <Button onClick={onPlayMovie}> Play Movie </Button> <Button onClick={onUploadImage}> Upload Image </Button> </div> ); } function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); }
توجه کنید که چگونه کامپوننت App نیازی به دانستن اینکه Toolbar چه کاری با onPlayMovie یا onUploadImage میخوهد انجام دهد، ندارد. این جزییات پیاده سازی Toolbar است. در اینجا، Toolbar آنها را بهعنوان کنترلکننده onClick به Buttonهای خود پاس میدهد، اما بعداً میتواند آنها را با کلیک نیز فعال کند. نامگذاری ابزارها بر اساس مفاهیم خاص برنامه مانند onPlayMovie به شما این امکان را میدهد که نحوه استفاده از آنها را بتوانید بعداً تغییر دهید.
انتشار رویداد
event handlerها همچنین رویدادها را از هر فرزندی که ممکن است کامپوننت شما داشته باشد، دریافت میکنند. ما می گوییم که یک رویداد “حباب” یا “تکثیر” میشود به بالای درخت: از جایی که رویداد اتفاق افتاده شروع می شود، و سپس به بالای درخت می رود.
این <div> حاوی دو دکمه است. هم <div> و هم دکمهها، کنترل کننده های onClick خود را دارند. فکر میکنید با کلیک روی یک دکمه، کدام کنترل کنندهها فعال میشوند؟
export default function Toolbar() { return ( <div className="Toolbar" onClick={() => { alert('You clicked on the toolbar!'); }}> <button onClick={() => alert('Playing!')}> Play Movie </button> <button onClick={() => alert('Uploading!')}> Upload Image </button> </div> ); }
اگر روی هر یک از دکمهها کلیک کنید، onClick آن ابتدا اجرا میشود و سپس onClick المنت <div> پدر اجرا میشود. بنابراین دو پیام ظاهر می شود. اگر روی نوار ابزار کلیک کنید، فقط onClick المنت <div> پدر اجرا خواهد شد.
توقف انتشار
event handlerها یک event object را به عنوان تنها آرگومان خود دریافت می کند. طبق قرارداد، معمولا e نامیده می شود که مخفف “event” است. می توانید از این شی برای خواندن اطلاعات مربوط به رویداد استفاده کنید.
این event object همچنین به شما امکان می دهد انتشار را متوقف کنید. اگر میخواهید از رسیدن یک رویداد به کامپوننتهای پدر جلوگیری کنید، باید ()e.stopPropagation را مانند این کامپوننت Button فراخوانی کنید:
function Button({ onClick, children }) { return ( <button onClick={e => { e.stopPropagation(); onClick(); }}> {children} </button> ); } export default function Toolbar() { return ( <div className="Toolbar" onClick={() => { alert('You clicked on the toolbar!'); }}> <Button onClick={() => alert('Playing!')}> Play Movie </Button> <Button onClick={() => alert('Uploading!')}> Upload Image </Button> </div> ); }
وقتی روی دکمهای کلیک می کنید:
- ریاکت کنترلکننده
onClickی که به<button>پاس داده شده است را که فراخوانی میکند. - آن کنترل کننده که در
Buttonتعریف شده است، کارهای زیر را انجام می دهد:()e.stopPropagationرا فراخوانی می کند و از بالا رفتن رویداد جلوگیری میکند.- تابع
onClickرا فراخوانی می کند، که propی است که از کامپوننتToolbarپاس داده شدهاست.
- این تابع، که در کامپوننت
Toolbarتعریف شده است، هشدار خود دکمه را نمایش می دهد. - از آنجایی که انتشار متوقف شده، کنترل کننده
onClickالمنت<div>پدر اجرا نمی شود.
در نتیجهی ()e.stopPropagation، کلیک کردن روی دکمهها فقط یک هشدار (از <button>) به جای دو مورد (از<button> و <div> نوارابزار پدر ) را نشان میدهد. کلیک کردن روی یک دکمه با کلیک کردن روی نوار ابزار اطراف یکسان نیست، بنابراین توقف انتشار برای این رابط کاربری منطقی است.
مرور عمیق
در موارد نادر، ممکن است لازم باشد همه رویدادهای المنتهای فرزند را دریافت کنید، حتی اگر انتشار آنها متوقف شود. برای مثال، شاید بخواهید بدون در نظر گرفتن منطق انتشار، هر کلیک را برای تجزیه و تحلیل ثبت کنید. می توانید این کار را با افزودن Capture در انتهای نام رویداد انجام دهید:
<div onClickCapture={() => { /* this runs first */ }}>
<button onClick={e => e.stopPropagation()} />
<button onClick={e => e.stopPropagation()} />
</div>هر رویداد در سه فاز منتشر می شود:
- به سمت پایین حرکت میکند و همه کنترلکنندههای
onClickCaptureرا فراخوانی میکند. - کنترل کننده
onClickالمنت کلیک شده را اجرا می کند. - به سمت بالا حرکت می کند و همه کنترل کننده های
onClickرا فرا می خواند.
capture eventها برای کدهایی مانند روترها یا تجزیه و تحلیل مفید هستند، اما احتمالاً از آنها در کد برنامه استفاده نخواهید کرد.
پاسدادن کنترلکنندهها به عنوان جایگزینی برای انتشار
توجه داشته باشید که چگونه این کنترل کننده کلیک، یک خط کد را اجرا می کند و سپس onClick prop را که توسط پدر پاس داده شده است فراخوانی می کند:
function Button({ onClick, children }) {
return (
<button onClick={e => {
e.stopPropagation();
onClick();
}}>
{children}
</button>
);
}میتوانید قبل از فراخوانی onClick event handler پدر، کد بیشتری به این کنترلکننده اضافه کنید. این الگو یک جایگزین برای انتشار فراهم می کند. به کامپوننت فرزند اجازه می دهد رویداد را مدیریت کند، در حالی که همچنین به کامپوننت پدر هم اجازه می دهد برخی رفتارهای اضافی را مشخص کند. برخلاف انتشار، این کار خودکار نیست. اما مزیت این الگو این است که می توانید به وضوح کل زنجیره کدی را که در نتیجه یک رویداد اجرا می شود دنبال کنید.
اگر به انتشار تکیه میکنید و ردیابی اینکه کدام کنترلکنندهها اجرا میشوند و چرا، دشوار است، این روش را امتحان کنید.
جلوگیری از رفتار پیشفرض
برخی از رویدادهای مرورگر دارای رفتار پیشفرض مرتبط با آنها هستند. به عنوان مثال، یک رویداد ارسال <form> که زمانی اتفاق میافتد که روی دکمهای در داخل آن کلیک میشود، بهطور پیشفرض کل صفحه را دوباره reload میکند:
export default function Signup() { return ( <form onSubmit={() => alert('Submitting!')}> <input /> <button>Send</button> </form> ); }
میتوانید ()e.preventDefault را روی event object فراخوانی کنید تا این اتفاق نیفتد:
export default function Signup() { return ( <form onSubmit={e => { e.preventDefault(); alert('Submitting!'); }}> <input /> <button>Send</button> </form> ); }
()e.stopPropagation و ()e.preventDefault را با هم اشتباه نگیرید. هر دو مفید هستند، اما ارتباطی به هم ندارند:
()e.stopPropagationهمه event handler های متصل به tagهای بالا را متوقف میکند.()e.preventDefaultاز رفتار پیشفرض مرورگر برای معدود رویدادهایی که دارای آن هستند جلوگیری میکند.
آیا event handlerها می توانند عوارض جانبی داشته باشند؟
قطعا! رویدادها بهترین مکان برای عوارض جانبی هستند.
برخلاف توابع rendering، نیازی نیست که event handlerها خالص (pure) باشند، بنابراین مکان بسیار خوبی برای تغییر چیزها هستند—به عنوان مثال، تغییر مقدار ورودی در پاسخ به تایپ کردن، یا تغییر یک لیست در پاسخ به فشار دادن دکمه. با این حال، برای تغییر برخی اطلاعات، ابتدا به روشی برای ذخیره آن نیاز دارید. در ریاکت، این کار با استفاده از state، حافظه یک کامپوننت انجام می شود که در صفحه بعد همه چیز را در مورد آن خواهید آموخت.
Recap
- میتوانید با پاسدادن یک تابع به عنوان prop به المنتی مانند
<button>، رویدادها را مدیریت کنید. - event handlerها باید پاس داده شوند نه اینکه فراخوانی شوند!
onClick={handleClick}، نهonClick={handleClick()}. - event handlerها در داخل یک کامپوننت تعریف شده اند، بنابراین می توانند به props دسترسی داشته باشند.
- شما می توانید یک event handler را در یک پدر تعریف کنید و آن را به عنوان یک prop به فرزند پاس دهید.
- میتوانید propهای event handler خود را با نامهایی بر اساس مفاهیم خاص برنامه تعریف کنید.
- رویدادها به سمت بالا منتشر می شوند. برای جلوگیری از آن،
()e.stopPropagationرا در اولین آرگومان فراخوانی کنید. - رویدادها ممکن است رفتار ناخواسته پیش فرض مرورگر را داشته باشند. برای جلوگیری از آن،
()e.preventDefaultرا فراخوانی کنید. - فراخوانی صریح یک event handler prop از یک کنترلکننده فرزند جایگزین خوبی برای انتشار است.
Challenge 1 of 2: درست کردن یک event handler
با کلیک بر روی این دکمه، پسزمینه صفحه بین سفید و سیاه تغییر میکند. با این حال، وقتی روی آن کلیک می کنید، هیچ اتفاقی نمی افتد. مشکل را حل کنید. (نگران منطق داخل handleClick نباشید - این بخش درست خواهد بود.)
export default function LightSwitch() { function handleClick() { let bodyStyle = document.body.style; if (bodyStyle.backgroundColor === 'black') { bodyStyle.backgroundColor = 'white'; } else { bodyStyle.backgroundColor = 'black'; } } return ( <button onClick={handleClick()}> Toggle the lights </button> ); }