useState là một hook trong React, được sử dụng để khai báo và quản lý trạng thái trong function component. Nó giúp component ghi nhớ giá trị qua các lần render, và cập nhập giao diện khi giá trị đó thay đổi.
Nó làm 3 việc chính
- Tạo một vùng nhớ riêng
- Trả về giá trị hiện tại
- Trả về một hàm để giúp nó thay đổi giá trị đó, và báo cho React render lại component.
Mô tả cách hoạt động
let states = [];
let index = 0;
function useState(initialValue) {
const currentIndex = index;
// nếu state chưa tồn tại -> khởi tạo
if (states[currentIndex] === undefined) {
states[currentIndex] = initialValue;
}
// hàm cập nhật state
const setState = (newValue) => {
states[currentIndex] = newValue;
render(); // giả lập việc re-render component
};
index++;
return [states[currentIndex], setState];
}
function render() {
index = 0; // reset lại index mỗi khi render
App(); // render lại component
}
function App() {
const [count, setCount] = useState(0);
console.log('Count:', count);
return {
click: () => setCount(count + 1)
};
}
let app = App(); // lần đầu render
app.click(); // gọi setCount -> render lạiCách hoạt động
Trong React, logic phức tạp hơn mô tả trên nhiều, có thể hiểu các phần sau:
- Mỗi component có fiber node riêng
- Mỗi useState tạo ra một hook object gắn vào fiber đó
- React lưu chuỗi hook này bằng linked list
- Khi component re-render, React đi lại cùng thứ tự hook → lấy đúng state tương ứng.
setStatesẽ đưa một update vào queue và React sắp xếp render lại với giá trị mới.
Vì vậy bắt buộc phải gọi hook theo cùng thứ tự mỗi lần render không được gọi tỏng if, for, hay sau return.
Vì sao cần gọi theo thứ tự cố định
React không đặt tên cho từng state, nó chỉ ghi nhớ thứ tự mà useState được gọi trong component.
Ví dụ đúng
function Example() {
const [count, setCount] = useState(0); // Hook #1
const [name, setName] = useState('Anh'); // Hook #2
return (
<div>
<p>{name}: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}Ở trong ví dụ này, mỗi lần render, React biết
- hook #1 → count
- hook #2 → name
Mọi thứ hoạt động đúng
Ví dụ sai
function Example({ showName }) {
const [count, setCount] = useState(0); // Hook #1
if (showName) {
const [name, setName] = useState('Anh'); // Hook #2 (CHỈ khi showName === true)
}
return <p>Count: {count}</p>;
}Ở trong ví dụ này, ở lần render đầu tiên có 2 hook được render, nhưng trong lần sau, chỉ có một hook được render. Và React vẫn nghĩ rằng hook thứ 2 tồn tại và sẽ áp state cũ của name cho hook kế tiếp làm gây lỗi như
Error: Rendered fewer hooks than expected.
Vì vậy nên luôn gọi hook ở top level của component. Không được gọi trong if, for, while, sau return hoặc trong useEffect, callback.
Vì sao React không dùng tên biến để biết state nào là state nào?
Khi React render component, thực chất nó chỉ chạy function của mình, ví dụ
function Counter() {
const [count, setCount] = useState(0);
const [name, setName] = useState('Anh');
return <p>{name}: {count}</p>;
}Khi React gọi Counter() Javascript không lưu lại tên biến count hay name ở đâu cả, chúng chỉ là biến cục bộ trong function, và biến mất sau khi function chạy xong.
Và sau khi function chạy xong, React chỉ giữ lại giá trị (state) chứ không có khái niệm về tên biến. Nên React không thể dựa vào tên biến.
Javascript engine không cho phép truy cập tên biến trong hàm, nó không thể “đọc code”, “phân tích code” để biết biến nào tên gì. Và khi build bị minify các tên biến lúc này sẽ đổi hết.
Gọi theo thứ tự là phương pháp nhanh nhất và dễ tối ưu bộ nhớ nhất, vì không cần phát sinh tài nguyên cho tìm kiếm, so sánh.