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

ری‌اکت به شما اجازه می‌دهد که به ‌JSX خود ‌event handlerها را اضافه. Event handlerها توابع خود شما هستند که در زمان پاسخگویی به تعاملاتی مانند کلیک کردن‌‌، hover کردن، تمرکز کردن روی input های فرم و دیگر چیزها فعال می‌شوند.

You will learn

  • روش های مختلف برای نوشتن یک event handler
  • نحوه انتقال منطق مدیریت رویداد از کامپوننت پدر
  • نحوه انتشار رویداد‌ها و چگونگی توقف آن‌ها

اضافه کردن event handlerها

برای افزودن یک event handler، ابتدا یک تابع تعریف می‌کنید و سپس آن را به عنوان props به تگ JSX مناسب پاس می‌دهید. به عنوان مثال، در اینجا دکمه ای وجود دارد که هنوز هیچ کاری انجام نمی دهد:

export default function Button() {
  return (
    <button>
      I don't do anything
    </button>
  );
}

با دنبال کردن این سه مرحله می توانید کاری کنید که وقتی کاربر کلیک می کند پیامی نشان دهد:

  1. تابعی به نام handleClick در داخل کامپوننت Button خود تعریف کنید.
  2. منطق را در داخل آن تابع پیاده سازی کنید (از alert برای نمایش پیام استفاده کنید).
  3. 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) برای عملکردهای کوتاه مناسب هستند.

Pitfall

توابع پاس داده شده به event handlerها باید پاس داده شوند، نه اینکه فراخوانی شوند. مثلا:

passing a function (correct)calling a function (incorrect)
<button onClick={handleClick}><button onClick={handleClick()}>

تفاوت ظریف است. در مثال اول، تابع handleClick به عنوان یک event handler از نوع onClick پاس داده می‌شود. این به ری‌اکت می‌گوید که آن را به خاطر بسپارد و تنها زمانی که کاربر روی دکمه کلیک کرد، تابع شما را فراخوانی کند.

در مثال دوم، () در انتهای ()handleClick تابع را بلافاصله در زمان رندر کردن، بدون هیچ کلیکی فعال می کند. این به این دلیل است که جاوا اسکریپت در JSX { و } بلافاصله اجرا می شود.

هنگامی که کد را به صورت درخط (inline) می نویسید، همان دام به شکل دیگری خود را نشان می دهد:

passing a function (correct)calling a function (incorrect)
<button onClick={() => alert('...')}><button onClick={alert('...')}>

پاس دادن کدهای درخط (inline) مانند این، هنگام کلیک کردن فراخوانی نمی‌شود—هر بار که کامپوننت رندر می‌شود فراخوانی می‌شود:

// This alert fires when the component renders, not when clicked!
<button onClick={alert('You clicked me!')}>

اگر می خواهید event handler خود را به صورت درخط (inline) تعریف کنید، آن را در یک تابع ناشناس مانند زیر قرار دهید:

<button onClick={() => alert('You clicked me!')}>

این کار به جای اجرای کد داخل با هر رندر، تابعی ایجاد می کند که بعدا فراخوانی می شود.

در هر دو مورد، چیزی که می خواهید پاس دهید یک تابع است:

  • <button onClick={handleClick}> تابع handleClick را پاس می‌دهد .
  • <button onClick={() => alert('...')}> تابع () => alert('...') را پاس می‌دهد .

درباره arrow functions بیشتر بخوانید.

خواندن 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‍‍‍ را به‌عنوان onClick prop به Button داخل پاس می‌دهد.
  • UploadButton هم alert('Uploading!') <= () را به عنوان onClick prop به 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 به شما این امکان را می‌دهد که نحوه استفاده از آنها را بتوانید بعداً تغییر دهید.

Note

Make sure that you use the appropriate HTML tags for your event handlers. For example, to handle clicks, use <button onClick={handleClick}> instead of <div onClick={handleClick}>. Using a real browser <button> enables built-in browser behaviors like keyboard navigation. If you don’t like the default browser styling of a button and want to make it look more like a link or a different UI element, you can achieve it with CSS. Learn more about writing accessible markup.

انتشار رویداد

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> پدر اجرا خواهد شد.

Pitfall

همه رویدادها در ری‌اکت منتشر می شوند به جز onScroll که فقط روی تگ JSX که به آن متصل شده کار می کند.

توقف انتشار

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>
  );
}

وقتی روی دکمه‌ای کلیک می کنید:

  1. ری‌اکت کنترل‌کننده onClickی که به <button> پاس داده شده است را که فراخوانی می‌کند.
  2. آن کنترل کننده که در Button تعریف شده است، کارهای زیر را انجام می دهد:
    • ()e.stopPropagation را فراخوانی می کند و از بالا رفتن رویداد جلوگیری می‌کند.
    • تابع onClick را فراخوانی می کند، که propی است که از کامپوننت Toolbar پاس داده شده‌است.
  3. این تابع، که در کامپوننت Toolbar تعریف شده است، هشدار خود دکمه را نمایش می دهد.
  4. از آنجایی که انتشار متوقف شده، کنترل کننده onClick المنت <div> پدر اجرا نمی شود.

در نتیجه‌ی ()e.stopPropagation، کلیک کردن روی دکمه‌ها فقط یک هشدار (از <button>) به جای دو مورد (از<button> و <div> نوارابزار پدر ) را نشان می‌دهد. کلیک کردن روی یک دکمه با کلیک کردن روی نوار ابزار اطراف یکسان نیست، بنابراین توقف انتشار برای این رابط کاربری منطقی است.

Deep Dive

ثبت رویدادهای فاز

در موارد نادر، ممکن است لازم باشد همه رویدادهای المنت‌های فرزند را دریافت کنید، حتی اگر انتشار آنها متوقف شود. برای مثال، شاید بخواهید بدون در نظر گرفتن منطق انتشار، هر کلیک را برای تجزیه و تحلیل ثبت کنید. می توانید این کار را با افزودن Capture در انتهای نام رویداد انجام دهید:

<div onClickCapture={() => { /* this runs first */ }}>
<button onClick={e => e.stopPropagation()} />
<button onClick={e => e.stopPropagation()} />
</div>

هر رویداد در سه فاز منتشر می شود:

  1. به سمت پایین حرکت می‌کند و همه کنترل‌کننده‌های onClickCapture را فراخوانی می‌کند.
  2. کنترل کننده onClick المنت کلیک شده را اجرا می کند.
  3. به سمت بالا حرکت می کند و همه کنترل کننده های 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>
  );
}