Hook là chức năng được thêm mới từ phiên bản React 16.8. Với hook, chúng ta có thể quản lý state của component mà không cần đến class cho dài dòng. Nói không ngoa, class component implement được chức năng gì thì function component dùng hook implement được chức năng đó.
1. Một số lưu ý về hook:
- Hoàn toàn tự nguyện: Bạn có thể chọn dùng hook với các component mới mà không cần phải viết lại hay sửa lại các component cũ.
- Hoàn toàn tương thích ngược: Hook implement lại các chứng năng của class theo một cách mới chứ không làm thay đổi cách nó hoạt động.
- Sẽ vẫn luôn hỗ trợ class component react: Bạn biết đấy hiện tại trong nhiều dự án tồn tại rất nhiều class component nên việc ngừng hỗ trợ class component là không khôn ngoan chút nào.
2. Hook rốt cuộc là cái gì
- Đọc đến đây mà để các bạn hỏi là hook là cái gì thì thật không nên, nên mình sẽ đưa ra một ví dụ mà sử dụng hook thông dụng nhất:
import React, { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Đấy thay vì dùng class component, khai báo state, rồi this các kiểu, rồi là this.setState().. dài dòng, function component dùng hook ở phía trên gọn code hơn hẳn phải không nào.
3. Có class rồi, sao phải tạo thêm hook
- Khi các bạn xài class nhiều rồi thì điều dễ thấy nhất là class quá dài dòng. Để implement một class component xử lý state đơn giản phải tốn khá nhiều dòng code. Dùng hook như ví dụ trên ta thấy code ngắn gọn hơn hẳn phải không nào.
- Thứ hai là khi dùng class thì chúng ta ta khó có thể share những stateful logic cho các component khác, để share được chúng ta lại phải dùng một số pattern dài dòng hơn. Thông qua hook chúng ta có thể viết các custom hook để share stateful logic cho các component khác dễ dàng. Mình sẽ trình bày sau ở phần custom hook.
- Khi component bự lên, nó trở nên khó hiểu với quá nhiều hàm handle lifecycle: khi xử lý các component phức tạp, ta thường dùng các hàm lifecycle rối rắm như componentDidMount, componentDidUpdate, componentWillUnmount. Thay vì thế chúng ta sẽ sử dụng 1 hook duy nhất để giải quyết tất cả - hook useEffect()
- Class khó học, khó hiểu hơn: thật sự thì javascript không phải là một ngôn ngữ hướng đối tượng thuần túy như java, nên cách triển khai class của javascript khá rối rắm với các từ khóa như this, arrow function, function thường, method... Khi bạn chưa hiểu javascript thật sự mà coi nó như một ngôn ngữ hướng đối tượng thuần túy, javascript sẽ thực hiện hành vi các method và keyword this một cách kỳ lạ. Phần này khá phức tạp mình sẽ đưa link tham khảo ở phía cuối bài.
Một số hook thông dụng:
Sau đây mình sẽ trình bày các hook thông dụng nhất, từ react đến redux.
4. State hook:
Hook này đúng như cái tên để quản lý state của component, concept hoạt động giống hệt state của class component.
Ví dụ sau đây sẽ render một counter. Khi click vào button, counter sẽ tăng giá trị thêm 1:
import React, { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Khai báo nhiều state cùng một lúc:
Để khai báo nhiều state một lúc đơn giản ta khai báo nhiều useState thôi
function ExampleWithManyStates() {
// Declare multiple state variables!
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
// ...
}
5. Effect hook:
Effect hook sẽ gộp logic của các lifecycle method như componentDidMount, componentDidUpdate, componentWillUnmount thành một. Sử dụng như sau:
Component sau sẽ set docucment title sau khi React update cây DOM:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Khi muốn 'clean up' khi component unmount, ta làm như sau:
Ví dụ sau đây, khi component update cây DOM sẽ subscribe đến friend status, khi component unmount thì sẽ unsubscribe friend status
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
Khai báo nhiều effect hook:
Giống như state hook, có khai báo effect hook nhiều lần như sau:
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
// ...
6. Redux hook - useSelector:
Thay vì dùng mapStateToProps trong class component, ta dùng useSelector tương tự như sau:
import React from 'react'
import { useSelector } from 'react-redux'
export const CounterComponent = () => {
const counter = useSelector(state => state.counter)
return <div>{counter}</div>
}
7. Redux hook - useDispatch:
Thay vì dùng mapDispatchToProps trong class component, ta dùng useDispatch tương tự như sau:
import React from 'react'
import { useDispatch } from 'react-redux'
export const CounterComponent = ({ value }) => {
const dispatch = useDispatch()
return (
<div>
<span>{value}</span>
<button onClick={() => dispatch({ type: 'increment-counter' })}>
Increment counter
</button>
</div>
)
}
8. Viết custom hook:
Nhiều khi, ta sẽ muốn tái sử dụng các stateful logic cho các component. Ở class component, khi muốn làm việc này ta thường sử dụng higher-order-component và render-props. Custom hook cho phép làm như này mà không cần phải thêm component cho phức tạp làm gì.
Ở ví dụ các hook thông dụng trên, ta đã tạo FriendStatus component mà gọi các hook useState() và useEffect() để subscribe friend status. Ta có thể tái sử dụng logic đó ở các component khác như sau:
Đầu tiên ta bọc logic cần tái sử dụng vào một hàm useFriendStatus như sau:
import React, { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
Rồi đây ta sử dụng logic đó ở các component dễ dàng như sau:
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
Hook đơn giản như vậy thôi đấy, dùng hook sẽ khiến cho code dự án đơn giản và ít phình ra phải không nào. Trên trang chủ còn vài cách sử dụng hook phức tạp hơn nhưng mình nghĩ chừng này là đủ dùng rồi. Các bạn muốn biết thêm thì có thể tham khảo thêm ở trang chủ React và Redux. Hy vọng qua bài này các bạn có thể sử dụng hook ngay trong các component của mình. :D