پاسخگویی به رویدادها در React

ری اکت به شما اجازه میدهد که توابع مدیریت رویداد (Event Handler) را به JSX خود اضافه کنید. مدیریت رویداد تابعی است که در پاسخ به تعاملاتی مانند کلیک، رفتن موس رو المان، و فوکوس بر روی ورودیهای فرم، فعال میشوند.
افزودن مدیریت رویداد
برای افزودن یک مدیر رویداد، ابتدا باید یک تابع تعریف کنید و سپس آن را به عنوان ورودی به تگ JSX مورد نظر ارسال کنید. به عنوان مثال، در اینجا یک دکمه داریم که هنوز هیچ کاری انجام نمیدهد:
export default function Button() {
return (
<button>
I don't do anything
</button>
);
}
با دنبال کردن سه مرحله زیر می توانید کد را طوری تغییر دهید که هنگام کلیک کاربر، پیامی را نمایش دهد:
- تابعی به نام
handleClick
درون کامپوننتButton
خود تعریف کنید. - منطق درون آن تابع را پیادهسازی کنید (از
alert
برای نمایش پیام استفاده کنید). onClick={handleClick}
را به JSX دکمه<button>
اضافه کنید.
مشاهده کد و نتیجه اجرا آن در CodeSandbox
export default function Button() {
function handleClick() {
alert('You clicked me!');
}
return (
<button onClick={handleClick}>
Click me
</button>
);
}
تابع handleClick
را تعریف کردید و سپس آن را به عنوان یک ورودی به <button>
ارسال کردید. handleClick
یک مدیر رویداد است. توابع مدیر رویداد:
- معمولاً درون کامپوننتهای شما تعریف میشوند.
- معمولا نامهایی دارند که با
handle
شروع میشوند و پس از آن نام رویداد قرار میگیرد.
به طور کلی، معمول است که نام تابع مدیریت رویداد را با handle
بعلاوه نام رویداد نامگذاری کنید. شما در کدهای React معمولاً دستوراتی به صورت onClick={handleClick}
، onMouseEnter={handleMouseEnter}
و غیره را به وفور میبینید.
به طور جایگزین، میتوانید یک مدیر رویداد را به صورت درونخطی نیز در JSX تعریف کنید:
<button onClick={function handleClick() {
alert('You clicked me!');
}}>
یا به طور مختصرتر، با استفاده از یک تابع پیکانی (Arrow):
<button onClick={() => {
alert('You clicked me!');
}}>
همه این سبکها معادل هم هستند. مدیریت رویداد درونخطی برای توابع کوتاه مناسبتر است.
توابع ارسال شده بعنوان مدیریت رویداد باید بعنوان ورودی به تگ ارسال شوند، نه اینکه در زمان مقدار دهی به ویژگی فراخوانی شوند. به عنوان مثال:
ارسال یک تابع (صحیح) | فراخوانی یک تابع (نادرست) |
---|---|
<button onClick={handleClick}> | <button onClick={handleClick()}> |
در مثال اول، تابع handleClick
به عنوان یک مدیر رویداد به onClick
ارسال شده است. این دستور به React میگوید، زمانی که کاربر روی دکمه کلیک کرد، تابع شما را فراخوانی کند.
در مثال دوم، علامت ()
در انتهای دستور handleClick()
، تابع را بلافاصله در زمان رندر اجرا میکند، بدون اینکه کلیکی صورت گرفته باشد. این به این دلیل است که جاوااسکریپت درون JSX مستقیماً اجرا میشود.
وقتی که شما کد را به صورت درونخطی مینویسید، همان مشکل به شکلی متفاوت بروز میکند:
ارسال یک تابع (صحیح) | فراخوانی یک تابع (نادرست) |
---|---|
<button onClick={() => alert('...')}> | <button onClick={alert('...')}> |
ارسال کد درونخطی به این صورت فعال نمیشود—این کد هر بار که کامپوننت رندر میشود، اجرا میشود:
// پیام با هر بار رندر شدن دکمه به کاربر نمایش داده می شود
<button onClick={alert('You clicked me!')}>
اگر میخواهید مدیر رویداد خود را به صورت درونخطی تعریف کنید، آن را در یک تابع ناشناس به شکل زیر قرار دهید:
<button onClick={() => alert('You clicked me!')}>
به جای این که کد درون تابع پیکانی را با هر رندر اجرا کنید، این روش تابعی برای فراخوانی در زمان کلیک کاربر ایجاد میکند.
در هر دو مورد، چیزی که ارسال می کنید یک تابع است:
<button onClick={handleClick}>
تابعhandleClick
را ارسال میکند.<button onClick={() => alert('...')}>
تابع() => alert('...')
را ارسال میکند.
بیشتر درباره توابع پیکانی بخوانید.
خواندن پروپها در مدیریت رویداد
بدلیل اینکه توابع مدیریت رویداد در داخل کامپوننت تعریف میشوند، به ورودی های کامپوننت دسترسی دارند. در اینجا دکمه ای را مشاهده می کنید که وقتی کلیک شود، یک هشدار با مقدار ورودی message
به کاربر نمایش داده می شود:
مشاهده کد و نتیجه اجرای آن در CodeSandbox
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>
);
}
این کار به دو دکمه اجازه میدهد که پیامهای مختلفی را نمایش دهند. سعی کنید پیامهای ارسال شده به آنها را تغییر دهید.
ارسال مدیریت رویداد به عنوان پروپ
گاهی ممکن است بخواهید که کامپوننت والد، مدیر رویداد کامپوننت فرزند را مشخص کند. دکمهها را در نظر بگیرید: بسته به اینکه شما از کامپوننت Button
در کجا استفاده میکنید، ممکن است بخواهید تابع متفاوتی را اجرا کنید—شاید یکی فیلم را پخش کند و دیگری یک تصویر را بارگذاری کند.
برای این کار، یک ورودی بعنوان مدیر رویداد در کامپوننت فرزند به شکل زیر تعریف کنید تا والد بتواند تابع مدیر رویداد را برای فرزند ارسال کند:
مشاهده کد و نتیجه اجرای آن در CodeSandbox
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
را رندر میکند:
PlayButton
تابعhandlePlayClick
را به عنوان پروپonClick
بهButton
درون خود ارسال میکند.UploadButton
تابع() => alert('Uploading!')
را به عنوان پروپonClick
بهButton
درون خود ارسال میکند.
در نهایت، کامپوننت Button
شما یک پروپ به نام onClick
میپذیرد. این پروپ را مستقیماً به دکمه <button>
داخلی مرورگر با onClick={onClick}
ارسال میکند. این به React میگوید که تابع ارسال شده را در هنگام کلیک فراخوانی کند.
اگر شما از یک سیستم طراحی استفاده میکنید، معمول است که کامپوننتهایی مانند دکمهها شامل استایل باشند اما رفتار را مشخص نکنند. در عوض، کامپوننتهایی مانند PlayButton
و UploadButton
مدیریت رویداد را به پایین منتقل میکنند.
نامگذاری پروپهای مدیر رویداد
کامپوننتهای داخلی مانند <button>
و <div>
تنها از نامهای رویداد مرورگر مانند onClick
پشتیبانی میکنند. با این حال، هنگامی که شما کامپوننتهای خود را میسازید، میتوانید پروپهای مدیر رویداد خود را هر طور که میخواهید نامگذاری کنید.
به طور کلی، ورودی های مدیر رویداد باید با on
شروع شوند و پس از آن یک حرف بزرگ قرار گیرد.
به عنوان مثال، پروپ onClick
در کامپوننت Button
میتوانسته به نام onSmash
نامگذاری شود:
مشاهده کد و نتیجه اجرا آن در CodeSandbox
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>
با حروف کوچک) هنوز به یک پروپ به نام onClick
نیاز دارد، اما نام پروپی که کامپوننت سفارشی Button
شما دریافت میکند، به خودتان بستگی دارد!
هنگامی که کامپوننت شما چندین رویداد منتشر می کند، میتوانید ورودیهای مدیریت رویداد را برای هر کدام به طور خاص نامگذاری کنید. به عنوان مثال، کامپوننت Toolbar
ورودی مدیریت رویداد onPlayMovie
و onUploadImage
را دریافت میکند:
مشاهده کد و نتیجه اجرا آن در CodeSandbox
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
دکمههای خود تنظیم میکند، اما ممکن است بعداً آنها را در زمان زدن کلیدهای صفحه کلید (میانبرها) فراخوانی کند. نامگذاری پروپها بر اساس تعاملات خاص برنامه مانند onPlayMovie
به شما انعطافپذیری بیشتری میدهد تا نحوه استفاده از آنها را بعداً تغییر دهید.
مطمئن شوید که از تگهای HTML مناسب برای مدیریت رویداد خود استفاده کنید. به عنوان مثال، برای مدیریت کلیکها، از <button onClick={handleClick}>
به جای <div onClick={handleClick}>
استفاده کنید. استفاده از المان <button>
مرورگر امکاناتی مانند جابجایی با کیبورد را برای کاربر ممکن میکند. اگر شما از استایل پیشفرض دکمه مرورگر خوشتان نمیآید و میخواهید آن را شبیهتر به یک لینک یا یک عنصر UI دیگر کنید، میتوانید با CSS انجام دهید. در مورد تگهای HTML بیشتر بدانید.
انتشار رویداد
تابع مدیریت رویداد همچنین می تواند رویدادها را از فرزندان خود، دریافت کنند. در این حالت به اصطلاح یک رویداد "حباب" میشود یا "انتشار" پیدا میکند: این رویداد از جایی که اتفاق افتاده شروع میشود و سپس در درخت DOM به سمت بالا میرود.
این <div>
شامل دو دکمه است. هم <div>
و هم دکمه ها دارای رویداد onClick
خود هستند. به نظر شما کدام تابع مدیریت رویداد به هنگام کلیک بر روی دکمه فعال میشوند؟
مشاهده کد و نتیجه اجرا آن در CodeSandbox
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>
اجرا میشود.
تمام رویدادها در React انتشار پیدا میکنند به جز onScroll
، که تنها در تگ JSX که به آن اتصال داده شده کار میکند.
متوقف کردن انتشار
تابع مدیریت رویداد یک شی رویداد به عنوان آرگومان خود دریافت میکند. به طور معمول، این شی e
نامیده میشود که به معنای "رویداد" است. شما میتوانید از این شی برای خواندن اطلاعات رویداد استفاده کنید.
این شی همچنین به شما اجازه میدهد که انتشار را متوقف کنید. اگر میخواهید از رسیدن یک رویداد به کامپوننتهای والدش جلوگیری کنید، باید e.stopPropagation()
را مانند کامپوننت Button
در کد زیر فراخوانی کنید:
مشاهده کد و نتیجه اجرا آن در CodeSandbox
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>
);
}
وقتی بر روی یک دکمه کلیک میکنید:
- React تابع
onClick
ارسال شده به<button>
را فراخوانی میکند. - آن تابع، که در
Button
تعریف شده، کارهای زیر را انجام میدهد:e.stopPropagation()
را فراخوانی میکند تا از حباب زدن رویداد به بالا جلوگیری کند.- تابع
onClick
را فراخوانی میکند، از کامپوننتToolbar
بعنوان ورودی ارسال شده است.
- آن تابع، که در کامپوننت
Toolbar
تعریف شده، هشدار مخصوص دکمه را نمایش میدهد. - از آنجا که انتشار متوقف شده است، مدیر
onClick
والد<div>
دیگر اجرا نمیشود.
نتیجه اجرای دستور e.stopPropagation()
، این است که اکنون با کلیک بر روی دکمهها تنها یک هشدار (از <button>
) را نشان داده می شود و نه دو تا (از <button>
و نوار ابزار والد <div>
). کلیک بر روی دکمه با کلیک بر روی نوار ابزار اطراف برابر نیست، بنابراین متوقف کردن انتشار برای این کامپوننت منطقی است.
دریافت رویدادهای فاز Capture
در موارد نادر، شاید بخواهید تمام رویدادهای فرزندان را بگیرید، حتی اگر آنها انتشار را متوقف شده باشند. به عنوان مثال، شاید بخواهید هر کلیک را صرفنظر از منطق انتشار برای تجزیه و تحلیل ثبت کنید. برای این کار می توانید با افزودن Capture
در انتهای نام رویداد به صورت زیر استفاده کنید:
<div onClickCapture={() => { /* this runs first */ }}>
<button onClick={e => e.stopPropagation()} />
<button onClick={e => e.stopPropagation()} />
</div>
هر رویداد در سه فاز منتشر میشود:
- در درخت دام از ریشه به سمت المان حرکت می کند و همه توابع
onClickCapture
ست شده را فراخوانی میکند. - مدیر
onClick
عنصر کلیک شده را اجرا میکند. - از المان هدف به سمت بالا حرکت میکند و همه توابع
onClick
ست شده را فراخوانی میکند.
رویدادهای فاز Capture برای کدهایی مانند روترها یا تجزیه و تحلیل مفیدند، اما کم پیش می آید که از آنها در کد برنامه استفاده کنید.
ارسال تابع مدیریت رویداد بعنوان جایگزینی برای انتشار
توجه کنید که چگونه تابع مدیر کلیک زیر، یک خط کد را اجرا میکند و سپس تابع onClick
ارسال شده توسط والد را فراخوانی میکند:
function Button({ onClick, children }) {
return (
<button onClick={e => {
e.stopPropagation();
onClick();
}}>
{children}
</button>
);
}
شما همچنین میتوانید کدهای بیشتری به این تابع قبل از فراخوانی تابع رویداد پاس داده شده بوسیله onClick
از والد را اجرا کنید. این الگو جایگزینی برای انتشار رویداد فراهم میکند. این به کامپوننت فرزند اجازه میدهد که رویداد را مدیریت کرده و در عین حال به کامپوننت والد امکان میدهد واکنشی اضافه بر فرزند انجام دهد. بر خلاف انتشار، این روش اتوماتیک نیست. اما مزیت این الگو این است که میتوانید زنجیرهای از کد را که به عنوان نتیجه یک رویداد اجرا شده را به وضوح پیگیری کنید.
اگر کد شما وابسته انتشار است و پیگیری دنبل کردن اینکه کدام تابع مدیریت رویداد کجا و چرا اجرا می شوند، مشکل است، سعی کنید از این رویکرد به جای آن استفاده کنید.
جلوگیری از رفتار پیشفرض
برخی از رویدادها، رفتار پیشفرض مرورگر را به همراه دارند. به عنوان مثال، رویداد ارسال <form>
، زمانی که دکمهای در داخل آن کلیک میشود، به طور پیشفرض کل صفحه را بارگذاری میکند:
مشاهده کد و نتیجه اجرا آن در CodeSandbox
export default function Signup() {
return (
<form onSubmit={() => alert('Submitting!')}>
<input />
<button>Send</button>
</form>
);
}
شما میتوانید با فراخوانی e.preventDefault()
بر روی شی رویداد، از این اتفاق جلوگیری کنید:
مشاهده کد و نتیجه اجرا آن در CodeSandbox
export default function Signup() {
return (
<form onSubmit={e => {
e.preventDefault();
alert('Submitting!');
}}>
<input />
<button>Send</button>
</form>
);
}
e.stopPropagation()
و e.preventDefault()
را با هم اشتباه نگیرید. هر دو مفید هستند، اما ارتباطی با هم ندارند:
e.stopPropagation()
مدیریت رویداد متصل به تگهای والد را از اجرا شدن متوقف میکند.e.preventDefault()
از رفتار پیشفرض مرورگر جلوگیری میکند.
آیا مدیریت رویداد میتوانند تاثیرات جانبی داشته باشند؟
قطعاً! مدیریت رویداد بهترین مکان برای تاثیرات جانبی هستند.
برخلاف توابع رندر، مدیریت رویداد نیازی به خالص بودن ندارند، بنابراین مکان خوبی برای تغییر مقادیر است—به عنوان مثال، تغییر مقدار ورودی در پاسخ به تایپ، یا تغییر یک لیست در پاسخ به فشار دکمه. با این حال، برای تغییر اطلاعات، ابتدا نیاز به روشی برای حفظ مقادیر دارید. در React، این کار با استفاده از وضعیت، حافظه یک کامپوننت انجام میشود. شما درباره این موضوع در مطالب بعدی خواهید آموخت.
جمع بندی
- شما میتوانید با ارسال یک تابع به عنوان پروپ به عنصری مانند
<button>
رویدادها را مدیریت کنید. - مدیریت رویداد باید ارسال شوند، نه فراخوانی!
onClick={handleClick}
، نهonClick={handleClick()}
. - میتوانید یک تابع مدیر رویداد را به صورت جداگانه یا درونخطی تعریف کنید.
- توابع مدیریت رویداد در داخل یک کامپوننت تعریف میشوند و بنابراین میتوانند به پروپها دسترسی داشته باشند.
- میتوانید یک مدیر رویداد را در والد تعریف کرده و به عنوان یک پروپ به فرزند ارسال کنید.
- میتوانید پروپهای مدیر رویداد خود را با نامهای مخصوص برنامه تعریف کنید.
- رویدادها به سمت بالا منتشر میشوند. کد
e.stopPropagation()
را فراخوانی کنید تا از این مسئله جلوگیری کنید. - رویدادها ممکن است رفتار پیشفرض مرورگر را بهمراه داشته باشند.
e.preventDefault()
را برای جلوگیری از آن فراخوانی کنید. - فراخوانی صریح ورودی تابع مدیریت رویداد در تابع مدیریت رویداد فرزند جایگزینی خوب برای انتشار رویداد است.
چالش ها
1. اصلاح یک مدیر رویداد
کلیک بر دکمه قرار است پسزمینه صفحه را بین سفید و سیاه تغییر دهد. با این حال، وقتی بر روی آن کلیک میکنید، هیچ اتفاقی نمیافتد. این مشکل را برطرف کنید. (نگران منطق داخل handleClick
نباشید—آن قسمت به درستی عمل می کند.)
مشاهده کد اصلاح آن در CodeSandbox
2. متصل کردن رویدادها
کامپوننت ColorSwitch
دکمه ای را رندر میکند که قرار است رنگ صفحه را تغییر دهد. آن را به پروپ مدیر رویداد onChangeColor
که از والد دریافت میکند متصل کنید تا کلیک بر روی دکمه رنگ را تغییر دهد.
بعد از اینکه این کار را انجام دادید، توجه کنید که کلیک بر روی دکمه، شمارنده کلیک صفحه را نیز افزایش میدهد. در حالی که همکار شما که کامپوننت والد را نوشته است تأکید میکند که onChangeColor
نبایدشمارنده را افزایش دهد. چه اتفاقی ممکن است افتاده باشد؟ آن را اصلاح کنید تا کلیک بر روی دکمه تنها رنگ را تغییر دهد و شمارشگر را افزایش ندهد.
مشاهده کد اصلاح آن در CodeSandbox
راه حل چالش ها
چالش اول
مشکل این است که <button onClick={handleClick()}>
تابع handleClick
را در حین رندر به جای اینکه آن را ارسال کند، فراخوانی میکند. حذف ()
از انتهای نام تابع و قرار دادن <button onClick={handleClick}>
مسئله را حل میکند:
مشاهده کد و نتیجه اجرا آن در CodeSandbox
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>
);
}
به طور جایگزین، میتوانید فراخوانی را در یک تابع پیکانی قرار دهید، مانند <button onClick={() => handleClick()}>
:
مشاهده کد و نتیجه اجرا آن در CodeSandbox
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>
);
}
چالش دوم
اول، شما باید مدیر رویداد را مانند <button onClick={onChangeColor}>
اضافه کنید.
با این حال، این کار مشکلی برای افزایش شمارنده ایجاد میکند. اگر onChangeColor
این کار را نمیکند، همانطور که همکار شما اصرار دارد، پس مشکل این است که این رویداد به سمت بالا منتشر میشود و برخی از توابع مدیرت رویداد در المان های بالاتر این کار را انجام میدهند. برای حل این مشکل، شما باید انتشار را متوقف کنید. اما فراموش نکنید که باید onChangeColor
را فراخوانی کنید.
مشاهده کد و نتیجه اجرا آن در CodeSandbox
export default function ColorSwitch({
onChangeColor
}) {
return (
<button onClick={e => {
e.stopPropagation();
onChangeColor();
}}>
Change color
</button>
);
}