ReactJS: Cách xử lý sự kiện DOM và Window bằng React
Giới thiệu
Trong phát triển web, các sự kiện đại diện cho các hành động xảy ra trong trình duyệt web. Bằng cách phản hồi sự kiện với trình xử lý sự kiện, bạn có thể tạo các ứng dụng JavaScript động phản hồi bất kỳ hành động nào của người dùng, bao gồm nhấp bằng chuột, cuộn dọc trang web, chạm vào màn hình cảm ứng, v.v.
Trong ứng dụng React, bạn có thể sử dụng trình xử lý sự kiện để cập nhật dữ liệu trạng thái, kích hoạt các thay đổi hỗ trợ hoặc ngăn các hành động mặc định của trình duyệt. Để làm điều này, React sử dụng một trình wrapper là SyntheticEvent
thay vì interface gốc Event. SyntheticEvent
mô phỏng chặt chẽ sự kiện trình duyệt tiêu chuẩn, nhưng cung cấp hành vi nhất quán hơn cho các trình duyệt web khác nhau. React cũng cung cấp cho bạn các công cụ để thêm và xóa trình xử lý sự kiện Window
một cách an toàn khi một component gắn (mount) và ngắt (unmount) kết nối khỏi Mô hình đối tượng tài liệu (DOM), cho phép bạn kiểm soát các sự kiện Window
đồng thời ngăn chặn rò rỉ bộ nhớ từ trình nghe bị xóa không đúng cách.
Trong bài hướng dẫn này, bạn sẽ học cách xử lý các sự kiện trong React. Bạn sẽ xây dựng một số component mẫu xử lý các sự kiện của người dùng, bao gồm component đầu vào tự xác thực và chú giải công cụ thông tin cho biểu mẫu đầu vào. Trong suốt hướng dẫn, bạn sẽ học cách thêm trình xử lý sự kiện vào các component, lấy thông tin từ trình xử lý sự kiện SyntheticEvent
và thêm và xóa trình xử lý sự kiện Window
. Đến cuối hướng dẫn này, bạn sẽ có thể làm việc với nhiều trình xử lý sự kiện khác nhau và áp dụng danh mục các sự kiện được React hỗ trợ.
Bước 1 - Tạo một dự án trống
Trong bước này, bạn sẽ tạo một dự án mới bằng Create React App. Sau đó, bạn sẽ xóa dự án mẫu và các tệp liên quan được cài đặt khi bạn khởi động dự án. Cuối cùng, bạn sẽ tạo một cấu trúc tệp đơn giản để tổ chức các component của mình. Điều này sẽ cung cấp cho bạn một cơ sở vững chắc để xây dựng ứng dụng mẫu của hướng dẫn này để tạo style trong bước tiếp theo.
Để bắt đầu, hãy thực hiện một dự án mới. Bạn mở terminal và chạy tập lệnh sau để cài đặt một dự án mới bằng cách sử dụng create-react-app
:
npx create-react-app events-tutorial
Sau khi dự án kết thúc, hãy thay đổi vào thư mục:
cd events-tutorial
Khởi động dự án với lệnh:
npm start
http://localhost:3000/
. Nếu bạn đang chạy điều này từ một máy chủ từ xa, địa chỉ sẽ là .http://your_domain:3000
Trình duyệt của bạn sẽ tải với một ứng dụng React đơn giản được bao gồm như một phần của Create React App:
Bạn sẽ xây dựng một tập hợp các component tùy chỉnh hoàn toàn mới, vì vậy bạn sẽ cần bắt đầu bằng cách xóa một số mã soạn sẵn để bạn có thể có một dự án trống.
Để bắt đầu, hãy mở component src/App.js
. Đây là component gốc được đưa vào trang. Tất cả các component sẽ bắt đầu từ đây. Bạn sửa lại file để trong đó chỉ còn chứa như sau:
import './App.css';
function App() {
return <></>;
}
export default App;
Mở một terminal khác và thực hiện thao tác xóa file logo.svg:
rm src/logo.svg
Tạo thư mục components:
mkdir src/components
Tạo thư mục App:
mkdir src/components/App
Di chuyển các file App.* vào thư mục App:
mv src/App.* src/components/App
Mở file index.js và chỉnh sửa:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App/App';
import * as serviceWorker from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
Lưu file lại và quay lại trang web ta được một trang web trống.
Bây giờ bạn đã hoàn thành dự án Tạo ứng dụng React mẫu, hãy tạo một cấu trúc tệp đơn giản. Điều này sẽ giúp bạn giữ cho các component của bạn cô lập và độc lập.
Tạo một thư mục được gọi components
trong thư mục src
, components sẽ là nơi chứa tất cả các component tùy chỉnh của bạn.
Bước 2 - Trích xuất dữ liệu sự kiện với SyntheticEvent
Trong bước này, bạn sẽ tạo component xác thực bằng cách sử dụng phần tử HTML là <input>
và trình xử lý sự kiện onChange
. Thành phần này sẽ chấp nhận đầu vào và xác thực nó hoặc đảm bảo rằng nội dung tuân theo một mẫu văn bản cụ thể. Bạn sẽ sử dụng wrapper SyntheticEvent
để truyền dữ liệu sự kiện vào hàm gọi lại và cập nhật component bằng cách sử dụng dữ liệu từ <input>
. Bạn cũng sẽ gọi các hàm từ SyntheticEvent
, chẳng hạn như preventDefault
để ngăn các hành động tiêu chuẩn của trình duyệt.
Trong React, bạn không cần phải chọn các phần tử trước khi thêm trình nghe sự kiện. Thay vào đó, bạn thêm trình xử lý sự kiện trực tiếp vào JSX của mình bằng cách sử dụng prop. Có một số lượng lớn các sự kiện được hỗ trợ trong React, bao gồm các sự kiện phổ biến như onClick
hoặc onChange
và các sự kiện ít phổ biến hơn như onWheel
.
Không giống như các trình xử lý sự kiện onevent
DOM gốc, React truyền một trình bao bọc (wrapper) đặc biệt được gọi là SyntheticEvent
đến trình xử lý sự kiện chứ không phải trình duyệt gốc Event
. Tính trừu tượng giúp giảm thiểu sự mâu thuẫn giữa các trình duyệt và cung cấp cho các component của bạn một giao diện chuẩn để làm việc với các sự kiện. API cho SyntheticEvent
tương tự như bản gốc Event
, vì vậy hầu hết các tác vụ được thực hiện theo cùng một cách.
Để chứng minh điều này, bạn sẽ bắt đầu bằng cách thực hiện đầu vào xác thực của mình. Đầu tiên, bạn sẽ tạo một component được gọi là FileNamer
. Đây sẽ là một phần tử <form>
có đầu vào để đặt tên tệp. Khi bạn điền thông tin đầu vào, bạn sẽ thấy thông tin cập nhật hộp xem trước đặt phía trên component. Component cũng sẽ bao gồm một nút gửi để chạy xác thực, nhưng đối với ví dụ này, biểu mẫu sẽ không thực sự gửi bất cứ thứ gì.
Đầu tiên, tạo thư mục:
mkdir src/components/FileNamer
Sau đó tạo và mở FileNamer.js
ra và đưa nội dung sau vào:
export default function FileNamer() {
return(
<div className="wrapper">
<div className="preview">
</div>
<form>
</form>
</div>
)
}
Tiếp theo, thêm một phần tử input với thuộc tính name để hiển thị trong hộp xem trước và nút Save. Thêm các dòng được đánh dấu sau:
export default function FileNamer() {
return(
<div className="wrapper">
<div className="preview">
<h2>Preview:</h2>
</div>
<form>
<label>
<p>Name:</p>
<input name="name" />
</label>
<div>
<button>Save</button>
</div>
</form>
</div>
)
}
Lưu file lại.
Tiếp theo, mở App.js
ra rồi đưa vào đoạn mã đánh dấu như sau:
import FileNamer from '../FileNamer/FileNamer';
function App() {
return <FileNamer />
}
export default App;
Lưu file lại và quay lại trang web ta được kết quả:
Tiếp theo, thêm một số style để giúp xác định các phần và thêm một số phần đệm và lề cho các phần tử.
Tạo và mở file FileNamer.css
ra và đưa vào đoạn code sau:
.preview {
border: 1px darkgray solid;
padding: 10px;
}
.wrapper {
display: flex;
flex-direction: column;
padding: 20px;
text-align: left;
}
.wrapper button {
background: none;
border: 1px black solid;
margin-top: 10px;
}
Trong đoạn code trên, cung cấp cho class .preview
một đường viền màu xám và phần đệm, sau đó cung cấp cho class .wrapper
một lượng nhỏ phần đệm. Hiển thị các mục trong một cột bằng cách sử dụng flex và flex-direction
và làm cho tất cả văn bản được căn chỉnh sang trái. Cuối cùng, xóa các kiểu nút mặc định bằng cách xóa đường viền và thêm đường viền màu đen.
Lưu file lại và mở file FileNamer.js
ra để import file FileNamer.css
import './FileNamer.css';
export default function FileNamer() {
return(
<div className="wrapper">
<div className="preview">
<h2>Preview:</h2>
</div>
<form>
<label>
<p>Name:</p>
<input name="name" />
</label>
<div>
<button>Save</button>
</div>
</form>
</div>
)
}
Lưu file lại và quay lại trang web ta được kết quả:
Bây giờ bạn đã có một component cơ bản, bạn có thể thêm các trình xử lý sự kiện vào phần tử <input>
. Nhưng trước tiên, bạn sẽ cần một nơi để lưu trữ dữ liệu trong trường input. Thêm Hook useState để lưu lại dữ liệu nhập vào:
import { useState } from 'react';
import './FileNamer.css';
export default function FileNamer() {
const [name, setName] = useState('');
return(
<div className="wrapper">
<div className="preview">
<h2>Preview: {name}.js</h2>
</div>
<form>
<label>
<p>Name:</p>
<input name="name" />
</label>
<div>
<button>Save</button>
</div>
</form>
</div>
)
}
Trong đoạn code trên, bạn đã sử dụng cấu trúc hủy useState
vào một biến name
để giữ dữ liệu đầu vào và một hàm được gọi setName
để cập nhật dữ liệu. Sau đó, bạn hiển thị name
trong phần xem trước, theo sau là phần mở rộng .js
, như thể người dùng đang đặt tên cho một tệp.
Bây giờ bạn có thể lưu trữ dữ liệu đầu vào, bạn có thể thêm một trình xử lý sự kiện vào phần tử <input>
. Thường có một số trình xử lý sự kiện khác nhau mà bạn có thể sử dụng cho một nhiệm vụ nhất định. Trong trường hợp này, ứng dụng của bạn cần nắm bắt dữ liệu mà người dùng nhập vào phần tử. Trình xử lý phổ biến nhất cho tình huống này là onChange
, nó sẽ kích hoạt mỗi khi thành phần thay đổi. Tuy nhiên, bạn cũng có thể sử dụng các sự kiện bàn phím, chẳng hạn như onKeyDown
, onKeyPress
, và onKeyUp
. Sự khác biệt chủ yếu liên quan đến thời điểm sự kiện xảy ra và thông tin được truyền đến đối tượng SyntheticEvent
. Ví dụ như onBlur
, một sự kiện xảy ra khi một phần tử không được focus, sẽ được kích hoạt trước sự kiện onClick
. Nếu bạn muốn xử lý thông tin người dùng trước khi một sự kiện khác xảy ra, bạn có thể chọn một sự kiện trước đó.
Lựa chọn sự kiện của bạn cũng được xác định bởi loại dữ liệu bạn muốn truyền đến SyntheticEvent
. Ví dụ như sự kiện onKeyPress
sẽ bao gồm mã charCode
mà người dùng nhấn, trong khi onChange
sẽ không bao gồm mã ký tự cụ thể, nhưng sẽ bao gồm đầy đủ các input. Điều này rất quan trọng nếu bạn muốn thực hiện các hành động khác nhau tùy thuộc vào phím mà người dùng đã nhấn.
Đối với hướng dẫn này, hãy sử dụng onChange
để nắm bắt toàn bộ giá trị đầu vào chứ không chỉ là mã gần đây nhất. Điều này sẽ giúp bạn tiết kiệm công sức lưu trữ và nối giá trị trên mỗi thay đổi.
Tạo một hàm nhận event
làm đối số và truyền nó vào phần tử <input>
bằng cách sử dụng prop onChange
:
import { useState } from 'react';
import './FileNamer.css';
export default function FileNamer() {
const [name, setName] = useState('');
return(
<div className="wrapper">
<div className="preview">
<h2>Preview: {name}.js</h2>
</div>
<form>
<label>
<p>Name:</p>
<input name="name" onChange={event => {}}/>
</label>
<div>
<button>Save</button>
</div>
</form>
</div>
)
}
Như đã đề cập trước đó, event
không phải là sự kiện trình duyệt gốc, mà nó là SyntheticEvent
được cung cấp bởi React, thường được xử lý giống nhau. Trong trường hợp hiếm hoi, bạn cần sự kiện gốc, bạn có thể sử dụng thuộc tính nativeEvent
trên SyntheticEvent
.
Bây giờ bạn đã có sự kiện, hãy lấy ra giá trị hiện tại từ thuộc tính target.value
của sự kiện. Truyền giá trị setName
để cập nhật bản xem trước:
import { useState } from 'react';
import './FileNamer.css';
export default function FileNamer() {
const [name, setName] = useState('');
return(
<div className="wrapper">
<div className="preview">
<h2>Preview: {name}.js</h2>
</div>
<form>
<label>
<p>Name:</p>
<input
autoComplete="off"
name="name"
onChange={event => setName(event.target.value) }
/>
</label>
<div>
<button>Save</button>
</div>
</form>
</div>
)
}
Ngoài ra, bạn đặt thuộc tính autoComplete thành "off"
để tắt các đề xuất của trình duyệt.
Lưu file lại và quay lại trang web thì khi bạn nhập dữ liệu vào <input>
bạn sẽ thấy bản cập nhật trong bản xem trước ở trên.
Lưu ý: Bạn cũng có thể truy cập vào tên của input bằng cách sử dụng event.target.name
. Điều này sẽ hữu ích nếu bạn đang sử dụng cùng một trình xử lý sự kiện trên nhiều đầu vào, vì name
sẽ tự động khớp với thuộc tính name
của component.
Tại thời điểm này, bạn có một trình xử lý sự kiện đang hoạt động. Bạn đang lấy thông tin người dùng, lưu nó vào state và cập nhật một component khác với dữ liệu. Nhưng ngoài việc lấy thông tin từ một sự kiện, có những tình huống mà bạn sẽ cần phải tạm dừng một sự kiện, chẳng hạn như nếu bạn muốn ngăn việc gửi biểu mẫu hoặc ngăn hành động nhấn phím.
Để dừng một sự kiện, hãy gọi hành động preventDefault
trên sự kiện đó. Điều này sẽ ngăn trình duyệt thực hiện hành vi mặc định.
Trong trường hợp của component FileNamer
, có một số ký tự nhất định có thể phá vỡ quy trình chọn tệp mà ứng dụng của bạn nên cấm. Ví dụ: bạn sẽ không muốn người dùng thêm một *
vào tên tệp vì nó xung đột với ký tự đại diện, ký tự này có thể được hiểu là tham chiếu đến một tập hợp các file khác. Trước khi người dùng có thể gửi biểu mẫu, bạn sẽ muốn kiểm tra để đảm bảo không có ký tự không hợp lệ nào. Nếu có một ký tự không hợp lệ, bạn sẽ ngăn trình duyệt gửi biểu mẫu và hiển thị thông báo cho người dùng.
Đầu tiên, tạo một Hook mà sẽ tạo ra một alert
có kiểu boolean và một hàm setAlert
tương ứng. Sau đó thêm một <div>
để hiển thị thông báo nếu alert
đúng:
import { useState } from 'react';
import './FileNamer.css';
export default function FileNamer() {
const [name, setName] = useState('');
const [alert, setAlert] = useState(false);
return(
<div className="wrapper">
<div className="preview">
<h2>Preview: {name}.js</h2>
</div>
<form>
<label>
<p>Name:</p>
<input
autoComplete="off"
name="name"
onChange={event => setName(event.target.value) }
/>
</label>
{alert && <div> Forbidden Character: *</div>}
<div>
<button>Save</button>
</div>
</form>
</div>
)
}
Trong đoạn code trên, bạn đã sử dụng toán tử &&
để chỉ hiển thị <div>
nếu alert
được thiết lập là true
đầu tiên. Thông báo trong <div>
sẽ cho người dùng biết rằng ký tự *
không được phép nhập vào.
Tiếp theo, tạo một hàm được gọi là validate
. Sử dụng phương thức biểu thức chính quy là .test để tìm xem chuỗi có chứa ký tự * nào hay không. Nếu có, bạn sẽ ngăn chặn việc gửi biểu mẫu:
import { useState } from 'react';
import './FileNamer.css';
export default function FileNamer() {
const [name, setName] = useState('');
const [alert, setAlert] = useState(false);
const validate = event => {
if(/\*/.test(name)) {
event.preventDefault();
setAlert(true);
return;
}
setAlert(false);
};
return(
<div className="wrapper">
<div className="preview">
<h2>Preview: {name}.js</h2>
</div>
<form>
<label>
<p>Name:</p>
<input
autoComplete="off"
name="name"
onChange={event => setName(event.target.value) }
/>
</label>
{alert && <div> Forbidden Character: *</div>}
<div>
<button onClick={validate}>Save</button>
</div>
</form>
</div>
)
}
Khi hàm validate
được gọi và hàm test() trả về true
, nó sẽ sử dụng lệnh event.preventDefault
rồi gọi setAlert(true)
. Nếu không, nó sẽ gọi setAlert(false)
. Trong phần cuối cùng của mã, bạn đã thêm trình xử lý sự kiện vào phần tử <button>
với prop onClick
.
Lưu file lại. Như trước đây, bạn cũng có thể sử dụng onMouseDown
, nhưng onClick
phổ biến hơn và do đó cho phép bạn tránh bất kỳ tác dụng phụ không mong muốn nào. Biểu mẫu này không có bất kỳ hành động gửi nào, nhưng bằng cách ngăn hành động mặc định, bạn sẽ ngăn được trang tải lại:
Bây giờ bạn có một biểu mẫu sử dụng hai trình xử lý sự kiện: onChange
và onClick
. Bạn đang sử dụng trình xử lý sự kiện để kết nối các hành động của người dùng với component và ứng dụng, làm cho nó trở nên tương tác được. Khi làm như vậy, bạn đã học cách thêm sự kiện vào các phần tử DOM và cách có một số sự kiện kích hoạt trên cùng một hành động, nhưng cung cấp thông tin khác nhau trong SyntheticEvent
. Bạn cũng đã học cách trích xuất thông tin từ SyntheticEvent
, cập nhật các component khác bằng cách lưu dữ liệu đó vào state và tạm dừng một sự kiện bằng cách sử dụng preventDefault
.
Trong bước tiếp theo, bạn sẽ thêm nhiều sự kiện vào một phần tử DOM để xử lý nhiều hành động khác nhau của người dùng.
Bước 3 - Thêm nhiều trình xử lý sự kiện vào cùng một phần tử
Có những tình huống khi một component duy nhất sẽ kích hoạt nhiều sự kiện và bạn sẽ cần có khả năng kết nối với các sự kiện khác nhau trên một component duy nhất. Ví dụ: trong bước này, bạn sẽ sử dụng trình xử lý sự kiện onFocus
và onBlur
để cung cấp cho người dùng thông tin kịp thời về component. Đến cuối bước này, bạn sẽ biết thêm về các sự kiện được hỗ trợ khác nhau trong React và cách thêm chúng vào các component của bạn.
Hàm validate
rất hữu ích để ngăn ngừa form của bạn khỏi việc submit dữ liệu xấu, nhưng nó không phải là rất hữu ích cho người dùng đã có kinh nghiệm: Người dùng chỉ nhận được thông tin về các ký tự có giá trị sau khi đã điền toàn bộ mẫu. Nếu có nhiều trường, nó sẽ không cung cấp cho người dùng bất kỳ phản hồi nào cho đến bước cuối cùng. Để làm cho component này thân thiện hơn với người dùng, hãy hiển thị các ký tự được phép và không được phép khi người dùng đưa dữ liệu vào trường input bằng cách thêm trình xử lý sự kiện onFocus
.
Đầu tiên, hãy cập nhật <div>
alert
để nó chứa thông tin về những ký tự được phép. Cho người dùng biết các ký tự chữ và số được phép và *
là không được phép:
import { useState } from 'react';
import './FileNamer.css';
export default function FileNamer() {
...
return(
<div className="wrapper">
<div className="preview">
<h2>Preview: {name}.js</h2>
</div>
<form>
<label>
<p>Name:</p>
<input
autocomplete="off"
name="name"
onChange={event => setName(event.target.value) }
/>
</label>
{alert &&
<div>
<span role="img" aria-label="allowed">✅</span> Alphanumeric Characters
<br />
<span role="img" aria-label="not allowed">⛔️</span> *
</div>
}
<div>
<button onClick={validate}>Save</button>
</div>
</form>
</div>
)
}
Trong doạn code trên, bạn đã sử dụng tiêu chuẩn Ứng dụng Internet đa dạng có thể truy cập (ARIA) để làm cho component dễ tiếp cận hơn với trình đọc màn hình.
Tiếp theo, thêm một trình xử lý sự kiện khác vào phần tử <input>
. Bạn sẽ thông báo cho người dùng về các ký tự được phép và không được phép khi họ kích hoạt component bằng cách nhấp hoặc tab vào input. Thêm vào dòng được đánh dấu sau:
import { useState } from 'react';
import './FileNamer.css';
export default function FileNamer() {
...
return(
<div className="wrapper">
<div className="preview">
<h2>Preview: {name}.js</h2>
</div>
<form>
<label>
<p>Name:</p>
<input
autocomplete="off"
name="name"
onChange={event => setName(event.target.value) }
onFocus={() => setAlert(true)}
/>
</label>
{alert &&
<div>
<span role="img" aria-label="allowed">✅</span> Alphanumeric Characters
<br />
<span role="img" aria-label="not allowed">⛔️</span> *
</div>
}
<div>
<button onClick={validate}>Save</button>
</div>
</form>
</div>
)
}
Bạn đã thêm prop onFocus
vào phần tử <input>
. Sự kiện này được kích hoạt khi người dùng chọn (focus) vào trường. Sau khi thêm trình xử lý sự kiện, bạn đã truyền một hàm ẩn danh để onFocus
gọi setAlert(true)
và hiển thị dữ liệu. Trong trường hợp này, bạn không cần bất kỳ thông tin nào từ SyntheticEvent
; bạn chỉ cần kích hoạt một sự kiện khi người dùng hành động. React vẫn đang gửi hàm SyntheticEvent
đến hàm, nhưng trong tình huống hiện tại, bạn không cần sử dụng thông tin trong đó.
Lưu ý: Bạn có thể kích hoạt hiển thị dữ liệu bằng
onClick
hoặc thậm chíonMouseDown
, nhưng điều đó sẽ không thể truy cập được đối với người dùng sử dụng bàn phím để tab vào các trường biểu mẫu. Trong trường hợp này, sự kiệnonFocus
sẽ xử lý cả hai trường hợp.
Lưu file lại và quay lại trang web bạn sẽ thấy thông tin sẽ vẫn ẩn cho đến khi người dùng focus vào input.
Thông tin người dùng hiện xuất hiện khi trường được focus, nhưng bây giờ dữ liệu hiện diện trong suốt thời gian của component. Không có cách nào để làm cho nó biến mất. May mắn thay, có một sự kiện khác được gọi là onBlur
kích hoạt khi người dùng để lại thông tin đầu vào. Thêm onBlur
với một hàm vô danh mà sẽ thiết lập alert
thành false
. Giống như onFocus
, điều này sẽ hoạt động cả khi người dùng nhấp vào hoặc khi người dùng rời khỏi input:
import { useState } from 'react';
import './FileNamer.css';
export default function FileNamer() {
...
return(
<div className="wrapper">
<div className="preview">
<h2>Preview: {name}.js</h2>
</div>
<form>
<label>
<p>Name:</p>
<input
autocomplete="off"
name="name"
onBlur={() => setAlert(false)}
onChange={event => setName(event.target.value) }
onFocus={() => setAlert(true)}
/>
</label>
{alert &&
<div>
<span role="img" aria-label="allowed">✅</span> Alphanumeric Characters
<br />
<span role="img" aria-label="not allowed">⛔️</span> *
</div>
}
<div>
<button onClick={validate}>Save</button>
</div>
</form>
</div>
)
}
Lưu file lại và quay lại trang web, thông tin sẽ hiển ra khi người dùng nhấp vào (focus) phần tử và ẩn đi khi người dùng nhấp ra (blur):
Bạn có thể thêm bao nhiêu trình xử lý sự kiện nếu bạn cần vào một phần tử. Nếu bạn có ý tưởng về một sự kiện bạn cần, nhưng không chắc về tên, hãy cuộn qua các sự kiện được hỗ trợ và bạn có thể tìm thấy thứ mình cần.
Trong bước này, bạn đã thêm nhiều trình xử lý sự kiện vào một phần tử DOM. Bạn đã biết cách các trình xử lý sự kiện khác nhau có thể xử lý một loạt các sự kiện — chẳng hạn như cả nhấp chuột và tab — hoặc một phạm vi sự kiện hẹp.
Trong bước tiếp theo, bạn sẽ thêm trình nghe sự kiện toàn cục vào đối tượng Window
để nắm bắt các sự kiện xảy ra bên ngoài component tức thì.
Bước 4 - Thêm sự kiện window
Trong bước này, bạn sẽ đưa thông tin người dùng vào một component bật lên (pop-up) sẽ kích hoạt khi người dùng focus vào một input và sẽ đóng khi người dùng nhấp vào bất kỳ nơi nào khác trên trang. Để đạt được hiệu ứng này, bạn sẽ thêm trình nghe sự kiện toàn cục vào đối tượng Window
bằng cách sử dụng Hook useEffect
. Bạn cũng sẽ xóa trình xử lý sự kiện khi component ngắt kết nối để tránh rò rỉ bộ nhớ, khi ứng dụng của bạn chiếm nhiều bộ nhớ hơn mức cần thiết.
Khi kết thúc bước này, bạn sẽ có thể thêm và xóa trình nghe sự kiện một cách an toàn trên các component riêng lẻ. Bạn cũng sẽ học cách sử dụng Hook useEffect
để thực hiện các hành động khi một component kết nối và ngắt kết nối.
Trong hầu hết các trường hợp, bạn sẽ thêm trình xử lý sự kiện trực tiếp vào các phần tử DOM trong JSX của mình. Điều này giữ cho mã của bạn tập trung và ngăn ngừa các tình huống khó hiểu trong đó một component đang kiểm soát hành vi của component khác thông qua đối tượng Window
. Nhưng có những lúc bạn sẽ cần thêm trình nghe sự kiện toàn cục. Ví dụ: bạn có thể muốn một trình nghe cuộn tải nội dung mới hoặc bạn có thể muốn nắm bắt các sự kiện nhấp chuột bên ngoài một component.
Trong hướng dẫn này, bạn chỉ muốn hiển thị cho người dùng thông tin về đầu vào nếu họ yêu cầu cụ thể. Sau khi hiển thị thông tin, bạn sẽ muốn ẩn thông tin đó bất cứ khi nào người dùng nhấp vào trang bên ngoài component.
Để bắt đầu, hãy di chuyển phần hiển thị alert
vào một <div>
mới với một className
là information-wrapper
. Sau đó, thêm một nút mới với một className
là information
và một sự kiện onClick
mà sẽ gọi setAlert(true)
:
import { useState } from 'react';
import './FileNamer.css';
export default function FileNamer() {
...
return(
<div className="wrapper">
<div className="preview">
<h2>Preview: {name}.js</h2>
</div>
<form>
<label>
<p>Name:</p>
<input
autocomplete="off"
name="name"
onChange={event => setName(event.target.value) }
/>
</label>
<div className="information-wrapper">
<button
className="information"
onClick={() => setAlert(true)}
type="button"
>
more information
</button>
{alert &&
<div className="popup">
<span role="img" aria-label="allowed">✅</span> Alphanumeric Characters
<br />
<span role="img" aria-label="not allowed">⛔️</span> *
</div>
}
</div>
<div>
<button onClick={validate}>Save</button>
</div>
</form>
</div>
)
}
Bạn cũng đã xóa onFocus
và onBlur
khỏi <input>
để xóa hành vi từ bước cuối cùng.
Lưu file lại sau đó mở FileNamer.css
ra, thêm một số style để định vị tuyệt đối (absolute) thông tin popup
ở phía trên nút. Sau đó, thay đổi <button>
với một class information
là màu xanh lam không có đường viền:
.information {
font-size: .75em;
color: blue;
cursor: pointer;
}
.wrapper button.information {
border: none;
}
.information-wrapper {
position: relative;
}
.popup {
position: absolute;
background: white;
border: 1px darkgray solid;
padding: 10px;
top: -70px;
left: 0;
}
.preview {
border: 1px darkgray solid;
padding: 10px;
}
.wrapper {
display: flex;
flex-direction: column;
padding: 20px;
text-align: left;
}
.wrapper button {
background: none;
border: 1px black solid;
margin-top: 10px;
}
Lưu file lại và quay lại trang web, khi bạn nhấp vào more information
, thông tin về component sẽ xuất hiện:
Bây giờ bạn có thể kích hoạt cửa sổ bật lên, nhưng không có cách nào để xóa nó. Để khắc phục sự cố đó, hãy thêm trình xử lý sự kiện toàn cầu mà sẽ gọi setAlert(false)
bất kỳ khi nào nhấp bên ngoài cửa sổ bật lên.
Trình nghe sự kiện sẽ trông giống như sau:
window.addEventListener('click', () => setAlert(false))
Tuy nhiên, bạn phải lưu ý về thời điểm bạn đặt trình xử lý sự kiện trong mã của mình. Ví dụ: bạn không thể thêm trình xử lý sự kiện ở trên mã component được, bởi vì sau đó mỗi khi có điều gì đó thay đổi, component sẽ hiển thị lại và thêm trình xử lý sự kiện mới. Vì component của bạn có thể sẽ hiển thị lại nhiều lần, điều đó sẽ tạo ra rất nhiều trình nghe sự kiện không sử dụng, chiếm bộ nhớ.
Để giải quyết vấn đề này, React có một Hook đặc biệt được gọi là useEffect
sẽ chỉ chạy khi các thuộc tính cụ thể thay đổi. Cấu trúc cơ bản là:
useEffect(() => {
// chạy code khi có bất kỳ điều gì trong mảng thay đổi
}, [someProp, someOtherProp])
Trong ví dụ đơn giản, React sẽ chạy mã trong hàm ẩn danh bất cứ khi nào someProp
hoặc someOtherProp
thay đổi. Các mục trong mảng được gọi là phụ thuộc (dependency) . Hook này lắng nghe các thay đổi đối với các phụ thuộc và sau đó chạy hàm sau khi thay đổi.
Bây giờ bạn có những công cụ để thêm và loại bỏ một trình lắng nghe sự kiện toàn cầu một cách an toàn bằng cách sử dụng useEffect
để thêm trình nghe sự kiện bất cứ khi nào alert
là true
và gỡ bỏ nó bất cứ khi nào alert
là false
.
Còn một bước nữa. Khi component ngắt kết nối, nó sẽ chạy bất kỳ chức năng nào mà bạn trả về từ bên trong Hook useEffect
của mình. Do đó, bạn cũng sẽ cần trả về một hàm loại bỏ trình xử lý sự kiện khi component ngắt kết nối.
Cấu trúc cơ bản sẽ như thế này:
useEffect(() => {
return () => {} // chạy code khi component ngắt kết nối (unmount)
}, [someProp, someOtherProp])
Bây giờ bạn đã biết cấu trúc của Hook useEffect
, hãy sử dụng nó trong ứng dụng của bạn. Mở ra FileNamer.js
ra và import useEffect
, sau đó thêm một hàm ẩn danh trống với phần phụ thuộc của alert
và setAlert
trong mảng sau hàm:
import { useEffect, useState } from 'react';
import './FileNamer.css';
export default function FileNamer() {
const [name, setName] = useState('');
const [alert, setAlert] = useState(false);
useEffect(() => {
}, [alert, setAlert]);
...
Trong đoạn code trên, bạn đã thêm cả alert
và setAlert
. Để hoàn thiện, React khuyên bạn nên thêm tất cả các phụ thuộc bên ngoài vào hàm useEffect
. Vì bạn sẽ gọi hàm setAlert
nên nó có thể được coi là một phụ thuộc. setAlert
sẽ không thay đổi sau lần hiển thị đầu tiên, nhưng bạn nên đưa bất kỳ thứ gì có thể được coi là phụ thuộc vào.
Tiếp theo, bên trong hàm ẩn danh, hãy tạo một hàm mới có tên handleWindowClick
g mà sẽ gọi setAlert(false)
:
import { useEffect, useState } from 'react';
import './FileNamer.css';
export default function FileNamer() {
const [name, setName] = useState('');
const [alert, setAlert] = useState(false);
useEffect(() => {
const handleWindowClick = () => setAlert(false)
}, [alert, setAlert]);
...
}
Sau đó, thêm một điều kiện sẽ gọi window.addEventListener('click', handleWindowClick)
khi nào alert
là true
và sẽ gọi window.removeEventListener('click', handleWindowClick)
khi nào alert
là false
. Điều này sẽ thêm trình xử lý sự kiện mỗi khi bạn kích hoạt cửa sổ bật lên và xóa nó mỗi khi cửa sổ bật lên đóng lại:
import { useEffect, useState } from 'react';
import './FileNamer.css';
export default function FileNamer() {
const [name, setName] = useState('');
const [alert, setAlert] = useState(false);
useEffect(() => {
const handleWindowClick = () => setAlert(false)
if(alert) {
window.addEventListener('click', handleWindowClick);
} else {
window.removeEventListener('click', handleWindowClick);
}
}, [alert, setAlert]);
...
}
Cuối cùng, trả về một hàm mà sẽ loại bỏ trình lắng nghe sự kiện. Một lần nữa, điều này sẽ chạy khi component ngắt kết nối. Có thể không có trình nghe sự kiện trực tiếp, nhưng nó vẫn đáng để dọn dẹp trong các tình huống mà trình nghe vẫn tồn tại:
import { useEffect, useState } from 'react';
import './FileNamer.css';
export default function FileNamer() {
const [name, setName] = useState('');
const [alert, setAlert] = useState(false);
useEffect(() => {
const handleWindowClick = () => setAlert(false)
if(alert) {
window.addEventListener('click', handleWindowClick);
} else {
window.removeEventListener('click', handleWindowClick)
}
return () => window.removeEventListener('click', handleWindowClick);
}, [alert, setAlert]);
...
}
Lưu các file lại và quay lại trang web, nếu bạn bấm vào nút more information, thì thông báo sẽ xuất hiện. Nếu bạn nhìn vào trình lắng nghe sự kiện toàn cầu trong các công cụ dành cho nhà phát triển, bạn sẽ thấy có một trình lắng nghe click
:
Nhấp vào bất kỳ đâu bên ngoài thành phần. Thông báo sẽ biến mất và khi bạn nhấn nút refresh bên phải bạn sẽ không còn thấy trình xử lý sự kiện click toàn cầu nữa.
Hook useEffect
của bạn đã thêm và xóa thành công trình xử lý sự kiện toàn cầu dựa trên tương tác của người dùng. Nó không bị ràng buộc với một phần tử DOM cụ thể, nhưng thay vào đó được kích hoạt bởi sự thay đổi trong state của component.
Lưu ý: Từ quan điểm truy cập, component này chưa hoàn chỉnh. Nếu người dùng không thể sử dụng chuột, họ sẽ bị kẹt với một cửa sổ bật lên đang mở vì họ sẽ không bao giờ có thể nhấp vào bên ngoài component. Giải pháp sẽ là thêm một trình xử lý sự kiện khác cho
keydown
để xóa thông báo. Code sẽ gần giống nhau ngoại trừ phương thức sẽ làkeydown
thay vìclick
.
Như vậy trong bước này, bạn đã thêm trình nghe sự kiện toàn cục bên trong một componet. Bạn cũng đã học cách sử dụng Hook useEffect
để thêm và xóa trình xử lý sự kiện đúng cách khi trạng thái thay đổi và cách xóa trình xử lý sự kiện khi component ngắt kết nối.
Phần kết luận
Trình xử lý sự kiện cung cấp cho bạn cơ hội để điều chỉnh các component của bạn với các hành động của người dùng. Những điều này sẽ mang lại cho ứng dụng của bạn trải nghiệm phong phú và sẽ tăng khả năng tương tác của ứng dụng. Chúng cũng sẽ cung cấp cho bạn khả năng nắm bắt và phản hồi các hành động của người dùng.
Các trình xử lý sự kiện của React cho phép bạn tích hợp các lệnh gọi lại sự kiện của mình với HTML để bạn có thể chia sẻ hảm và thiết kế trên một ứng dụng. Trong hầu hết các trường hợp, bạn nên tập trung vào việc thêm trình xử lý sự kiện trực tiếp vào các phần tử DOM, nhưng trong các tình huống bạn cần nắm bắt các sự kiện bên ngoài component, bạn có thể thêm trình xử lý sự kiện và dọn dẹp chúng khi chúng không còn được sử dụng để tránh rò rỉ bộ nhớ và tạo ra các ứng dụng hiệu quả.