React – “Nghệ thuật” sử dụng useEffect() được hiệu quả hơn

6192

Bài viết được sự cho phép của tác giả Sơn Dương

useEffect là một trong những hook quan trọng và hay sử dụng nhất trong các dự án React. Do useEffect được sử dụng rất nhiều, nên nếu bạn viết code trong hook này không tốt, sẽ khiến cho dự án rất khó đọc và dễ gây ra những lỗi tiềm tàng.

Bài viết này mình sẽ chia sẻ một số nguyên tắc để làm việc với useEffect tốt hơn, dễ quản lý hơn.

1. Viết ít effect hơn

Về cơ bản, để mã nguồn dễ quản lý hơn, tránh những lỗi không đáng có, bạn hạn chế tối đa sử dụng các effects khi làm việc với state.

Với useEffect cũng vậy, nếu hạn chế được thì cứ hạn chế, hoặc tìm giải pháp khác thay thế.

Tuy nhiên, có những trường hợp bắt buộc bạn phải dùng tới useEffect hook, ví dụ như phải lấy dữ liệu ban đầu mỗi khi khởi tạo màn hình nào đó.

Data fetching

Data fetching là một side effect phổ biến thường được quản lý bằng useEffect(). Về cơ bản, các ứng dụng kiểu SPA đều phải lấy dữ liệu từ server. Do đó, data fetching là công việc phổ biến tới mức có rất nhiều thư viện 3rd được viết ra để tối ưu hóa chúng.

Có một số thư viện như react-queryApollo… mà bạn có thể thử. Với những thư viện, bạn sẽ hạn chế tối đa phải sử dụng tới useEffect.

  5 VS Code Extensions hữu ích cho React developers

  ReactJS và React Native: Những điểm giống và khác nhau cơ bản

 2. Tuân theo nguyên tắc single responsibility principle

Nếu bạn đã từng đọc về bộ triết lý để viết code clean có tên S.O.L.I.D thì sẽ biết ngay nguyên tắc này.

Nội dung nguyên tắc này là: Mỗi class, mỗi function chỉ nên làm một việc duy nhất, không nên kiêm nhiệm nhiều việc.

Nguyên tắc này cũng tương tự khi áp dụng với hàm trong useEffect(), không nhồi nhét nhiều thứ vào một lần gọi useEffect.

Ví dụ: chúng ta có một lần dùng useEffect như sau:

React.useEffect(() => {
  document.title = 'hello world'
  trackPageVisit()
}, [])

Như ở đây, mỗi khi component được mounted, bạn sẽ thực hiện hai việc là: gán tiêu đề “hello world” cho trang và theo dõi lượt visit trang.

Như vậy là bạn đã thực hiện 2 việc rất khác nhau trong lần sử dụng useEffect này. Hiện tại thì chúng ta có thể tặc lưỡi bỏ qua vì nó cũng vẫn đơn giản. Nhưng khi chức năng phần mềm phức tạp dần lên theo năm tháng thì sao?

Ví dụ chúng ta muốn thêm tính năng đồng bộ state với tiêu đề trang, mỗi khi state thay đổi thì tiêu đề trang sẽ tự động cập nhật theo:

const [title, setTitle] = React.useState('hello world')

React.useEffect(() => {
  document.title = title
  trackPageVisit()
}, [title])

Bạn đã nhận ra bug bắt đầu xuất hiện ở đây chưa?

Bug ở đây là mỗi khi tiêu đề trang thay đổi (vì một lý do nào đó như  thay đổi cài đặt) thì hàm trackPageVisit() cũng bị gọi và dẫn đến làm sai số liệu lượt visit trang.

Để xử lý lỗi này, đơn giản là tách ra làm hai lần gọi effect thôi:

const [title, setTitle] = React.useState('hello world')

React.useEffect(() => {
  document.title = title
}, [title])

React.useEffect(() => {
  trackPageVisit()
}, [])

Bạn thấy đấy, code vừa dễ đọc, vừa tránh được lỗi lại vừa dễ quản lý khi sau này yêu cầu chức năng có phức tạp hơn.

Tham khảo việc làm React hấp dẫn rên TopDev

3. Viết custom hook

Thực sự mà nói, mình không thích những component mà nhồi nhét quá nhiều hook, chưa kể trong mỗi hook lại có quá nhiều công việc trong đó. Vì cách viết như này sẽ trộn lẫn giữa logic nghiệp vụ và các chỉ dấu – markup.

Bạn hoàn toàn có thể refectoring lại chỗ này cho code được đẹp hơn, bằng cách viết custom hook. Đặc biệt, các custom hook còn có thể tái sử dụng.

Dưới đây là một số ưu điểm của custom hook.

Có thể đặt tên hook theo ý muốn

Việc tạo ra một hook riêng với tên gọi dễ nhớ sẽ giúp mã nguồn của bạn dễ đọc hơn rất nhiều. Thay vì chỉ thấy gọi useEffect và một đống logic bên trong, dev nào mà join vào sau, để đọc đoạn code đó cũng hết hơi mới hiểu được ý đồ của tác giả.

Dưới đây là một ví dụ về đặt tên cho custom hook. Chỉ cần nhìn tên là biết hook này định làm gì.

const useTitleSync = (title: string) => {
  React.useEffect(() => {
    document.title = title
}, [title])
}

const useTrackVisit = () => {
  React.useEffect(() => {
    trackPageVisit()
  }, [])
}

Đóng gói logic

Đây có lẽ là lợi thế lớn nhất của custom hook. Khi mà mọi logic được đóng gói hoàn chỉnh, khi cần là gọi sử dụng.

Như ví dụ trên, useTitleSync hook vẫn chưa hoàn chỉnh lắm, khi mà nó chỉ đóng gói được phần đánh dấu effect, khi mà component nào sử dụng vẫn phải quản lý thủ công phần tiêu đề. Vậy tại sao chúng ta không đưa luôn phần quản lý tiêu đề vào trong hook này?!

const useTitle = (initialTitle: string) => {
  const [title, setTitle] = React.useState(initialTitle)

  React.useEffect(() => {
    document.title = title
  }, [title])

  return [title, setTitle] as const
}

Dễ unit test

Nói đến clean code thì không thể nhắc tới việc phải dễ unit test.

Việc chúng ta đã tách được custom hook ra khỏi logic của component sẽ khiến việc test custom hook giống như bao function bình thường khác. Khi test, bạn sẽ không còn phải quan tâm tới các logic khác của component như trackingPageVisit.

import { act, renderHook } from '@testing-library/react-hooks'

describe('useTitle', () => {
  test('sets the document title', () => {
    const { result } = renderHook(() => useTitle('hello'))
    expect(document.title).toEqual('hello')

    act(() => result.current('world'))
    expect(document.title).toEqual('world')
  })
})

Trên đây là một kinh nghiệm để sử dụng useEffect được hiệu quả, giúp mã nguồn cũng sẽ dễ đọc và hạn chế lỗi tiềm tàng.

Bạn thấy những kinh nghiệm này thế nào? Có giúp được gì cho dự án của bạn không? Để lại ý kiến chia sẻ với mọi người trong phần bình luận nhé.

Bài viết gốc được đăng tải tại vntalking.com

Xem thêm:

Xem thêm Việc làm IT hấp dẫn trên TopDev