React дазваляе дадаваць апрацоўшчыкі падзей у JSX. Апрацоўшчыкі падзей — гэта вашыя ўласныя функцыі, якія будуць запускацца ў адказ на ўзаемадзеянне карыстальніка, напрыклад, націсканне кнопак, навядзенне курсора на элементы, атрыманне фокусу на ўводах формы і гэтак далей.

You will learn

  • Розныя спосабы напісання апрацоўшчыкаў падзей
  • Як перадаць логіку апрацоўкі падзей ад бацькоўскіх кампанентаў
  • Як падзеі распаўсюджваюцца і як іх спыніць

Дадаванне апрацоўшчыкаў падзей

Каб дадаць апрацоўшчык падзеі, спачатку трэба вызначыць функцыю, а потым перадаць яе ў якасці пропса ў адпаведны JSX тэг. Напрыклад, вось кнопка, якая пакуль нічога не робіць:

export default function Button() {
  return (
    <button>
      Я нічога не раблю
    </button>
  );
}

Для таго, каб пры націсканні на кнопку паказвалася паведамленне, выканайце наступныя тры крокі:

  1. Аб’явіце функцыю handleClick унутры кампанента Button.
  2. Рэалізуйце логіку ўнутры гэтай функцыі (выкарыстоўвайце alert, каб паказаць паведамленне).
  3. Дадайце onClick={handleClick} у JSX тэг <button>.
export default function Button() {
  function handleClick() {
    alert('Вы націснулі на мяне!');
  }

  return (
    <button onClick={handleClick}>
      Націсніце на мяне
    </button>
  );
}

Вы аб’явілі функцыю handleClick і затым перадалі яе ў якасці пропса ў <button>. Функцыя handleClick — гэта апрацоўшчык падзей. Функцыі апрацоўшчыкі падзей:

  • Звычайна аб’яўляюцца ўнутры вашых кампанентаў.
  • Маюць назвы, якія пачынаецца са слова handle, за якім ідзе назва падзеі.

Назвы апрацоўшчыкаў падзей звычайна пачынаюцца са слова handle, за якім ідзе назва падзеі. Вы часта будзеце сустракаць onClick={handleClick}, onMouseEnter={handleMouseEnter} і т.п.

У якасці альтэрнатывы, вы можаце аб’явіць апрацоўшчык падзей унутры JSX:

<button onClick={function handleClick() {
alert('Вы націснулі на мяне!');
}}>

Або, больш сцісла, выкарыстоўваючы стрэлачную функцыю:

<button onClick={() => {
alert('Вы націснулі на мяне!');
}}>

Абодва гэтыя варыянты эквівалентныя. Апрацоўшчыкі падзей унутры JSX зручныя для кароткіх функцый.

Pitfall

Функцыі, якія перадаюцца ў апрацоўшчыкі падзей, павінны перадавацца, а не выклікацца. Напрыклад:

перадача функцыі (правільна)выклік функцыі (няправільна)
<button onClick={handleClick}><button onClick={handleClick()}>

Розніца тут невялікая. У першым прыкладзе функцыя handleClick перадаецца як апрацоўшчык падзеі onClick. Гэта кажа React запомніць яе і выклікаць толькі тады, калі карыстальнік націскае кнопку.

У другім прыкладзе дужкі () у канцы handleClick() выклікаюць функцыю адразу падчас рэндэрынгу, без націскання кнопкі. Так адбываецца таму, што JavaScript код унутры { і } JSX выконваецца адразу.

Калі вы пішаце ўбудаваны (inline) код, тая ж пастка праяўляецца па-іншаму:

перадача функцыі (правільна)выклік функцыі (няправільна)
<button onClick={() => alert('...')}><button onClick={alert('...')}>

Калі мы перададзім убудаваны код наступным чынам, то ён не будзе выкананы пры націсканні кнопкі — ён будзе выконвацца пры кожным рэндэрынгу кампанента:

// Гэта паведамленне з'яўляецца пры рэндэрынгу кампанента, а не пры націсканні кнопкі!
<button onClick={alert('Вы націснулі на мяне!')}>

Калі вы хочаце аб’явіць апрацоўшчык падзей убудаваным чынам, абгарніце яго ў ананімную функцыю, як паказана ніжэй:

