So sánh connect vs useSelector & useDispatch: Redux Connect với Redux Hooks mới

Tram Ho

React cho ra con hàng React Hook làm các anh em thư viện liên quan cũng phải chạy theo và cho ra phiên bản hook riêng, ông Redux cũng không nằm ngoài cuộc chơi này .

Redux có thể nói là một thư viện quản lý state phổ biến nhất cho React (mình chỉ nói React thôi nhé, không mấy ông vào bắt bẻ Redux dùng cho mọi framework mà bla bla ? ). Theo như anh em code trước đây thì ta có connect() – một Higher Order Component (HOC) giúp chúng ta nhận statedispatch action từ store tại component. Gần đây chúng ta có thêm một số hook mới, căn bản là những API mới cho phép chúng ta subcribe Redux store và dispatch các action mà không cần phải bao gói component vào trong connect().

Tụi nó là ?

  1. useSelector
  2. useDispatch
  3. useStore (cái này hôm nay sẽ không bàn tới, vì theo mình khá ít dùng)
    Trong bài này chúng ta sẽ xây dựng một shop sản phẩm siêu đơn giản sử dụng cả 2 là connect() HOC truyền thống và Redux hook mới.

Mục đích bài viết này sẽ cho bạn thấy được

  1. Cách kết nối React component với store bằng connect()
  2. Cách kết nói React component với store bằng Redux hook mới
  3. Ưu nhược của Redux hook
    Link codesandbox mình sẽ để cuối bài, bạn có thể test bằng cách log component.

Cấu trúc thư mục

Phần cài đặt package thì mình chỉ cài thêm redux và react-redux thôi nhé.

Cấu hình Redux store

File store/store.js

Tiếp theo là store/reducer.js

Tiếp theo store/constants.js

Và cuối cùng store/actions.js

Cấu hình components
Hehe , đây rồi, chúng ta sẽ cần tạo file ProductList.js và file ProductListHook.js để render file ProductItem.js. Chúng ta chỉ có 3 component đơn giản vậy thôi.

App.js sẽ là component lớn nhất render cả 2 ProductList.jsProductListHook.js

App.js dùng connect() để lấy statedispatch action

file components/ProductList.js đại diện cho việc dùng connect() HOC

file components/ProductListHook.js đại diện cho dùng Redux hook mới, cụ thể là useSelectoruseDispatch

Và cuối cùng là file component/ProductItem.js

Và kết quả sẽ như thế này đây

Ohhhh, có một chút khác biệt về cách dùng 2 hook mới là useSelector và useDispatch. Trước khi đi đến thử nghiệm thì hãy đọc lại một chút thông tin về chúng nhé.

useSelector là gì

Hook này cho phép chúng ta lấy state từ** Redux store** bằng cách sử dụng một selector function làm tham số đầu vào. Trong đoạn code phía trên bạn thấy thì mình có trả về mảng products từ store.

Mặc dù nó thực hiện công việc như mapStateToProps ( ở trên mình viết mapState cho gọn) nhưng nó vẫn có một số khác biệt mà bạn cần phải quan tâm.

  1. mapStateToProps chỉ return về 1 object, còn useSelector có thể return bất cứ giá trị nào
  2. Khi dispatch một action,useSelector sẽ thực hiện so sánh tham chiếu với giá trị được return trước đó và giá trị hiện tại. Nếu chúng khác nhau, component sẽ bị re-render. Nếu chúng giống nhau, component sẽ không re-render.
    Nếu các bạn chưa biết thì mapState là một function sẽ luôn được chạy lại mỗi khi store có một sự thay đổi bất kì nào trong đó. Với mapState, tất cả các trường được return lại thành một dạng object kết hợp. Vậy nên mỗi khi mapState chạy thì nó sẽ return về một object với tham chiếu mới. Hàm connect() sẽ thực hiện so sánh nông với object mà mapState trả về, nếu khác nhau thì sẽ re-render lại component. Tức hiểu cặn kẽ hơn là so sánh tham chiếu (so sánh ===) các trường bên trong object mà mapState trả về, chỉ cần 1 trường khác nhau là sẽ bị coi là khác nhau. ?

Suy nghĩ kĩ một chút nhé. Thoạt nhìn cách so sánh useSelector vs connect() có khác nhau 1 tẹo nhưng nếu ta khai báo nhiều useSelector cho mỗi state khác nhau thay vì gom lại một cục object duy nhất thì cách so sánh lại tương đương với connect().

