Cả loạt OWL này ta luôn để framework lo DOM: đổi state, template render lại, xong. Đó là cách đúng cho 99% trường hợp. Nhưng 1% còn lại cần chạm thẳng vào node DOM thật: tự đặt con trỏ vào một ô khi mở trang, đo chiều rộng thật để tính toán, vẽ lên <canvas>, hay cắm một thư viện JS bên ngoài vào một <div>. Cho những lúc đó, OWL có useRef.
useRef + t-ref: cầu nối tới node thật
Cơ chế hai mảnh: gắn t-ref="tên" lên một phần tử trong template, và useRef("tên") trong setup() trả về một đối tượng ref. Thuộc tính .el của nó là node DOM thật. Nhưng có một điều sống còn về thời điểm: lúc setup() chạy, DOM chưa được dựng, nên .el là null. Node chỉ tồn tại sau khi component mount — tức trong onMounted trở đi:

Hình 1: useRef("oTim") gắn với <input t-ref="oTim"/>. Trong setup() ref đã tạo nhưng .el còn null — chạm DOM ở đây sẽ nổ. Đợi tới onMounted (DOM đã dựng) mới gọi this.oTim.el.focus() và đọc offsetWidth. Nút "Chọn hết chữ" gọi .el.select() — lúc người dùng bấm thì DOM chắc chắn đã có.
Chạy thật: tự focus và đo px
Mình thêm một ô tìm kiếm vào dashboard: khi mở trang nó tự nhận con trỏ, và mình đo bề rộng thật của nó bằng offsetWidth:

Hình 2: Thật cả. Ô tìm kiếm có viền focus (con trỏ tự vào khi mở trang, nhờ onMounted gọi .el.focus()). Chữ "VIP-0003" đang bôi đen vì mình đã bấm "Chọn hết chữ" (.el.select()). Và dòng cuối: "Bề rộng đo được ... 420 px" — con số offsetWidth đọc trực tiếp từ node DOM, không phải bịa. Đây là những việc state/props không làm được: chúng là đo và điều khiển DOM, không phải dữ liệu.
Khi nào dùng — và khi nào KHÔNG
Đây là chỗ dễ lạm dụng nhất của người mới. Quy tắc:
- Dùng
useRefkhi bạn cần một thao tác DOM thô mà OWL không có API khai báo:focus(),select(),scrollIntoView(), đooffsetWidth/getBoundingClientRect(), vẽ canvas, khởi tạo thư viện ngoài (chart, map...). - ĐỪNG dùng
useRefđể đọc/ghi dữ liệu. Lấy giá trị ô input thì dùngstate+t-modelhay sự kiện, không đọcref.el.value. Đổi màu, ẩn/hiện thì đổistateđể template render lại, không sờref.el.style. Chạm DOM để đổi thứ mà state quản lý là đi ngược mô hình, và OWL sẽ ghi đè thay đổi của bạn ở lần render sau.
Nói ngắn: ref là để đo và điều khiển DOM, state/props là để quản lý dữ liệu. Lẫn hai thứ là nguồn của những bug "sửa rồi lại mất".
Vài lưu ý
.ellànulltrướconMountedvànullsauonWillUnmount. Kiểmif (this.oTim.el)nếu có khả năng gọi lúc chưa mount.t-refnhận tên động:t-ref="item_#{id}"để trỏ tới phần tử trong vòng lặp — hữu ích khi cần scroll tới đúng dòng.- Ref không làm component render lại. Đổi qua
.elkhông kích hoạt render; nếu cần UI phản ứng theo, đó là việc củastate.
Ba ý mang về
useRef("tên")+t-ref="tên"cho bạn node DOM thật qua.el— để làm những thao tác DOM thô mà OWL không có API:focus,select, đooffsetWidth, vẽ canvas, cắm thư viện ngoài..elchỉ có sau khi mount — dùng trongonMountedtrở đi, không phải trongsetup()(lúc đó.ellànull). SauonWillUnmountnó lại vềnull.- Ref để đo/điều khiển DOM, state/props để quản lý dữ liệu. Đừng dùng ref đọc giá trị input hay đổi style — đó là việc của state; lẫn lộn sinh bug "sửa rồi mất".
Trong dashboard ta tính tong, diem, vang ngay trong onWillStart rồi nhét vào state. Nhưng nếu một giá trị suy ra từ state khác thì sao — có nên lưu riêng không? Phần sau nói về getter thay cho "computed": cách OWL tính giá trị dẫn xuất gọn gàng mà không cần cơ chế computed riêng như Vue.