Bạn có một mảng điểm trong state, và cần hiển thị tổng, trung bình, cao nhất. Bản năng đầu tiên: tính ba số đó, lưu vào state.tong, state.trungBinh, state.caoNhat. Sai lầm kinh điển — vì giờ bạn có bốn nguồn dữ liệu phải giữ đồng bộ tay: mỗi lần mảng điểm đổi, phải nhớ cập nhật cả ba, quên một chỗ là số lệch. Vue giải quyết bằng computed, React bằng useMemo. OWL thì đơn giản hơn cả hai: chỉ cần một getter JavaScript.
Giá trị dẫn xuất là getter, không phải state
Nguyên tắc: thứ suy ra được từ dữ liệu khác thì đừng lưu — tính lại khi cần. Trong OWL, "tính lại khi cần" nghĩa là một getter (get tenGetter() { ... }). Getter đọc state, trả về giá trị, và vì template gọi nó ở mỗi lần render, nó luôn khớp với state hiện tại:

Hình 1: state.diems là nguồn sự thật duy nhất. tongDiem, trungBinh, caoNhat là getter — không lưu ở đâu cả, tính từ diems mỗi lần cần. Getter gọi getter được (trungBinh dùng this.tongDiem). Template đọc getter y như một thuộc tính: <t t-esc="tongDiem"/>. Muốn đổi số? Chỉ đổi state.diems trong themDiem — ba getter tự ra số mới.
Thấy tận mắt: đổi state, cả ba tự cập nhật
Ban đầu ba getter tính từ điểm thật của 4 thẻ (150+85+0+150):

Hình 2: Trước — Tổng 385, Trung bình 96 (round(385/4)), Cao nhất 150. Cả ba là getter, không có trong state.
Bấm nút "+10 điểm ảo cho mỗi thẻ" — hàm chỉ làm một việc: state.diems = diems.map(d => d + 10). Không đụng tới tổng/trung bình/cao nhất:

Hình 3: Sau — Tổng 425, Trung bình 106, Cao nhất 160. Mình không viết dòng nào cập nhật ba số này; chúng tự đổi vì getter đọc state.diems (reactive), và OWL render lại khi state đổi nên getter chạy lại với dữ liệu mới. Nếu lưu ba số vào state riêng, mình đã phải nhớ cập nhật cả ba trong themDiem — và một ngày nào đó sẽ quên.
Vì sao OWL không cần "computed"
Vue có computed với cache (chỉ tính lại khi phụ thuộc đổi). OWL bỏ hẳn cơ chế đó vì mô hình render của nó đã đủ: component chỉ render lại khi state phản ứng đổi, nên getter cũng chỉ chạy lại vào đúng những lúc đó — không phải mỗi mili-giây. Đơn giản hơn, ít khái niệm hơn.
Đổi lại, getter không được cache trong một lần render: nếu template gọi tongDiem ba lần, nó chạy ba lần. Với phép tính nhẹ (cộng một mảng) thì không sao. Nhưng đây dẫn tới các lưu ý:
- Getter phải nhẹ và thuần (pure). Chỉ đọc và tính, không side-effect (đừng gọi ORM, đừng đổi state, đừng log rối) — nó chạy mỗi lần render, khó lường.
- Việc nặng thì tính một lần, lưu vào state. Nếu phép tính thực sự tốn kém (lọc/sắp một mảng lớn), tính khi dữ liệu đổi rồi lưu — hoặc dùng một cơ chế memo — thay vì getter chạy đi chạy lại.
- Getter đọc state phản ứng mới tự cập nhật. Đọc một biến thường (
this.xkhông nằm tronguseState) thì đổi nó không render lại.
Ba ý mang về
- Giá trị dẫn xuất từ state thì dùng getter, đừng lưu riêng. Lưu bản sao là tạo ra nhiều nguồn sự thật phải đồng bộ tay — nguồn của bug "số lệch". Getter = một nguồn sự thật.
- OWL không có
computed/useMemoriêng — chỉ cầnget tenGetter() { return ... }, đọc trong template như thuộc tính. Nó chạy lại mỗi lần render, mà render chỉ khi state phản ứng đổi, nên vừa gọn vừa đủ nhanh. - Getter phải nhẹ và thuần (không side-effect, không việc nặng). Đọc
statereactive để tự cập nhật; việc thật sự tốn kém thì tính một lần và lưu.
Cho tới giờ mọi thứ chạy trơn. Nhưng nếu một component ném lỗi giữa lúc render thì sao — cả trang trắng? Phần sau nói về xử lý lỗi trong component: onError/error boundary để một component hỏng không kéo sập toàn bộ giao diện.