<button onClick={() => alert('Вы націснулі на мяне!')}>

Такім чынам, замест выканання кода пры кожным рэндэрынгу, ствараецца функцыя, якая будзе выклікана пазней.

У абодвух выпадках вам трэба перадаваць функцыю:

  • <button onClick={handleClick}> перадае функцыю handleClick.
  • <button onClick={() => alert('...')}> перадае стрэлачную функцыю () => alert('...').

Падрабязней пра стрэлачныя функцыі.

Чытанне пропсаў у апрацоўшчыках падзей

Паколькі апрацоўшчыкі падзей аб’яўлены ўнутры кампанента, яны маюць доступ да пропсаў кампанента. Вось кнопка, пры націсканні на якую паказваецца паведамленне са значэннем пропса message:

function AlertButton({ message, children }) {
  return (
    <button onClick={() => alert(message)}>
      {children}
    </button>
  );
}

export default function Toolbar() {
  return (
    <div>
      <AlertButton message="Прайграваецца!">
        Прайграць фільм
      </AlertButton>
      <AlertButton message="Запампоўваецца!">
        Запампаваць відарыс
      </AlertButton>
    </div>
  );
}

Дзякуючы гэтаму дзве кнопкі паказваюць розныя паведамленні. Паспрабуйце змяніць значэнні, якія ім перадаюцца.

Перадача апрацоўшчыкаў падзей у якасці пропсаў

Часта вам можа спатрэбіцца, каб бацькоўскі кампанент вызначаў апрацоўшчыка падзей для даччынага кампанента. Разгледзім кнопкі: у залежнасці ад таго, дзе вы выкарыстоўваеце кампанент Button, вам можа спатрэбіцца выконваць розныя функцыі — магчыма, адна кнопка павінна прайграваць фільм, а другая загружаць відарыс.

Каб зрабіць гэта, перадайце кампаненту пропс, які ён атрымлівае ад свайго бацькі, у якасці апрацоўшчыка падзей, наступным чынам:

function Button({ onClick, children }) {
  return (
    <button onClick={onClick}>
      {children}
    </button>
  );
}

function PlayButton({ movieName }) {
  function handlePlayClick() {
    alert(`Прайграваецца ${movieName}!`);
  }

  return (
    <Button onClick={handlePlayClick}>
      Прайграць "{movieName}"
    </Button>
  );
}

function UploadButton() {
  return (
    <Button onClick={() => alert('Запампоўваецца!')}>
      Запампаваць відарыс
    </Button>
  );
}

export default function Toolbar() {
  return (
    <div>
      <PlayButton movieName="Ведзьміна дастаўка" />
      <UploadButton />
    </div>
  );
}

Тут кампанент Toolbar рэндэрыць кампаненты PlayButton і UploadButton:

  • PlayButton перадае handlePlayClick у якасці пропса onClick у кампанент Button.
  • UploadButton перадае () => alert('Запампоўваецца!') у якасці пропса onClick у кампанент Button.

Нарэшце, ваш кампанент Button прымае пропс пад назвай onClick. Ён перадае гэты пропс непасрэдна ва ўбудаваны браўзерны кампанент <button> з дапамогай onClick={onClick}. Гэта загадвае React выклікаць перададзеную функцыю пры націсканні.

Калі вы выкарыстоўваеце дызайн-сістэму, то звычайна такія кампаненты, як кнопкі, змяшчаюць стылі, але не вызначаюць паводзіны. Замест гэтага кампаненты, як PlayButton і UploadButton, будуць перадаваць апрацоўшчыкі падзей уніз па іерархіі.

Называнне пропсаў апрацоўшчыка падзей

Убудаваныя кампаненты, такія як <button> і <div>, падтрымліваюць толькі назвы падзей браўзера, напрыклад onClick. Аднак, пры стварэнні ўласных кампанентаў, вы можаце называць іх апрацоўшчыкі падзей так, як вам падабаецца.

Назва пропса апрацоўшчыка падзей павінна пачынацца з on, за якім ідзе вялікая літара.

Напрыклад, пропс для падзеі onClick у кампаненце Button мог бы называцца onSmash:

