Đây là lỗi kinh điển của người mới OWL: viết this.count++ trong một handler, bấm nút, và... số trên màn hình đứng im. Code chạy đúng, biến có tăng, nhưng giao diện không biết. Điều làm OWL "sống" — dữ liệu đổi thì màn hình tự vẽ lại — không tự nhiên có; nó đến từ một thứ: useState. Bài này đặt hai bộ đếm cạnh nhau, một dùng useState một không, rồi bấm thật để thấy khác biệt.
Một reactive, một thường
Trong setup, tôi tạo hai bộ đếm: state.dem (qua useState) và demThuong (thuộc tính thường), cùng hai nút tăng chúng:

Hình 1: Hai bộ đếm. this.state = useState({..., dem: 0}) — useState bọc object trong một proxy theo dõi mọi truy cập/thay đổi. this.demThuong = 0 là thuộc tính JavaScript thường, không được theo dõi. tangPhanUng tăng state.dem; tangThuong tăng demThuong. t-on-click gắn handler vào nút.
Đo: bấm nút thường, màn hình đứng im
Bấm nút demThuong++ ba lần. Biến demThuong trong bộ nhớ đã thành 3, nhưng màn hình:

Hình 2: Sau khi bấm demThuong++ ba lần — cả hai vẫn hiện 0. Biến demThuong đã là 3 trong bộ nhớ, nhưng vì nó không đi qua useState, OWL không biết có gì đổi nên không vẽ lại. Màn hình đứng im dù dữ liệu đã thay đổi. Đây chính là cái bẫy "số không nhúc nhích".
Bấm nút reactive: mọi thứ hiện ra
Giờ bấm state.dem++ một lần. Nó thay đổi state (được theo dõi) → OWL vẽ lại toàn component → và lần vẽ lại này lộ ra luôn giá trị demThuong đã tích luỹ:

Hình 3: Sau một lần bấm state.dem++ — state.dem thành 1, và demThuong bất ngờ nhảy lên 3. Con số 3 đó đã có từ trước (ba lần bấm trước), chỉ là bây giờ mới lộ ra vì việc render bị kích hoạt bởi thay đổi state.dem. Bài học rõ ràng: chỉ thay đổi qua useState mới khiến giao diện tự vẽ lại; thuộc tính thường có đổi cũng phải chờ một render do thứ khác gây ra mới hiện.
Vài lưu ý về reactivity
- Luôn để dữ liệu-cần-hiển-thị trong
state. Nếu một giá trị sẽ đổi và cần cập nhật UI, nó phải nằm trong objectuseState. Thuộc tính thường chỉ hợp cho thứ không ảnh hưởng giao diện (một tham chiếu service, một bộ đếm nội bộ). - Proxy theo dõi sâu.
useStatereactive cả với object/mảng lồng nhau:state.cards.push(x)haystate.user.name = "..."đều kích hoạt render. Không cần "set" đặc biệt như vài framework khác. - Fine-grained: chỉ render khi thứ ĐANG hiển thị đổi. OWL chỉ vẽ lại nếu thuộc tính bị thay đổi có được đọc trong lần render trước. Đổi một field trong state mà template không dùng thì không tốn công vẽ lại.
- Đừng thay cả object state. Gán
this.state = {...}mới là mất proxy. Sửa bên trong:this.state.x = ..., không gán lại chínhthis.state.
Ba ý mang về
useState(obj)trả về một proxy phản ứng: thay đổithis.state.xkhiến OWL tự vẽ lại component; thay đổithis.x(thuộc tính thường) thì không.- Đo thật thấy rõ: bấm nút tăng thuộc tính thường 3 lần, UI vẫn 0; chỉ khi một thay đổi
statekích hoạt render, giá trị tích luỹ mới lộ ra. - Để mọi dữ liệu-hiển-thị trong
state; proxy theo dõi cả object/mảng lồng; và đừng gán lại cảthis.statekẻo mất tính phản ứng.
Một component tự quản state của mình là đủ cho widget nhỏ. Nhưng ứng dụng thật gồm nhiều component lồng nhau, và cha cần đưa dữ liệu xuống con. Phần sau đi vào props — truyền dữ liệu xuống component con: khai báo, kiểu, và vì sao con không nên tự sửa props.