Ở bài t-call ta gặp khe 0 của QWeb: nội dung con của lời gọi được "đổ" vào chỗ định sẵn của template kia. OWL có một cơ chế mạnh hơn hẳn cho component: slots. Một component con định nghĩa những chỗ trống, và mỗi nơi dùng nó lại nhét nội dung khác nhau vào — kể cả HTML phức tạp, thậm chí component khác. Bài này dựng một "khung thẻ" dùng chung cho ba nội dung, để thấy slots giải quyết bài toán tái sử dụng theo cách khác props.

props truyền dữ liệu, slots truyền cả một khối giao diện

props (đã học ở loạt OWL) truyền giá trị vào con: một chuỗi, một số, một object. Nhưng khi thứ bạn muốn truyền là cả một mảng HTML — một tiêu đề có icon, một bảng nhỏ, vài nút — thì nhồi vào props rất gượng. Slots sinh ra cho việc đó: con chừa sẵn chỗ, cha đặt nội dung vào.

Con định nghĩa chỗ bằng t-slot; cha đặt nội dung bằng cách viết ngay giữa thẻ component:

Ảnh chụp ba đoạn mã OWL nền tối. Đoạn một component con t-name quan_ca_phe KhoiThe, một div card chứa div card-header với t-slot header chú thích khe header và div card-body với t-slot default chú thích khe mặc định. Đoạn hai nơi gọi dùng KhoiThe, bên trong t-set-slot header là biểu tượng thẻ Tổng số thẻ chú thích vào khe header rồi một div display-5 t-esc state chấm tong chú thích vào khe default, rồi một KhoiThe thứ hai với t-set-slot header là ngôi sao Tổng điểm và div t-esc state chấm diem. Đoạn ba JS lớp QuanDashboard khai static components gồm KhoiThe chú thích để dùng thẻ KhoiThe hợp lệ

Hình 1: Ba mảnh. (1) Con KhoiThe khai hai khe: <t t-slot="header"/> (khe đặt tên "header") và <t t-slot="default"/> (khe mặc định). (2) Nơi gọi: nội dung trong <t t-set-slot="header">...</t> chảy vào khe "header"; mọi con trực tiếp còn lại chảy vào khe default. (3) JS phải khai static components = { KhoiThe } để dùng thẻ <KhoiThe/>.

Slot mặc định và slot đặt tên

Có hai kiểu khe:

  • Khe mặc định (default): nhận con trực tiếp của component — thứ bạn viết ngay giữa <KhoiThe>...</KhoiThe> mà không gói trong t-set-slot.
  • Khe đặt tên (header, footer...): nhận nội dung gói trong <t t-set-slot="tên">...</t>.

Nhờ vậy một khung có thể có nhiều "vùng" — tiêu đề, thân, chân — mỗi vùng nơi gọi tự quyết định.

Kết quả: ba thẻ, một khung

Mình dùng KhoiThe ba lần trong bảng điều khiển, mỗi lần bơm tiêu đề và thân khác nhau:

Ảnh chụp thật bảng điều khiển Thẻ thành viên trên Odoo 19, ba thẻ card cùng kiểu dáng nằm ngang. Thẻ một tiêu đề biểu tượng thẻ Tổng số thẻ, thân số 4 lớn màu xám và dòng thẻ đang quản lý. Thẻ hai tiêu đề ngôi sao Tổng điểm, thân số 385 lớn màu xanh lá và dòng điểm tích luỹ toàn quán. Thẻ ba tiêu đề huy chương Thẻ hạng Vàng, thân số 1 lớn màu vàng và dòng thẻ đạt hạng cao nhất

Hình 2: Ba thẻ thật, cùng một component KhoiThe. Khung (viền, header nền xám, body) viết một lần; ba nội dung ("📇 Tổng số thẻ / 4", "⭐ Tổng điểm / 385", "🥇 Thẻ hạng Vàng / 1") do ba lời gọi bơm vào slot. Số liệu là thật (orm.searchRead): 4 thẻ, 385 điểm, 1 thẻ Vàng. Đổi kiểu khung? Sửa KhoiThe, cả ba đổi theo — nhưng nội dung từng thẻ vẫn riêng.

Slots so với t-call và props

  • t-call (QWeb) tái dùng một template tĩnh, dùng chung context, body vào khe 0. Slots là bản nâng cấp cho component: nhiều khe đặt tên, và nội dung slot là của cha (không phải context của con).
  • props truyền dữ liệu; slots truyền giao diện. Một component hay dùng cả hai: props cho cấu hình (title, color), slots cho nội dung tự do.
  • Slots còn truyền được dữ liệu ngược ra (scoped slot) khi con muốn cho cha một biến để dùng trong nội dung slot — nhưng đó là mức nâng cao, phần lớn nhu cầu chỉ cần slot mặc định + vài slot tên.

Ba ý mang về

  1. Slots cho phép nơi gọi nhét cả một khối giao diện vào chỗ định sẵn của component con — giải bài toán tái sử dụng khi thứ cần truyền là HTML/nội dung, không phải một giá trị props.
  2. Con định nghĩa khe bằng <t t-slot="tên"/>; cha đặt nội dung bằng <t t-set-slot="tên">...</t> (khe đặt tên) hoặc viết trực tiếp giữa thẻ (khe default). Nhớ static components = { Con } để dùng thẻ con.
  3. props truyền dữ liệu, slots truyền giao diện — thường dùng cùng nhau. Slots là bản nâng cấp của khe 0 (t-call) dành cho component OWL, với nhiều khe đặt tên.

Ta vừa tách một "khung" ra thành component tái dùng. Phần sau đi sâu hơn vào chia nhỏ thành sub-component: khi nào nên tách một component lớn thành nhiều con, cách chúng nói chuyện qua props và callback, và lợi ích cho việc bảo trì.