function Button({ onSmash, children }) {
  return (
    <button onClick={onSmash}>
      {children}
    </button>
  );
}

export default function App() {
  return (
    <div>
      <Button onSmash={() => alert('Прайграваецца!')}>
        Прайграць фільм
      </Button>
      <Button onSmash={() => alert('Запампоўваецца!')}>
        Запампаваць відарыс
      </Button>
    </div>
  );
}

У гэтым прыкладзе <button onClick={onSmash}> паказвае, што браўзерны кампанент <button> (з маленькай літары) па-ранейшаму патрэбуе пропс з назвай onClick, але назву пропса, які атрымлівае ваш карыстальніцкі кампанентам Button, выбіраеце вы!

Калі ваш кампанент падтрымлівае некалькі ўзаемадзеянняў, вы можаце назваць пропсы апрацоўшчыкаў падзей у адпаведнасці з канкрэтнымі канцэпцыямі вашай праграмы. Напрыклад, кампанент Toolbar атрымлівае апрацоўшчыкі падзей onPlayMovie і onUploadImage:

export default function App() {
  return (
    <Toolbar
      onPlayMovie={() => alert('Прайграваецца!')}
      onUploadImage={() => alert('Запампоўваецца!')}
    />
  );
}

function Toolbar({ onPlayMovie, onUploadImage }) {
  return (
    <div>
      <Button onClick={onPlayMovie}>
        Прайграць фільм
      </Button>
      <Button onClick={onUploadImage}>
        Запампаваць відарыс
      </Button>
    </div>
  );
}

function Button({ onClick, children }) {
  return (
    <button onClick={onClick}>
      {children}
    </button>
  );
}

Звярніце ўвагу, што кампанент App не павінен ведаць, што кампанент Toolbar будзе рабіць з onPlayMovie або onUploadImage. Гэта дэталі рэалізацыі Toolbar. У гэтым прыкладзе Toolbar перадае іх у якасці апрацоўшчыкаў падзеі onClick сваім кампанентам Button, але пазней ён таксама можа выклікаць іх па спалучэнні клавіш. Называнне пропсаў у адпаведнасці са спецыфікай вашай праграмы, напрыклад onPlayMovie, дае вам магчымасць змяніць спосаб іх выкарыстання ў будучыні.

Note

Упэўніцеся, што вы выкарыстоўваеце адпаведныя HTML тэгі для апрацоўшчыкаў падзей. Напрыклад, для апрацоўкі націскання выкарыстоўвайце <button onClick={handleClick}> замест <div onClick={handleClick}>. Выкарыстанне сапраўднага браўзернага кампанента <button> дазваляе выкарыстоўваць убудаваныя функцыі браўзера, напрыклад навігацыю з клавіятуры. Калі вам не падабаецца прадвызначаны стыль кнопкі ў браўзеры і вы хочаце зрабіць яе больш падобнай на спасылку або іншы элемент інтэрфейсу, вы можаце дасягнуць гэтага з дапамогай CSS. Даведайцеся больш пра напісанне даступнай разметкі.

Распаўсюджванне падзей

Апрацоўшчыкі падзей таксама будуць перахопліваць падзеі ад любых даччыных кампанентаў. Кажуць, што падзея «ўсплывае» або «распаўсюджваецца» ўверх па дрэве: яна пачынаецца там, дзе адбылася падзея, а потым ідзе ўверх па дрэве.

Гэты <div> змяшчае дзве кнопкі. У <div> і ў кожнай кнопкі ёсць уласныя апрацоўшчыкі падзеі onClick. Як думаеце, якія апрацоўшчыкі будуць выкліканы, калі вы націснеце кнопку?

export default function Toolbar() {
  return (
    <div className="Toolbar" onClick={() => {
      alert('Вы націснулі на панэль інструментаў!');
    }}>
      <button onClick={() => alert('Прайграваецца!')}>
        Прайграць фільм
      </button>
      <button onClick={() => alert('Запампоўваецца!')}>
        Запампаваць відарыс
      </button>
    </div>
  );
}

Калі вы націснеце на любую кнопку, то спачатку будзе выкліканы яе апрацоўшчык onClick, а пасля апрацоўшчык onClick бацькоўскага элемента <div>. Такім чынам, з’явяцца два паведамленні. Калі вы націснеце на саму панэль інструментаў, то будзе выкліканы толькі апрацоўшчык onClick бацькоўскага элемента <div>.