Ồ, vậy là có vẻ như 2 bên tương đương rồi ha, test thử bài test nào. Mình sẽ click liên tục 5 lần vào button open shop và đây là kết quả nhận được khi component render.

Ohhh, Vậy nguyên nhân do đâu ? ?

Trong ngữ cảnh bài viết này thì mình sẽ phân tích như sau

  • Yếu tố ảnh hưởng đến việc re-render ProductList chỉ có những statemapState đăng kí. Dù cho App bị re-render 5 lần do state isOpen thay đổi, nhưng component con ProductList được bao bọc bởi connect() HOC, nó sẽ so sánh nông các state để quyết định có re-render hay không (cách này hoạt động tương tự như React.memo). Nếu anh em để ý trên hình Profiler thì ProductList được bao bởi một component làConnectFuntion (Memo)
  • Còn với ProductListHook thì có 2 yếu tố ảnh hưởng đến việc re-render đó là component cha AppstateuseSelector đăng kí. Vì vậy dù cho state products không thay đổi nhưng component cha re-render 5 lần dẫn đến ProductListHook cũng bị re-render 5 lần.

Để khắc phục điều này thì anh em có thể dùng một HOC là React.memo() cho ProductListHook.

useDispatch là gì

Hook này đơn giản chỉ là return về một tham chiếu đến dispatch function từ Redux store và được sử dụng để dispatch các action. Nhưng sẽ có vài điều mà mình cần cho các bạn biết.

file components/ProductListHook.js sau khi thêm React.memo

Nếu các bạn nhìn ProductListHook.js component thì có thể thấy rằng mình truyền một anonymous function là dispatchAddToCart xuống cho ProductItem component.

Hãy xem điều gì sẽ xảy ra khi mình click một lần vào nút Mua sản phẩm của ProductList.

Làm điều tương tự với ProductListHook xem thử kết quả như thế nào

Tôi chỉ thay đổi 1 sản phẩm, tôi chỉ muốn sản phấm đó re-render thôi, nhưng ở đây lại bị re-render hẳn 3 sản phẩm!

Nếu phân tích kĩ thì

  • Bên ProductList, ProductListItem đầu tiên re-render bởi props thay đổi (productItem), 2 cái còn lại là do component cha re-render
  • Bên ProductListHook, ProductListItem đầu tiên re-render bởi props thay đổi (productItem, addToCart), 2 cái còn lại là do props (addToCart).
    À, Vậy biết được nguyên nhân rồi, vậy cùng để khắc phục cho ProductList thì chúng ta chỉ cần dùng React.memo bao ngoài ProductItem là được.

file components/ProductItem.js lúc này

Còn với bên ProductListHook thì sẽ phức tạp hơn, chúng ta phải đi giải quyết thêm prop addToCart. Vì vậy cần làm 2 việc đó là dùng React.memo cho ProductItemuseCallback cho anonymous function là dispatchAddToCart

file components/ProductListHook.js lúc này

Và ta đã có kết quả tương tự với ProductList phía trên, nhưng tốn thêm 1 công đoạn nữa.

Vậy đi tới kết luận được rồi :

Ưu nhược của việc sử dụng Redux Hooks

Ưu điểm

Không còn connect() HOC => ít node trong hệ thống component hơn.

Nhược điểm

Bạn sẽ mất tính năng tự động memo mà connect() cung cấp.

Thoạt nhìn cứ tưởng đơn giản, nhưng cuối cùng lại dài dòng hơn ?

Vậy tôi nên có nên dùng Redux Hook?

À, cái này tùy thuộc vào bạn thôi. Nếu bỏ connect(), bạn sẽ mất nhiều tính năng tối ưu performance mà nó cung cấp. Điều này nghĩa là bạn phải để ý hơn đến việc re-render component vốn đã là vấn đề đau đầu của React. Hiện tại cá nhân mình không nghĩ rằng mình sẽ chuyển sang dùng Redux Hook.

Lời khuyên của mình khi các bạn bắt đầu sử dụng Redux Hook hãy tự hỏi

  • Các hook này có thực sự tốt hơn cách hiện tại hay không?
  • Tôi sẽ đánh mất điều gì khi sử dụng hook này?
    Luôn nhớ rằng Redux hook chỉ là tùy chọn, một phương thức thêm vào thôi! Bạn không bắt buộc phải chuyển qua dùng chúng.

Cảm mọi người đã đọc đến đây. Hi vọng bài viết có ích với mọi người

Chia sẻ bài viết ngay

Nguồn bài viết : Viblo