Bài viết dịch từ:
https://dmitripavlutin.com/react-useeffect-explanation/
1. useEffect () dành cho side-effect
Một component React sử dụng các props và/hoặc state để tính toán kết quả đầu ra. Nếu component thực hiện các tính toán không nhắm mục đích là giá trị đầu ra, thì các tính toán này được đặt tên là side effect.
Ví dụ về các side effect
là lấy data, thao tác DOM trực tiếp, sử dụng các hàm hẹn giờ như setTimeout (), v.v.
Component rendering và logic của side effect
là độc lập. Sẽ là một sai lầm nếu thực hiện các side effect
trực tiếp trong phần thân của Component , vốn chủ yếu được sử dụng để tính toán kết quả đầu ra.
Tần suất hiển thị của component không phải là điều bạn có thể kiểm soát - nếu React muốn hiển thị component, bạn không thể dừng nó.
function Greet({ name }) {
const message = `Hello, ${name}!`; // Calculates output
// Bad!
document.title = `Greetings to ${name}`; // Side-effect!
return <div>{message}</div>; // Calculates output
}
Làm thế nào để tách rendering khỏi side effect
? Hãy dùng
useEffect()
- hook chạy các side effect
độc lập với việc rendering.
import { useEffect } from 'react';
function Greet({ name }) {
const message = `Hello, ${name}!`; // Calculates output
useEffect(() => {
// Good!
document.title = `Greetings to ${name}`; // Side-effect!
}, [name]);
return <div>{message}</div>; // Calculates output
}
useEffect()
hook chấp nhận 2 đối số:
useEffect(callback[, dependencies]);
-
callback
là hàm chứa logic củaside effect
.callback
được thực thi ngay sau khi các thay đổi được đẩy vào DOM. -
dependencies
là một mảng tùy chọn của cácdependencies
.useEffect()
chỉ thực thi lệnh gọi lại nếu cácdependencies
thay đổi giữa các lần hiển thị.
Đặt logic của side effect vào hàm callback, sau đó sử dụng đối số dependencies để kiểm soát thời điểm bạn muốn side effect chạy. Đó là mục đích duy nhất của useEffect().
Ví dụ: trong đoạn code trước đó, bạn đã thấy useEffect()
đang hoạt động:
useEffect(() => {
document.title = `Greetings to ${name}`;
}, [name]);
Cập nhật tiêu đề document là side effect
vì nó không trực tiếp tính toán kết quả đầu ra của component. Đó là lý do tại sao cập nhật tiêu đề document được đặt trong một hàm callback và được cung cấp cho useEffect()
.
Ngoài ra, bạn không muốn bản cập nhật tiêu đề document thực thi mỗi khi component Greet hiển thị. Bạn chỉ muốn nó được thực thi khi prop name thay đổi - đó là lý do bạn cung cấp tên làm dependency cho useEffect (callback, [name])
.
2. Đối số Dependencies
Đối số Dependencies
của useEffect(callback, dependencies)
cho phép bạn kiểm soát thời điểm side effect chạy. Khi Dependencies
là:
A) Không có: Side effect chạy sau mỗi lần rendering
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Runs after EVERY rendering
});
}
B) Array rỗng []: side-effect chạy một lần sau lần hiển thị đầu tiên.
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Runs ONCE after initial rendering
}, []);
}
C) Có props hoặc state [prop1, prop2, ..., state1, state2]: side-effect chỉ chạy khi bất kỳ giá trị phụ thuộc nào thay đổi.
import { useEffect, useState } from 'react';
function MyComponent({ prop }) {
const [state, setState] = useState('');
useEffect(() => {
// Runs ONCE after initial rendering
// and after every rendering ONLY IF `prop` or `state` changes
}, [prop, state]);
}
Hãy phân tích các trường hợp B) và C) vì chúng được sử dụng thường xuyên.
3. Vòng đời của component
3.1 ComponentDidMount
Sử dụng một mảng phụ thuộc trống để gọi một side effect
một lần sau khi component được mount:
import { useEffect } from 'react';
function Greet({ name }) {
const message = `Hello, ${name}!`;
useEffect(() => {
// Runs once, after mounting
document.title = 'Greetings page';
}, []);
return <div>{message}</div>;
}
useEffect (..., []) được cung cấp một mảng trống làm đối số phụ thuộc. Khi được cấu hình theo cách như vậy, useEffect ()
sẽ thực hiện callback chỉ một lần, sau khi được mount vào DOM.
Ngay cả khi component hiển thị với thuộc tính name
khác, side effect chỉ chạy một lần sau lần hiển thị đầu tiên:
// First render
<Greet name="Eric" /> // Side-effect RUNS
// Second render, name prop changes
<Greet name="Stan" /> // Side-effect DOES NOT RUN
// Third render, name prop changes
<Greet name="Butters"/> // Side-effect DOES NOT RUN
3.2 Component did update
Mỗi khi side effect sử dụng props hoặc state, bạn phải chỉ ra các giá trị này dưới dạng dependencies:
import { useEffect } from 'react';
function MyComponent({ prop }) {
const [state, setState] = useState();
useEffect(() => {
// Side-effect uses `prop` and `state`
}, [prop, state]);
return <div>....</div>;
}
useEffect (callback, [prop, state])
gọi hàm callback sau khi các thay đổi được mount vào DOM và khi và chỉ khi bất kỳ giá trị nào trong mảng phụ thuộc [prop, state] đã thay đổi.
Sử dụng đối số dependencies của useEffect()
, bạn kiểm soát thời điểm gọi side effect, độc lập với các chu kỳ hiển thị của component. Một lần nữa, đó là bản chất của hook useEffect()
.
Hãy cải thiện component Greet bằng cách sử dụng prop name
trong tiêu đề của document:
import { useEffect } from 'react';
function Greet({ name }) {
const message = `Hello, ${name}!`;
useEffect(() => {
document.title = `Greetings to ${name}`;
}, [name]);
return <div>{message}</div>;
}
Prop name được đề cập trong đối số dependencies của useEffect (..., [name])
. useEffect()
hook chạy side effect sau lần hiển thị ban đầu và chỉ khi hiển thị sau khi giá trị name
thay đổi.
5, Xóa Side-effect
Một số side-effect cần được xóa: đóng socket, xóa bộ hẹn giờ.
Nếu callback
của useEffect(callback, deps)
trả về một hàm, thì useEffect () coi đây là xóa side effect:
Phần xóa side effect (dọn dẹp) hoạt động theo cách sau:
A) Sau khi rendering ban đầu, useEffect()
gọi hàm callback có side effect. chức năng dọn dẹp không được gọi.
B) Trong các lần hiển thị sau đó, trước khi gọi side effect của callback tiếp theo, useEffect()
gọi hàm dọn dẹp từ lần thực thi side effect trước đó (để dọn dẹp mọi thứ sau side effect trước đó), sau đó chạy side effect hiện tại.
C) Cuối cùng, sau khi component được loại bỏ khỏi DOM useEffect()
gọi hàm dọn dẹp từ side effect mới nhất.
Hãy xem một ví dụ khi dọn dẹp side effect là hữu ích.
Component sau <RepeatMessage message = "My Message" />
có props là message
. Sau đó, cứ sau 2 giây, props message
được ghi vào console:
import { useEffect } from 'react';
function RepeatMessage({ message }) {
useEffect(() => {
setInterval(() => {
console.log(message);
}, 2000);
}, [message]);
return <div>I'm logging to console "{message}"</div>;
}
Ở demo nhập text. Console ghi lại 2 giây một lần bất kỳ message nào đã từng được nhập từ input. Tuy nhiên, bạn chỉ cần log ra message mới nhất.
Đó là trường hợp để dọn dẹp side effect: hủy bộ hẹn giờ trước đó khi bắt đầu bộ hẹn giờ mới. Hãy trả về một chức năng dọn dẹp dừng bộ đếm thời gian trước đó:
import { useEffect } from 'react';
function RepeatMessage({ message }) {
useEffect(() => {
const id = setInterval(() => {
console.log(message);
}, 2000);
return () => {
clearInterval(id);
};
}, [message]);
return <div>I'm logging to console "{message}"</div>;
}
Thử trên demo, nhập vào ô input chỉ message mới nhất được log ra.
6. Ứng dụng của useEffect
6.1 Fetching data
useEffect() có thể thực hiện side effect là lấy dữ liệu.
Component FetchEmployees lấy danh sách nhân viên:
import { useEffect, useState } from 'react';
function FetchEmployees() {
const [employees, setEmployees] = useState([]);
useEffect(() => {
async function fetchEmployees() {
const response = await fetch('/employees');
const fetchedEmployees = await response.json(response);
setEmployees(fetchedEmployees);
}
fetchEmployees();
}, []);
return (
<div>
{employees.map(name => <div>{name}</div>)}
</div>
);
}
useEffect() bắt đầu lấy data từ request bằng cách gọi hàm bất đồng bộ fetchEmployees() sau lần khởi tạo ban đầu.
Khi request hoàn tất, setEmployees(fetchedEmployees)
sẽ cập nhật state của nhân viên với danh sách nhân viên vừa lấy được.
Lưu ý rằng đối số callback của useEffect(callback)
không thể là một hàm bất đồng bộ. Nhưng bạn luôn có thể định nghĩa và sau đó gọi một hàm bất đồng bộ bên trong chính callback:
function FetchEmployees() {
const [employees, setEmployees] = useState([]);
useEffect(() => { // <--- CANNOT be an async function
async function fetchEmployees() {
// ...
}
fetchEmployees(); // <--- But CAN invoke async functions
}, []);
// ...
}
Để get data từ props hoặc state, chỉ cần chỉ ra dependencies trong đối số dependencies : useEffect (fetchSideEffect, [prop, stateValue]).
7. Kết luận
useEffect(callback, dependencies)
là hook quản lý các side effect trong các component chức năng. đối số callback là một hàm để đặt logic của side effect. dependencies là danh sách các phụ thuộc của side effect của bạn: là props hoặc state.
useEffect(callback, dependencies) gọi lại sau khi gắn vào DOM lần đầu và trong các lần rendering sau đó, nếu bất kỳ giá trị nào bên trong các dependencies thay đổi.
Top comments (0)