Pitfall

У React усе падзеі распаўсюджваюцца, за выключэннем падзеі onScroll, якая працуе толькі ў JSX тэгу, да якога яна прывязана.

Спыненне распаўсюджвання падзей

Апрацоўшчыкі падзей атрымліваюць аб’ект падзеі ў якасці адзінага аргумента. Звычайна гэты аб’ект называецца e, што расшыфроўваецца як «event» (падзея). З дапамогай гэтага аб’екта вы можаце атрымаць інфармацыю аб падзеі.

Аб’ект падзеі таксама дазваляе спыніць распаўсюджванне падзеі. Калі вы не хочаце, каб падзея дасягнула бацькоўскіх кампанентаў, вам трэба выклікаць 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('Вы націснулі на панэль інструментаў!');
    }}>
      <Button onClick={() => alert('Прайграваецца!')}>
        Прайграць фільм
      </Button>
      <Button onClick={() => alert('Запампоўваецца!')}>
        Запампаваць відарыс
      </Button>
    </div>
  );
}

Пры націсканні на кнопку:

  1. React выклікае апрацоўшчык onClick, перададзены ў <button>.
  2. Гэты апрацоўшчык, аб’яўлены ў Button, выконвае наступнае:
    • Выклікае e.stopPropagation(), што прадухіляе далейшае ўсплыванне падзеі.
    • Выклікае функцыю onClick, якая з’яўляецца пропсам, які перадалі з кампанента Toolbar.
  3. Гэтая функцыя, аб’яўленая ў кампаненце Toolbar, паказвае ўсплывальнае акно alert для кнопкі.
  4. Паколькі распаўсюджванне падзеі было спынена, апрацоўшчык onClick бацькоўскага элемента <div> не выклікаецца.

У выніку e.stopPropagation(), пры націсканні на кнопкі цяпер паказваецца толькі адно ўсплывальнае акно (ад <button>), а не два (ад <button> і ад бацькоўскага <div> у кампаненце Toolbar). Націсканне кнопкі — гэта не тое ж самае, што націсканне на панэль інструментаў, таму спыненне распаўсюджвання падзеі мае сэнс для гэтага інтэрфейсу.

Deep Dive

Перахопліванне падзей

У рэдкіх выпадках вам можа спатрэбіцца перахапіць усе падзеі на даччыных элементах, нават калі яны спынілі распаўсюджванне. Напрыклад, магчыма, вы хочаце зарэгістраваць кожнае націсканне мышы ў аналітыцы, незалежна ад логікі распаўсюджвання. Для гэтага можна дадаць Capture у канец назвы падзеі:

<div onClickCapture={() => { /* гэта адбываецца ў першую чаргу */ }}>
<button onClick={e => e.stopPropagation()} />
<button onClick={e => e.stopPropagation()} />
</div>

Кожная падзея распаўсюджваецца ў тры фазы:

  1. Яна спускаецца ўніз, выклікаючы ўсе апрацоўшчыкі onClickCapture.
  2. Яна выклікае апрацоўшчык onClick для націснутага элемента.
  3. Яна падымаецца ўверх, выклікаючы ўсе апрацоўшчыкі onClick.

Перахопліванне падзей карысны пры напісанні маршрутызатараў або аналітыцы, але вы, верагодна, не будзеце выкарыстоўваць іх у сваёй праграме.

Перадача апрацоўшчыкаў як альтэрнатыва распаўсюджванню падзей

Звярніце ўвагу, як гэты апрацоўшчык націскання кнопкі выконвае радок кода, а потым выклікае перададзены бацькам пропс onClick:

function Button({ onClick, children }) {
return (
<button onClick={e => {
e.stopPropagation();
onClick();
}}>
{children}
</button>
);
}

