Sử dụng Effect Hook (ok)
https://vi.reactjs.org/docs/hooks-effect.html
Hook là một tính năng mới từ React 16.8. Nó cho phép sử dụng state và các tính năng khác của React mà không cần viết dạng class
Effect Hook cho phép thực hiện side effect bên trong các function component:
Đoạn snippet này dựa trên ví dụ về counter ở trang trước, chúng ta có thêm tính năng mới: đặt giá trị document title tương ứng với số lần click.
Việc fetching data, thiết lập các subscription, và việc thay đổi DOM trong React component, những hành động như vậy được gọi là “side effect” (hoặc “effect). Bạn có thể đã sử dụng những “side effect” này trong những component của bạn trước đây.
Mẹo nhỏ
Nếu bạn quen với các phương thức lifecycle của React class, bạn có thể hình dung
useEffect
Hook như sự kết hợp củacomponentDidMount
,componentDidUpdate
, vàcomponentWillUnmount
.
Có 2 loại side effect phổ biến trong React component: loại không cần cleanup, và loại cần. Cùng phân biệt 2 loại này kỹ hơn.
Effect không cần Cleanup
Đôi lúc, chúng ta muốn chạy một vài đoạn code sau khi React đã cập nhập DOM. Network request, tự ý thay đổi DOM, và logging là những ví dụ điển hình của effect không cần cleanup. Chúng ta gọi như vậy vì có thể chạy chúng và quên ngay lập tức. Hãy so sánh class và Hook cho phép thực hiện side effect như thế ra sao.
Ví dụ sử dụng Classes
Trong React class components, phương thức render
không được phép tạo ra side effect. Nó sẽ là quá sớm — chúng ta thường chỉ muốn chạy effect sau khi React đã cập nhập DOM.
Đó là lý do tại sao trong React class, chúng ta đặt side effect bên trong componentDidMount
và componentDidUpdate
. Quay lại ví dụ, đây là React counter class component sẽ cập nhập document title ngay sau khi React thay đổi DOM:
Để ý cách chúng ta đã lập lại 2 thao tác tương tự nhau bên trong 2 phương thức lifecycle
Đó là bởi vì trong đa phần các trường hợp, chúng ta muốn thực hiện cùng một side effect khi component đã mount hoặc đã update. Một cách tổng quát, chúng ta muốn thực hiện sau mỗi lần render — nhưng React class component không có phương thức như vậy. Chúng ta có thể tách nó ra thành một hàm riêng, nhưng vẫn phải gọi nó ở 2 nơi khác nhau.
Bây giờ chúng ta xem cách làm tương tự với useEffect
Hook.
Ví dụ sử dụng Hook
Chúng ta đã xem ví dụ ở trên, giờ xem kỹ hơn một lần nữa:
useEffect
đã làm gì? Bằng cách sử dụng Hook này, chúng ta nói với React rằng component của chúng ta cần thực hiện một việc gì đó sau khi render. React sẽ ghi nhớ hàm bạn truyền vào (chúng tôi thích gọi nó là “effect”), và sau đó gọi lại hàm này sau khi DOM đã update. Trong effect này, chúng ta đổi document title, chúng ta cũng có thể fetch data hoặc gọi một số API khác.
Tại sao useEffect
được gọi bên trong component? Đặt useEffect
bên trong component cho phép chúng ta truy xuất đến state count
(hoặc bất kỳ prop nào) bên trong effect. Chúng ta không cần một API đặc biệt để đọc nó — nó đã nằm trong scope của function. Hook tận dụng JavaScript closures và tránh cung cấp thêm các APIs mà bản thân JavaScript đã có sẵn giải pháp.
useEffect
chạy sau tất cả những lần render? Đúng! Theo mặc định, nó chạy sau lần render đầu tiên và mỗi lần update. (Chúng ta sẽ nói về làm cách nào để tùy biến lại.) Thay vì nghĩ theo hướng “mounting” và “updating”, bạn sẽ thấy dễ hiểu hơn nếu nghĩ theo kiểu “sau khi render”. React đảm bảo DOM đã được update trước khi chạy effect.
Giải thích cụ thể
Giờ chúng ta đã hiểu về effect, đoạn code này sẽ rất dễ hiểu:
Chúng ta khai báo state count
, và sau đó nói với React chúng ta cần sử dụng. Chúng ta truyền cho useEffect
Hook một hàm. Hàm truyền vào này là effect. Bên trong effect, chúng ta đặt document title sử dụng API document.title
. Chúng ta có thể đọc giá trị sau cùng của count
bên trong effect bởi vì nó nằm chung scope với function. Khi React render component, nó sẽ nhớ lại effect chúng ta đã gửi, và chạy effect sau khi cập nhập DOM. Nó xảy ra ở tất cả các lần render, kể cả lần đầu.
Lập trình viên JavaScript có kinh nghiệm sẽ để ý thấy function truyền vào cho useEffect
sẽ khác nhau cho tất cả các lần render. Đây là điều cố ý. Thật ra, nó sẽ cho chúng ta đọc giá trị count
bên trong effect mà không cần lo lắng về việc lấy state. Mỗi lần chúng ta re-render, chúng ta gọi một effect khác, thay thế cái trước đó. Bằng cách này, nó làm cho effect như một phần của việc render — mỗi effect “thuộc vào” một render cụ thể. Chúng ta sẽ hiểu tại sao cách này lại hiệu quả ở phần sau của bài này.
Mẹo nhỏ
Không giống
componentDidMount
hoặccomponentDidUpdate
, effect chạy vớiuseEffect
không block trình duyệt cập nhập màn hình. Các effect chủ yếu không cần xảy ra tuần tự. Trong vài tình huống không mấy phổ biến (ví dụ như đo layout), chúng ta cóuseLayoutEffect
Hook với API tính năng tương tự nhưuseEffect
.
Effect cần Cleanup
Ở trên, chúng ta đã bàn về những side effect không cần cleanup. Tuy nhiên, một vài effect cần có. Ví dụ, chúng ta muốn thiết lập các subscription cho vài data source bên ngoài. Tình huống đó, clean up là rất quan trọng để không xảy ra memory leak! Cùng so sánh cách làm giữa class và Hook
Ví dụ sử dụng Class
Trong React class, chúng ta thường cài đặt một subscription trong componentDidMount
, và clean it up trong componentWillUnmount
. Lấy ví dụ, chúng ta có ChatAPI
module cho phép chúng ta subscribe vào tình trạng online của 1 danh sách friend. Cách chúng ta làm với class
Để ý componentDidMount
và componentWillUnmount
. Phương thức Lifecycle buộc chúng ta tách logic này ra thậm chí cả 2 đoạn code trên điều liên quan đến cùng một effect.
Lưu ý
Nếu để ý kỹ hơn, bạn sẽ thấy chúng ta còn cần thêm
componentDidUpdate
để thực sự chuẩn xác. Tạm thời cứ bỏ qua phần đó vì chúng ta sẽ đề cập lại ở phần sau of this page.
Ví dụ sử dụng Hooks
Cùng xem cách chúng ta làm với Hook.
Bạn có thể sẽ nghĩ chúng ta cần 2 effect khác nhau để thực hiện cleanup. Code khởi tạo và xóa subscription luôn luôn đứng kề nhau, useEffect
được thiết kế để dữ chúng cùng một chỗ. Nếu effect trả về function, React sẽ chạy function đó, chúng ta đưa clean up vào bên trong function trả về:
Tại sao chúng ta trả về function bên trong effect? Đây là một tùy chọn để chạy cơ chế cleanup cho effect. Nó cho phép chúng ta đưa tạo và xóa subscription trong cùng một effect.
Khi nào React clean up một effect? React thực hiện cleanup khi component unmount. Tuy nhiên, như đã học trước đó, effect trên tất cả những lần render, không phải chỉ một. Đó là tại sao React đồng thời cleans up effect từ những lần render trước. Chúng ta sẽ thảo luận thêm việc này giúp tránh bug và làm cách nào tùy biến đặc tính này để cái thiện performance ở bên dưới.
Ghi chú
Chúng ta không cần trả về một function có tên trong effect. Chúng ta gọi nó là
cleanup
để chỉ rõ mục đích, bạn có thể dùng arrow function trong thực tế.
Tổng hợp
Chúng ta đã học useEffect
cho phép chúng ta thực hiện nhiều kiểu side effect sau khi component được render. Một vài effect cần cleanup nó sẽ return một function:
Một vài effect khác có thể không cần cleanup, thì không cần return gì cả.
Mẹo nhỏ: Sử dụng nhiều Effect tách biệt
Một trong những vấn đề đã liệt kê ở động lực tạo ra Hooks là các phương thức lifecycle của class thường chứa những logic không liên quan với nhau, còn những logic đáng lý phải nằm gần nhau lại nằm ở các phương thức khác nhau. Đây là component kết hợp counter và friend status từ ví dụ ở trên
Để ý cái logic của document.title
đang nằm ở componentDidMount
và componentDidUpdate
. Logic của subscription thì cũng nằm ở componentDidMount
và componentWillUnmount
. Và componentDidMount
chứa code cả hai.
Như vậy hook đã giải quyết vấn đề này như thế nào? Nếu như bạn có thể sử dụng State Hook nhiều lần, bạn cũng có thể sử dụng nhiều effect. Nó cho phép tách những logic không liên quan ra thành những effect khác nhau:
Hook cho phép tách code dựa trên cái nó đang làm chứ không đi theo phương thức lifecycle. React sẽ apply từng effect được sử dụng trong component, theo thứ tự đã khai báo.
Giải thích: Tại sao Effect chạy trên mỗi update
Nếu đã từng sử dụng class, bạn sẽ thắc mắc tại sao bước cleanup effect lại chạy trên mỗi lần re-render, mà không phải khi unmounting. Xét một ví dụ thực tế để thấy tại sao thiết kế này giúp chúng ta có những component ít bug hơn
Ở phần trước, chúng ta có đề cập ví dụ FriendStatus
để hiển thị trạng thái online của Friend. Class đọc friend.id
từ this.props
, subscribe sau khi component mount, và unsubscribe trong lúc unmounting:
Chuyện gì sẽ xảy ra nếu prop friend
thay đổi trong khi component đang hiển thị trên màn hình (chưa unmount)? Chắc chắn có bug với danh sách status. Chúng ta cũng có thể gây ra memory leak hoặc crash khi đang unmounting và gọi unsubscribe nếu có một Friend ID không đúng.ư
Quên handle componentDidUpdate
là điều dễ dẫn tới có bug trong React.
Đây là phiên bản sử dụng Hook
Không còn bị dính bug như ở trên
Sẽ không có một đoạn code nào đặc biệt để xử lý lúc update vì theo cách chạy mặc định của useEffect
nó đã xóa effect trước khi apply effect mới. Để hình dung hóa, đây là các bước gọi subscribe và unsubscribe mà component đã chạy qua:
Đặc tính này đảm bảo thống nhất và ngăn bug thường xuất hiện do không cập nhập login với class component
Mẹo nhỏ: Tối ưu Performance bằng cách bỏ qua Effect
Trong một số trường hợp, clean và apply effect sau khi render có thể dẫn đến ảnh hưởng performance. Trong class component, chúng ta giải quyết bằng viết một hàm so sánh giữa prevProps
hoặc prevState
bên trong componentDidUpdate
:
Đây là yêu cầu rất cần thiết, nên đã được có đưa sẵn trong useEffect
Hook API. Bạn có thể bảo React bỏ qua việc apply effect nếu một số giá trị không thay đổi giữa các lần render. Để làm như vậy, truyền vào một array (không bắt buộc) vào useEffect
:
Trong ví dụ ở trên, chúng ta truyền vào [count]
như một tham số thứ 2. Nó nghĩa là gì? Nếu count
là 5
, rồi sau đó component re-render với count
vẫn bằng 5
, React sẽ so sánh [5]
từ lần render trước và [5]
với lần render hiện tại. Vì tất cả giá trị trong mảng bằng nhau (5 === 5
), React sẽ bỏ qua effect. Đó là cách chúng ta tối ưu
Khi chúng ta render với count
thành 6
, React sẽ so sánh các giá trị trong [5]
từ lần render trước với các giá trị trong [6]
lần render hiện tại. Ở lần này, React sẽ gọi lại effect vì 5 !== 6
. Nếu có nhiều giá trị bên trong array, React sẽl re-run effect nếu một trong các giá trị đó khác với lần trước.
Effect cũng làm việc tương tự với quá trình cleanup:
Trong tương lai, tham số thứ 2 sẽ được tự động thêm vào trong lúc build-transform.
Last updated
Was this helpful?