Phản hồi các sự kiện
React cho bạn thêm các hàm xử lý sự kiện vào JSX. Hàm xử lý sự kiện là các hàm bạn tự định nghĩa mà sẽ được kích hoạt để phản hồi lại các tương tác như nhấn chuột, hover chuột hay focus các trường input của form, và các tương tác tương tự.
You will learn
- Những cách khác nhau để viết một hàm xử lý sự kiện
- Cách truyền logic xử lý sự kiện từ một component cha
- Cách các sự kiện lan truyền và cách dừng sự lan truyền sự kiện
Thêm các hàm xử lý sự kiện
Để thêm các hàm xử lý sự kiện, bạn sẽ cần khai báo hàm rồi truyền nó như một prop tới thẻ JSX thích hợp. Ví dụ, đây là một nút hiện tại chưa có chức năng gì:
export default function Button() { return ( <button> I don't do anything </button> ); }
Bạn có thể làm nó hiển thị một lời nhắn khi người dùng nhấn vào qua các bước sau:
- Khai báo một hàm
handleClick
bên trong componentButton
của bạn - Thực thi logic bên trong hàm đó (sử dụng
alert
để hiện lời nhắn) - Thêm
onClick={handleClick}
vào thẻ JSX<button>
export default function Button() { function handleClick() { alert('You clicked me!'); } return ( <button onClick={handleClick}> Click me </button> ); }
Bạn đã định nghĩa hàm handleClick
rồi truyền nó như một prop tới <button>
. handleClick
là một hàm xử lý sự kiện. Hàm xử lý sự kiện:
- Thường được định nghĩa bên trong các component của bạn.
- Có tên bắt đầu với
handle
, theo sau đó là tên sự kiện.
Theo quy chuẩn, ta thường đặt tên các hàm xử lý sự kiện là handle
rồi đến tên sự kiện. Bạn sẽ hay thấy onClick={handleClick}
, onMouseEnter={handleMouseEnter}
, …
Cách khác, bạn có thể định nghĩa một hàm xử lý sự kiện theo kiểu inline trong JSX như sau:
<button onClick={function handleClick() {
alert('You clicked me!');
}}>
Hoặc, ngắn gọn hơn, sử dụng hàm mũi tên:
<button onClick={() => {
alert('You clicked me!');
}}>
Tất cả cách viết trên đều như nhau. Các hàm xử lý sự kiện inline sẽ tiện hơn cho các hàm ngắn.
Đọc các prop trong hàm xử lý sự kiện
Vì các hàm xử lý sự kiện được khai báo trong một component, chúng có quyền truy cập vào các prop của component. Đây là một nút mà khi nhấn, hiện ra một alert với prop message
của nó:
function AlertButton({ message, children }) { return ( <button onClick={() => alert(message)}> {children} </button> ); } export default function Toolbar() { return ( <div> <AlertButton message="Đang phát!"> Phát phim </AlertButton> <AlertButton message="Đang tải!"> Tải ảnh lên </AlertButton> </div> ); }
Điều này sẽ cho hai nút hiển thị hai lời nhắn khác nhau. Hãy thử thay đổi lời nhắn (message
) được truyền cho các nút.
Truyền các hàm xử lý sự kiện như prop
Thông thường, bạn sẽ muốn component cha chỉ định hàm xử lý sự kiện của component con. Xem xét các nút: tuỳ thuộc vào nơi bạn đang sử dụng component Button
, bạn có thể muốn thực thi một hàm khác—có thể là một nút phát phim còn một nút khác tải ảnh lên.
Để làm được điều này, truyền một prop mà component nhận từ cha của nó như một hàm xử lý sự kiện:
function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); } function PlayButton({ movieName }) { function handlePlayClick() { alert(`Đang phát ${movieName}!`); } return ( <Button onClick={handlePlayClick}> Phát "{movieName}" </Button> ); } function UploadButton() { return ( <Button onClick={() => alert('Đang tải!')}> Tải ảnh lên </Button> ); } export default function Toolbar() { return ( <div> <PlayButton movieName="Kiki's Delivery Service" /> <UploadButton /> </div> ); }
Tại đây, component Toolbar
render một PlayButton
và một UploadButton
:
PlayButton
truyềnhandlePlayClick
như một proponClick
tớiButton
bên trong.UploadButton
truyền() => alert('Uploading!')
như một proponClick
tớiButton
bên trong.
Cuối cùng, component Button
của bạn nhận một prop được gọi là onClick
. Nó truyền prop đó trực tiếp tới thẻ <button>
có sẵn của trình duyệt với onClick={onClick}
. Điều này bảo React gọi hàm được truyền khi nhấn nút.
Nếu bạn sử dụng một hệ thống thiết kế, thông thường các component như các nút (Button
) chứa styling chứ không chỉ định hành vi. Thay vào đó, các component như PlayButton
và UploadButton
sẽ truyền hàm xử lý sự kiện xuống Button
.
Đặt tên cho các prop hàm xử lý sự kiện
Các component có sẵn như <button>
và <div>
chỉ hỗ trợ các tên sự kiện của trình duyệt như onClick
. Tuy nhiên, khi xây dựng những component của riêng mình, bạn có thể đặt tên cho các prop hàm xử lý sự kiện của các component đó tuỳ ý.
Theo quy chuẩn, các prop hàm xử lý sự kiện nên bắt đầu bằng on
, theo sau đó là chữ cái viết hoa.
Ví dụ, prop onClick
của component Button
có thể được gọi là onSmash
:
function Button({ onSmash, children }) { return ( <button onClick={onSmash}> {children} </button> ); } export default function App() { return ( <div> <Button onSmash={() => alert('Đang phát!')}> Phát phim </Button> <Button onSmash={() => alert('Đang tải!')}> Tải ảnh lên </Button> </div> ); }
Ở ví dụ này, <button onClick={onSmash}>
cho ta thấy <button>
(viết thường) của trình duyệt vẫn cần một prop gọi là onClick
, nhưng tên prop nhận bởi component tuỳ chỉnh Button
là do bạn quyết định!
Khi component của bạn hỗ trợ nhiều tương tác, bạn có thể đặt các prop hàm xử lý sự kiện cho các khái niệm riêng của ứng dụng. Ví dụ, component Toolbar
này nhận hàm xử lý sự kiện onPlayMovie
và onUploadImage
:
export default function App() { return ( <Toolbar onPlayMovie={() => alert('Đang phát!')} onUploadImage={() => alert('Đang tải!')} /> ); } function Toolbar({ onPlayMovie, onUploadImage }) { return ( <div> <Button onClick={onPlayMovie}> Phát phim </Button> <Button onClick={onUploadImage}> Tải ảnh lên </Button> </div> ); } function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); }
Hãy để ý cách component App
không cần biết Toolbar
sẽ làm gì với onPlayMovie
hoặc onUploadImage
. Đó là chi tiết thực thi của riêng Toolbar
. Ở đây, Toolbar
truyền chúng bằng các prop hàm xử lý onClick
xuống các Button
của Toolbar
, nhưng Toolbar
cũng có thể kích hoạt chúng sau trên phím tắt của bàn phím. Đặt tên prop theo các tương tác riêng của ứng dụng như onPlayMovie
cho bạn sự linh hoạt trong việc thay đổi cách sử dụng chúng sau này.
Sự lan truyền sự kiện
Các hàm xử lý sự kiện cũng sẽ bắt các sự kiện từ bất cứ component con nào mà component của bạn có thể có. Ta nói sự kiện “nổi bọt” hay “lan truyền” lên cây component: bắt đầu từ nơi sự kiện xảy ra, và sau đó lan lên trên cây.
<div>
này chứa hai nút. Cả <div>
và mỗi nút đều có hàm xử lý onClick
riêng. Bạn nghĩ hàm xử lý nào sẽ được kích hoạt khi bạn nhấn vào một nút?
export default function Toolbar() { return ( <div className="Toolbar" onClick={() => { alert('Bạn đã nhấn vào thanh công cụ!'); }}> <button onClick={() => alert('Đang phát!')}> Phát phim </button> <button onClick={() => alert('Đang tải lên!')}> Tải ảnh lên </button> </div> ); }
Nếu bạn nhấn vào một trong hai nút, onClick
của nút đó sẽ chạy trước, tiếp đến là onClick
của <div>
cha. Nên hai lời nhắn sẽ xuất hiện. Nếu bạn nhấn vào thanh công cụ, sẽ chỉ có onClick
của <div>
cha chạy.
Dừng sự lan truyền
Các hàm xử lý sự kiện nhận một đối tượng sự kiện làm tham số duy nhất. Theo quy chuẩn, tham số này thường được gọi là e
, viết tắt cho event
(sự kiện). Bạn có thể sử dụng dối tượng này để đọc thông tin về sự kiện.
Đối tượng sự kiện đó cũng cho bạn dừng sự lan truyền. Nếu bạn muốn ngăn một sự kiện truyền tới các component cha, bạn cần gọi e.stopPropagation()
như component Button
dưới đây:
function Button({ onClick, children }) { return ( <button onClick={e => { e.stopPropagation(); onClick(); }}> {children} </button> ); } export default function Toolbar() { return ( <div className="Toolbar" onClick={() => { alert('Bạn đã nhấn vào thanh công cụ!'); }}> <Button onClick={() => alert('Đang phát!')}> Phát phim </Button> <Button onClick={() => alert('Đang tải lên!')}> Tải ảnh lên </Button> </div> ); }
Khi bạn nhấn vào một nút:
- React gọi hàm xử lý
onClick
được truyền tới<button>
. - Hàm xử lý đó, được định nghĩa trong
Button
:- Gọi
e.stopPropagation()
, ngăn sự kiện nổi bọt xa hơn. - Gọi hàm
onClick
, một prop được truyền từ componentToolbar
.
- Gọi
- Hàm đó, được định nghĩa trong component
Toolbar
, hiển thị alert riêng của button. - Vì sự lan truyền đã bị dừng, hàm xử lý
onClick
của<div>
cha không chạy.
Như một hệ quả của e.stopPropagation()
, nhấn vào các nút giờ chỉ hiện một alert duy nhất (từ <button>
) chứ không phải hai alert (từ <button>
và từ <div>
cha). Nhấn một nút không giống như việc nhấn vào xung quanh thanh công cụ, nên việc dừng sự lan truyền hợp lý cho giao diện này.
Deep Dive
Trong một số trường hợp hiếm hoi, bạn có thể cần bắt tất cả sự kiện trên các element con, kể cả khi chúng đã bị dừng lan truyền. Ví dụ, có thể bạn muốn log mỗi lượt nhấn để phân tích, bất kể logic lan truyền là gì. Bạn có thể làm thế bằng cách thêm Capture
vào cuối tên sự kiện:
<div onClickCapture={() => { /* hàm này chạy trước */ }}>
<button onClick={e => e.stopPropagation()} />
<button onClick={e => e.stopPropagation()} />
</div>
Mỗi sự kiện lan truyền theo ba giai đoạn:
- Đi xuống, gọi tất cả hàm xử lý
onClickCapture
. - Chạy hàm xử lý
onClick
của element được nhấn. - Đi lên, gọi tất cả hàm xử lý
onClick
.
Việc bắt các sự kiện có lợi cho code như router hay phân tích, nhưng bạn có thể sẽ không cần sử dụng chúng trong code của ứng dụng.
Truyền các hàm xử lý thay thế cho sự lan truyền
Hãy để ý cách hàm xử lý onClick
chạy một dòng code và sau đó gọi prop onClick
được truyền từ component cha:
function Button({ onClick, children }) {
return (
<button onClick={e => {
e.stopPropagation();
onClick();
}}>
{children}
</button>
);
}
Bạn cũng có thể thêm code vào hàm xử lý trước khi gọi hàm xử lý sự kiện onClick
cha. Pattern này cung cấp một phương án thay thế cho sự lan truyền. Nó cho component con xử lý sự kiện, đồng thời cũng cho component cha chỉ định thêm một số hành vi. Không giống như sự lan truyền, nó không hề tự động. Nhưng lợi ích của pattern này là bạn có thể theo dõi rõ ràng toàn bộ chuỗi code được thực thi do một số sự kiện gây ra.
Nếu bạn dựa vào sự lan truyền và thấy khó theo dõi hàm xử lý nào thực thi và tại sao, hãy thử phương pháp này xem.
Ngăn hành vi mặc định
Một số sự kiện trình duyệt có hành vi mặc định gắn liền với chúng. Ví dụ, sự kiện submit của <form>
xảy ra khi một nút bên trong nó bị nhấn, sẽ mặc định tải lại toàn bộ trang:
export default function Signup() { return ( <form onSubmit={() => alert('Submitting!')}> <input /> <button>Send</button> </form> ); }
Bạn có thể gọi e.preventDefault()
trên đối tượng sự kiện để ngăn hành vi này xảy ra:
export default function Signup() { return ( <form onSubmit={e => { e.preventDefault(); alert('Submitting!'); }}> <input /> <button>Send</button> </form> ); }
Đừng nhầm lẫn e.stopPropagation()
và e.preventDefault()
. Cả hai đều có ích, nhưng không liên quan:
e.stopPropagation()
không cho các hàm xử lý sự kiện được gắn vào các thẻ trên kích hoạt.e.preventDefault()
ngăn các hành vi mặc định từ trình duyệt của một số ít các sự kiện.
Can event handlers have side effects?
Absolutely! Event handlers are the best place for side effects.
Unlike rendering functions, event handlers don’t need to be pure, so it’s a great place to change something—for example, change an input’s value in response to typing, or change a list in response to a button press. However, in order to change some information, you first need some way to store it. In React, this is done by using state, a component’s memory. You will learn all about it on the next page.
Recap
- You can handle events by passing a function as a prop to an element like
<button>
. - Event handlers must be passed, not called!
onClick={handleClick}
, notonClick={handleClick()}
. - You can define an event handler function separately or inline.
- Event handlers are defined inside a component, so they can access props.
- You can declare an event handler in a parent and pass it as a prop to a child.
- You can define your own event handler props with application-specific names.
- Events propagate upwards. Call
e.stopPropagation()
on the first argument to prevent that. - Events may have unwanted default browser behavior. Call
e.preventDefault()
to prevent that. - Explicitly calling an event handler prop from a child handler is a good alternative to propagation.
Challenge 1 of 2: Fix an event handler
Clicking this button is supposed to switch the page background between white and black. However, nothing happens when you click it. Fix the problem. (Don’t worry about the logic inside handleClick
—that part is fine.)
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> ); }