Вы таксама можаце дадаць больш кода ў гэты апрацоўшчык перад тым, як выклікаць бацькоўскі апрацоўшчык падзеі onClick. Гэты шаблон забяспечвае альтэрнатыву распаўсюджванню падзеі. Ён дазваляе даччынаму кампаненту апрацоўваць падзею, а таксама дазваляе бацькоўскаму кампаненту вызначаць дадатковыя паводзіны. У адрозненне ад распаўсюджвання, гэта не адбываецца аўтаматычна. Але перавага гэтага шаблону ў тым, што вы можаце дакладна прасачыць увесь ланцужок кода, які выконваецца ў выніку нейкай падзеі.

Калі вы разлічваеце на распаўсюджванне падзей і вам цяжка сачыць за тым, якія апрацоўшчыкі выконваюцца і чаму, паспрабуйце выкарыстоўваць гэты падыход.

Прадухіленне прадвызначаных паводзін

Некаторыя браўзеры маюць прадвызначаныя паводзіны для некаторых падзей. Напрыклад, падзея адпраўкі формы <form>, якая адбываецца пры націсканні кнопкі ўнутры яе, прадвызначана перазагружае ўсю старонку:

export default function Signup() {
  return (
    <form onSubmit={() => alert('Адпраўка!')}>
      <input />
      <button>Адправіць</button>
    </form>
  );
}

Вы можаце выклікаць e.preventDefault() для аб’екта падзеі, каб прадухіліць гэта:

export default function Signup() {
  return (
    <form onSubmit={e => {
      e.preventDefault();
      alert('Адпраўка!');
    }}>
      <input />
      <button>Адправіць</button>
    </form>
  );
}

Не блытайце функцыі e.stopPropagation() і e.preventDefault(). Яны абедзве карысныя, але не звязаны паміж сабой:

  • e.stopPropagation() спыняе спрацоўванне апрацоўшчыкаў падзей, прывязаных да тэгаў вышэй па іерархіі DOM.
  • e.preventDefault() прадухіляе прадвызначаныя паводзіны браўзера для некаторых падзей.

Ці могуць апрацоўшчыкі падзей мець пабочныя эфекты?

Вядома! Апрацоўшчыкі падзей — гэта лепшае месца для пабочных эфектаў.

У адрозненне ад функцый рэндэрынгу, апрацоўшчыкі падзей не павінны быць чыстымі функцыямі, таму гэта выдатнае месца, каб змяніць што-небудзь — напрыклад, змяніць значэнне ўводу ў адказ на набор тэксту або змяніць спіс у адказ на націсканне кнопкі. Аднак, каб змяніць нейкую інфармацыю, спачатку яе трэба неяк захаваць. У React для гэтага выкарыстоўваецца стан — памяць кампанента. Пра ўсё гэта вы даведаецеся на наступнай старонцы.

Recap

  • Вы можаце апрацоўваць падзеі, перадаючы функцыю ў якасці пропса элементу, напрыклад <button>.
  • Апрацоўшчыкі падзей павінны перадавацца, а не выклікацца! Трэба onClick={handleClick}, а не onClick={handleClick()}.
  • Вы можаце аб’явіць функцыю апрацоўшчыка падзей асобна або ў самім тэгу (убудаваным чынам).
  • Апрацоўшчыкі падзей аб’яўляюцца ўнутры кампанента, таму яны маюць доступ да пропсаў.
  • Вы можаце аб’явіць апрацоўшчык падзей у бацькоўскім кампаненце і перадаць яго ў якасці пропса даччынаму кампаненту.
  • Вы можаце аб’яўляць свае ўласныя пропсы апрацоўшчыка падзей з назвамі, спецыфічнымі для вашай праграмы.
  • Падзеі распаўсюджваюцца ўверх. Каб прадухіліць гэта, выклічце e.stopPropagation().
  • Падзеі могуць мець непажаданыя прадвызначаныя паводзіны браўзера. Каб прадухіліць гэта, выклічце e.preventDefault().
  • Яўны выклік апрацоўшчыка падзей з даччынага апрацоўшчыка з’яўляецца добрай альтэрнатывай распаўсюджванню падзеі.

Challenge 1 of 2:
Выправіце апрацоўшчык падзей

Пры націсканні гэтай кнопкі фон старонкі павінен пераключацца паміж белым і чорным. Аднак, нічога не адбываецца пры націсканні на яе. Выправіце гэту праблему. (Не хвалюйцеся наконт логікі ўнутры 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()}>
      Пераключыць тэму
    </button>
  );
}