Component đầu tiên bạn viết thường ổn: một cái dashboard, vài chục dòng. Rồi nó phình ra — thêm ô này, thêm nút kia, thêm logic nọ — tới lúc là một khối 300 dòng làm đủ thứ, không ai dám sửa. Cách chữa của mọi framework component (React, Vue, OWL) giống nhau: chia nhỏ thành sub-component. Bài slots tách phần giao diện; bài này tách phần dữ liệu và hành vi — mỗi con là một mảnh tự lo, giao tiếp với cha qua hai chiều rõ ràng.

Dữ liệu xuống bằng props, sự kiện lên bằng callback

Nguyên tắc vàng của luồng dữ liệu một chiều (đã gặp ở bài props): dữ liệu chảy XUỐNG từ cha tới con qua props; con không sửa props. Khi con cần báo điều gì cho cha (người dùng bấm nút, chọn dòng), nó gọi một callback mà cha truyền xuống — một hàm trong props. Cha giữ quyền quyết định, con chỉ "gõ cửa".

Mình tách ba ô số liệu của dashboard thành một sub-component TheCard:

Ảnh chụp ba đoạn mã OWL nền tối. Đoạn một lớp con TheCard kế thừa Component có static template quan_ca_phe TheCard và static props gồm nhan kiểu String, so kiểu Number, mau kiểu String là dữ liệu xuống, và onChon kiểu Function là callback báo ngược lên. Đoạn hai template con một div card hiển thị t-esc props chấm so và t-esc props chấm nhan, một button t-on-click gọi mũi tên props chấm onChon với props chấm nhan. Đoạn ba component cha khai static components gồm TheCard, method onChonCard nhận nhan gọi notification add Bạn vừa chọn ô, và template cha dùng TheCard với t-foreach state chấm items truyền nhan so mau và onChon là mũi tên gọi this chấm onChonCard

Hình 1: Ba phần. Con TheCard: khai static props với kiểu cho từng prop (nhan: String, so: Number) — OWL kiểm props lúc chạy, sai kiểu là báo lỗi ngay. onChon: Function là callback. Template con: bấm nút gọi props.onChon(props.nhan) — con không tự xử lý, nó báo lên. Cha: khai static components = { TheCard }, viết hàm onChonCard nhận sự kiện, và truyền props xuống + callback lên qua <TheCard .../>.

Chạy thật: bấm con, cha nhận

Dashboard giờ chỉ lặp <TheCard/> ba lần, mỗi lần truyền props khác nhau. Bấm nút "Chọn" ở một ô:

Ảnh chụp thật bảng điều khiển Thẻ thành viên trên Odoo 19, ba thẻ card mỗi thẻ do sub-component TheCard vẽ, thẻ một số 4 nhãn Tổng số thẻ, thẻ hai số 385 màu xanh nhãn Tổng điểm, thẻ ba số 1 màu vàng nhãn Thẻ hạng Vàng, mỗi thẻ có một nút Chọn. Góc trên bên phải hiện một toast thông báo viền xanh dương nội dung Bạn vừa chọn ô Tổng điểm sau khi bấm nút Chọn ở thẻ giữa

Hình 2: Ba TheCard thật (4/385/1), mỗi cái nhận props riêng. Bấm "Chọn" ở ô giữa → con gọi props.onChon("Tổng điểm") → hàm onChonCard của cha chạy, bật toast "Bạn vừa chọn ô: Tổng điểm". Con không biết gì về notification; nó chỉ báo "tôi được chọn", cha quyết định làm gì. Đó là ranh giới trách nhiệm rõ ràng.

Vì sao bõ công tách

  • Dễ đọc. Cha giờ là một vòng lặp ngắn; chi tiết vẽ một ô nằm gọn trong TheCard. Đọc cha thấy bố cục, đọc con thấy một ô.
  • Tái dùng. TheCard dùng được ở màn hình khác, không dính gì tới dashboard cụ thể — vì nó chỉ nhận props.
  • Dễ test và sửa. Lỗi ở cách vẽ ô? Sửa TheCard, không đụng cha. Muốn thêm ô? Thêm một phần tử vào state.items, không sửa template.
  • Ranh giới trách nhiệm. Con lo hiển thị và bắt tương tác; cha lo dữ liệu và quyết định. Không lẫn lộn.

Sub-component so với slots

Hai cách tách khác nhau, bổ sung nhau:

  • Sub-component + props/callback (bài này): tách theo dữ liệu và hành vi. Con nhận dữ liệu cụ thể, báo sự kiện cụ thể. Dùng khi các mảnh có cùng cấu trúc, khác dữ liệu (danh sách ô, dòng bảng).
  • Slots (bài trước): tách theo khung và nội dung. Con chừa chỗ, cha nhét giao diện tuỳ ý. Dùng khi cái khung cố định nhưng ruột mỗi nơi một khác.

Thực tế hay dùng cả hai: một sub-component nhận props và có slot.

Ba ý mang về

  1. Chia component lớn thành sub-component nhỏ, mỗi con một trách nhiệm — dễ đọc, tái dùng, test và sửa. Cha khai static components = { Con } để dùng <Con/>.
  2. Dữ liệu chảy XUỐNG bằng props (khai static props với kiểu để OWL kiểm), sự kiện dội LÊN bằng callback prop (onXxx: Function, con gọi props.onXxx(payload)). Con không sửa props, không tự quyết — nó báo, cha quyết.
  3. Sub-component tách theo dữ liệu/hành vi; slots tách theo khung/nội dung — hai cách bổ sung nhau, hay dùng chung.

Trong TheCard ta vẽ HTML và để OWL lo DOM. Nhưng đôi khi bạn cần chạm thẳng vào một phần tử DOM thật — lấy chiều cao, focus một ô input, vẽ lên canvas. Phần sau nói về useRef: cách OWL cho bạn tham chiếu tới một node DOM thật một cách an toàn.