DEV Community

mikebui
mikebui

Posted on

Giải thích đơn giản về React.useEffect ()

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

useEffect() hook chấp nhận 2 đối số:

useEffect(callback[, dependencies]);

  • callback là hàm chứa logic của side 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ác dependencies . useEffect() chỉ thực thi lệnh gọi lại nếu các dependencies 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().

Image 1

Ví dụ: trong đoạn code trước đó, bạn đã thấy useEffect() đang hoạt động:

useEffect(() => {
  document.title = `Greetings to ${name}`;
}, [name]);
Enter fullscreen mode Exit fullscreen mode

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
  });  
}
Enter fullscreen mode Exit fullscreen mode

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
  }, []);
}
Enter fullscreen mode Exit fullscreen mode

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]);
}
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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.

Image capture 1

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>;
}
Enter fullscreen mode Exit fullscreen mode

Ở 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>;
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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
  }, []);
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Để 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)