Có lẽ tất cả chúng ta đã quá quen thuộc với hooks trong ReactJS rồi, thậm chí có người đã sử dụng từ ngay những ngày đầu. Hooks được ra mắt trong bản 16.8 của framework reactJS. Hooks đã mang lại cho chúng ta nhưng trải nghiệm mới bằng cách đưa những chức năng dùng trong class based component.
Ngoài những hook mà đã được cung cấp trong framework như useState
, useEffect
, useContext
, … thì chúng ta cũng nó thể tạo ra những custom hook tuỳ thuộc vào dạng chức năng chúng ta muốn. Thì trong bài post này thì mình xin được giới thiệu với mọi người về 1 website rất thú vị (có thể sẽ có nhiểu người biết rồi :v) về một số công thức về hook trong ReactJS (mình xin phép dùng từ recipe thay từ “công thức” trong xuyên suốt bài đọc) và đồng thời cũng giới thiệu một số custom hook thú vị!
1. Giới thiệu về useHooks
Đây là một website cung cấp các recipe liên quan đến hooks trong ReactJS. Như cái tên của website useHooks
giới thiệu rất nhiều các recipe về hook dùng để xử lý những khó khăn, tăng những thuận lợi khi chúng ta phát triển website với ReactJS.
Trong một lần search trên google về hook thì mình đã tình cờ thấy được website này (một domain rất hay useHooks.com
) và đã rất tò mò vào xem thử thì phát hiện website này rất thú vị và hữu ích cho chúng ta. Thì đã có những bạn đã biết đến website này, thậm chí dùng một số recipe trong này nhưng mình xin phép được giới thiệu một lần nữa.
Bắt đầu thôi!
2. Giới thiệu một số recipe thú vị
Trong quá trình mình đọc về website này thì mình có đưa ra một số hook rất thú vị và hữu ích cho chúng ta trong quá trình làm việc cùng với ReactJS hooks như dưới:
useRouter
useEventListener
useLockBodyScroll
Giờ thì chúng ta cùng xem xét từng recipe một nào!
2.1 useRouter
Nếu bạn có sử dụng thư viện react-router thì ở phiên bản v5.1 thì react-router đã cho ra mắt một số hooks API (useParams
, useLocation
, useHistory
, useRouteMatch
) nhằm cung cấp cho chúng ta một cách tiếp cận mới trong cách quản lý các router state. Nhưng trong recipe này nó sẽ cung cấp cho chúng ta một cách dùng mới nhắm giải thiểu những re-render không cần thiết. Vậy hãy cùng xem recipe này nào!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | <span class="token keyword">import</span> <span class="token punctuation">{</span> useParams<span class="token punctuation">,</span> useLocation<span class="token punctuation">,</span> useHistory<span class="token punctuation">,</span> useRouteMatch <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react-router-dom'</span><span class="token punctuation">;</span> <span class="token comment">// Dùng để parse những tham số search trên URL và combine chúng thành 1 object</span> <span class="token keyword">import</span> queryString <span class="token keyword">from</span> <span class="token string">'query-string'</span><span class="token punctuation">;</span> <span class="token keyword">function</span> <span class="token function">MyComponent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token comment">// Lấy object router</span> <span class="token keyword">const</span> router <span class="token operator">=</span> <span class="token function">useRouter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Lấy giá trị từ tham số search của URL (?postId=123) hoặc router param (/:postId)</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>router<span class="token punctuation">.</span>query<span class="token punctuation">.</span>postId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Lấy giá tri pathname hiện tại</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>router<span class="token punctuation">.</span>pathname<span class="token punctuation">)</span> <span class="token comment">// Dẫn đến 1 trang khác bằng cách dùng router.push() thay vì history.push()</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator"><</span>button onClick<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token operator">=></span> router<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token string">'/about'</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token operator">></span>About<span class="token operator"><</span><span class="token operator">/</span>button<span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// Hook</span> <span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">useRouter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> params <span class="token operator">=</span> <span class="token function">useParams</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> location <span class="token operator">=</span> <span class="token function">useLocation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> history <span class="token operator">=</span> <span class="token function">useHistory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> match <span class="token operator">=</span> <span class="token function">useRouteMatch</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Trả về giá trị router object sau khi chúng ta chỉnh sửa</span> <span class="token comment">// Sử dụng tính năng Memoize để nhớ object router mà chúng ta chỉnh sửa</span> <span class="token comment">// và chỉ thay đổi khi 1 trong 4 tham số trên bị thay đổi</span> <span class="token keyword">return</span> <span class="token function">useMemo</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token comment">// Lấy function push(), replace của history lên để sử dụng</span> <span class="token comment">// mang lại sự thuận tiện thay vì dùng history.push() và history.replace()</span> push<span class="token punctuation">:</span> history<span class="token punctuation">.</span>push<span class="token punctuation">,</span> replace<span class="token punctuation">:</span> history<span class="token punctuation">.</span>replace<span class="token punctuation">,</span> pathname<span class="token punctuation">:</span> location<span class="token punctuation">.</span>pathname<span class="token punctuation">,</span> <span class="token comment">// Combine tham số search của URL và tham số truyền vào của react-router</span> <span class="token comment">// thành một object dùng chung</span> <span class="token comment">// ví dụ: /:id?sort=asc | /10?sort=asc -> { id: "10", sort: "asc" }</span> query<span class="token punctuation">:</span> <span class="token punctuation">{</span> <span class="token operator">...</span>queryString<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>location<span class="token punctuation">.</span>search<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token comment">// Convert string to object</span> <span class="token operator">...</span>params <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token comment">// Kèm theo các đối tượng match, localtion, history trong trường hợp chúng ta cần</span> match<span class="token punctuation">,</span> location<span class="token punctuation">,</span> history <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>params<span class="token punctuation">,</span> match<span class="token punctuation">,</span> location<span class="token punctuation">,</span> history<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> |
Một recipe rất thú vị đúng không mọi người! Nó sẽ giúp chúng ta sử quản lý state của router thuận tiện hơn trong 1 object duy nhất, không lo về những re-render không cần thiết và không cần phải ...restProps
và restProps.history.push(/abc)
nữa!
2.2 useEventListener
Trong một số trường hợp chúng ta sẽ phải sử dụng việc thêm một số event listeners bằng cách sử dụng useEffect
. Vậy sao chúng ta không suy nghĩ trong việc sử dụng lại logic đó trong một custom hook. Trong công thức này chúng ta sẽ tạo một hook nhằm handle việc addEventListener
đó.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | <span class="token keyword">import</span> <span class="token punctuation">{</span> useState<span class="token punctuation">,</span> useRef<span class="token punctuation">,</span> useEffect<span class="token punctuation">,</span> useCallback <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span> <span class="token keyword">function</span> <span class="token function">App</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token comment">// State for storing mouse coordinates</span> <span class="token comment">// Khởi tạo state dùng cho việc lưu trữ toạ độ của mouse</span> <span class="token keyword">const</span> <span class="token punctuation">[</span>coords<span class="token punctuation">,</span> setCoords<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">{</span> x<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span> y<span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Function dùng để handle event</span> <span class="token comment">// và dùng `useCallback` để chắc chắn rằng tham chiếu vùng nhớ sẽ không đổi</span> <span class="token keyword">const</span> handler <span class="token operator">=</span> <span class="token function">useCallback</span><span class="token punctuation">(</span> <span class="token punctuation">(</span><span class="token punctuation">{</span> clientX<span class="token punctuation">,</span> clientY <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// Cập nhật lại toạ độ</span> <span class="token function">setCoords</span><span class="token punctuation">(</span><span class="token punctuation">{</span> x<span class="token punctuation">:</span> clientX<span class="token punctuation">,</span> y<span class="token punctuation">:</span> clientY <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>setCoords<span class="token punctuation">]</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Thêm event listener</span> <span class="token function">useEventListener</span><span class="token punctuation">(</span><span class="token string">'mousemove'</span><span class="token punctuation">,</span> handler<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator"><</span>h1<span class="token operator">></span> The mouse position <span class="token function">is</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>coords<span class="token punctuation">.</span>x<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>coords<span class="token punctuation">.</span>y<span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator"><</span><span class="token operator">/</span>h1<span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">function</span> <span class="token function">useEventListener</span><span class="token punctuation">(</span>eventName<span class="token punctuation">,</span> handler<span class="token punctuation">,</span> element <span class="token operator">=</span> window<span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token comment">// Tạo một ref để lưu trữ handle</span> <span class="token keyword">const</span> savedHandler <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Cập nhật giá trị ref.current nếu handler thay đổi</span> <span class="token comment">// để chắc rằng handler luôn mới nhất</span> <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> savedHandler<span class="token punctuation">.</span>current <span class="token operator">=</span> handler<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>handler<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">useEffect</span><span class="token punctuation">(</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// Kiểm tra xem `element.addEventListener` có hợp lệ hay không</span> <span class="token keyword">const</span> isSupported <span class="token operator">=</span> element <span class="token operator">&&</span> element<span class="token punctuation">.</span>addEventListener<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>isSupported<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token comment">// Khởi tạo even listener và gọi handle function đã được chúng ta</span> <span class="token comment">// lưu trữ ở ref</span> <span class="token keyword">const</span> <span class="token function-variable function">eventListener</span> <span class="token operator">=</span> event <span class="token operator">=></span> savedHandler<span class="token punctuation">.</span><span class="token function">current</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Thêm event listener</span> element<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span>eventName<span class="token punctuation">,</span> eventListener<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Xoá event listener (cleanup)</span> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> element<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span>eventName<span class="token punctuation">,</span> eventListener<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>eventName<span class="token punctuation">,</span> element<span class="token punctuation">]</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> |
Một recipe rất hữu dụng! Nó bao hàm rất nhiều kiến thức bao gồm cả tips and tricks
trong ReactJS hooks. Hy vọng nó sẽ mang lại nhiều lợi ích cho mọi người.
2.3 useLockBodyScroll
Lock body scroll
một vấn đề chúng ta gặp phải rất nhiều trong quá trình phát triển một website. Trong một số trường hợp chúng ta muốn chặn user khỏi việc scroll (vd như modal chẳng hạn, …). Đây không phải là một vấn đề đơn giản nếu chúng ta phải xử lý trên nhiều môi trường khác nhau. Thì recipe hook bên dưới sẽ giúp chúng ta giải quyết phần nào vấn đề này! Hãy cùng xem xét nào (trong ví dụ này chúng ta sẽ làm về trường hợp modal)!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | <span class="token keyword">import</span> <span class="token punctuation">{</span> useState<span class="token punctuation">,</span> useLayoutEffect <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span> <span class="token keyword">function</span> <span class="token function">App</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token comment">// State dùng để open, close modal</span> <span class="token keyword">const</span> <span class="token punctuation">[</span>modalOpen<span class="token punctuation">,</span> setModalOpen<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator"><</span>div<span class="token operator">></span> <span class="token operator"><</span>button onClick<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setModalOpen</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token operator">></span>Show Modal<span class="token operator"><</span><span class="token operator">/</span>button<span class="token operator">></span> <span class="token operator"><</span>Content <span class="token operator">/</span><span class="token operator">></span> <span class="token punctuation">{</span>modalOpen <span class="token operator">&&</span> <span class="token punctuation">(</span> <span class="token operator"><</span>Modal title<span class="token operator">=</span><span class="token string">"Try scrolling"</span> content<span class="token operator">=</span><span class="token string">"I bet you you can't! Muahahaha ?"</span> onClose<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setModalOpen</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">}</span> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">function</span> <span class="token function">Modal</span><span class="token punctuation">(</span><span class="token punctuation">{</span> title<span class="token punctuation">,</span> content<span class="token punctuation">,</span> onClose <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token comment">// Sử dụng hook để lock body scroll khi chúng ta mở modal (mount modal)</span> <span class="token function">useLockBodyScroll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator"><</span>div className<span class="token operator">=</span><span class="token string">"modal-overlay"</span> onClick<span class="token operator">=</span><span class="token punctuation">{</span>onClose<span class="token punctuation">}</span><span class="token operator">></span> <span class="token operator"><</span>div className<span class="token operator">=</span><span class="token string">"modal"</span><span class="token operator">></span> <span class="token operator"><</span>h2<span class="token operator">></span><span class="token punctuation">{</span>title<span class="token punctuation">}</span><span class="token operator"><</span><span class="token operator">/</span>h2<span class="token operator">></span> <span class="token operator"><</span>p<span class="token operator">></span><span class="token punctuation">{</span>content<span class="token punctuation">}</span><span class="token operator"><</span><span class="token operator">/</span>p<span class="token operator">></span> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// Hook</span> <span class="token keyword">function</span> <span class="token function">useLockBodyScroll</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">useLayoutEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// Lấy giá trị overflow khởi đầu của body</span> <span class="token keyword">const</span> originalStyle <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">getComputedStyle</span><span class="token punctuation">(</span>document<span class="token punctuation">.</span>body<span class="token punctuation">)</span><span class="token punctuation">.</span>overflow<span class="token punctuation">;</span> <span class="token comment">// Ngăn không cho scroll khi mount</span> document<span class="token punctuation">.</span>body<span class="token punctuation">.</span>style<span class="token punctuation">.</span>overflow <span class="token operator">=</span> <span class="token string">'hidden'</span><span class="token punctuation">;</span> <span class="token comment">// Cho phép scroll khi component unmounts</span> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> document<span class="token punctuation">.</span>body<span class="token punctuation">.</span>style<span class="token punctuation">.</span>overflow <span class="token operator">=</span> originalStyle<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> |
Recipe này cung cấp cho chúng ta phương pháp dùng để bật tắt scroll trong một số trường hợp (trong ví dụ ở trên là modal). Rất hữu dụng đúng không! Nhưng nó vẫn là công thức, còn khi chúng ta áp dụng thì sẽ còn nhiều trường hợp rẽ nhánh nữa!
3. Kết luận
Vậy là mình đã giới thiệu xong với các bạn về useHooks
rồi! Một website rất thú vị đúng không? Những recipe rất hợp lý phải không nào? Nhưng những recipe trên vẫn chỉ là recipe thôi chúng ta cần phải kiểm nghiệm nó xem có run với code trong project của mình nữa. Thì mong là sau bài viết này thì chúng ta sẽ biết thêm một website có thể tham khảo về hook trong ReactJS và cũng như là giúp chúng ta có thể từ đó tạo ra nhiều custom hook thú vị nữa.
Thì website mọi người có thể truy cập ở đây, có rất nhiều hook mà mọi người có thể tham khảo, kèm theo đó là ví dụ trên CodeSandbox nữa. Mong là mọi người sẽ có những giây phút thú vị khi làm việc với hooks và ReactJS.
Thì bài post của mình đến đây là hết rồi! Mong rằng bài post này sẽ giúp mạng lại nhiều lợi ích cho mọi người! Xin cảm ơn và hẹn gặp lại trong các bài post tiếp theo! Xin chào!