Mục lục

Headless UI & Render Props: Tách biệt Logic và Giao diện

Tại sao các thư viện như TanStack Table, Downshift lại dùng Headless Pattern? Render Props là gì? Cách viết Custom Hooks để chia sẻ logic mà không áp đặt UI.

Ngày xưa: Bootstrap (Cung cấp sẵn UI + Logic). -> Khó sửa UI. Ngày nay: Headless UI (Chỉ cung cấp Logic). -> UI tự do 100%.

1. Render Props (Function as Child)

Thay vì render JSX cụ thể, component sẽ gọi một hàm prop trả về JSX.

tsx:
// Logic di chuyển chuột
function MouseTracker({ render }) {
  const [pos, setPos] = useState({ x: 0, y: 0 });

  useEffect(() => {
    const handler = (e) => setPos({ x: e.clientX, y: e.clientY });
    window.addEventListener('mousemove', handler);
    return () => window.removeEventListener('mousemove', handler);
  }, []);

  return render(pos); // 🔥 Trao quyền hiển thị lại cho cha
}

// Usage
<MouseTracker 
  render={({ x, y }) => (
    <h1>Mouse is at ({x}, {y})</h1>
  )} 
/>

2. Custom Hooks (Modern Headless)

Render Props hơi khó đọc (Callback Hell). Custom Hook là bản nâng cấp.

tsx:
function useMouse() {
  const [pos, setPos] = useState({ x: 0, y: 0 });
  // logic...
  return pos;
}

// Usage
function App() {
  const { x, y } = useMouse();
  return <h1>({x}, {y})</h1>;
}

3. Prop Getters (Pattern siêu việt)

Ví dụ useTable của TanStack Table. Hook trả về không chỉ data (state) mà trả về cả hàm gắn props (getTableProps).

tsx:
function useToggle() {
  const [on, setOn] = useState(false);
  
  const getButtonProps = () => ({
    onClick: () => setOn(!on),
    "aria-pressed": on,
    role: "button"
  });

  return { on, getButtonProps };
}

// Usage
function App() {
  const { on, getButtonProps } = useToggle();
  
  return (
    // Spread props vào button: Tự động có onClick và aria
    <button {...getButtonProps()}>
       {on ? "ON" : "OFF"}
    </button>
  );
}

Kết luận

Khi xây dựng thư viện nội bộ (Design System):

  1. Nếu cần tái sử dụng Logic nhưng UI thay đổi liên tục -> Dùng Custom Hooks.
  2. Nếu cần tái sử dụng Cấu trúc DOM nhưng content thay đổi -> Dùng Composition.
  3. Nếu cần sự linh hoạt tối đa cho người dùng -> Headless (Prop Getters).
Quảng cáo
mdhorizontal