Ant Design (antd) là một trong những thư viện UI phổ biến nhất cho ứng dụng React, được phát triển bởi Alibaba và cộng đồng. Với hơn 80.000 sao trên GitHub, antd cung cấp một hệ thống thiết kế toàn diện với nhiều component được đóng gói sẵn, giúp tăng tốc quá trình phát triển giao diện người dùng.
Tuy nhiên, khi các dự án có nhu cầu thiết kế tùy chỉnh cao, đặc biệt là khi cần phá vỡ các quy tắc thiết kế chuẩn của Ant Design, nhà phát triển có thể gặp nhiều thách thức. Báo cáo này phân tích chi tiết các trường hợp mà Ant Design gặp khó khăn trong việc đáp ứng yêu cầu thiết kế tùy chỉnh, kèm theo ví dụ cụ thể và các giải pháp thay thế tiềm năng.
- Giới hạn về theme và styling
- Hạn chế về cấu trúc và hành vi component
- Thách thức về thiết kế responsive
- Vấn đề về hiệu suất
- Giới hạn về animation và transition
- Tùy chỉnh về accessibility
- Các component đặc biệt
- Kết luận và khuyến nghị
Vấn đề: Ant Design sử dụng cấu trúc CSS phức tạp với nhiều lớp lồng nhau, làm cho việc ghi đè kiểu khó khăn mà không ảnh hưởng đến các thành phần khác.
Ví dụ: Tùy chỉnh Button trong Dropdown
// Khó tùy chỉnh vì cấu trúc lồng nhau phức tạp
<Dropdown
overlay={
<Menu>
<Menu.Item>
<Button type="primary">Action Button</Button> {/* Khó styling riêng cho button này */}
</Menu.Item>
</Menu>
}
>
<Button>Dropdown</Button>
</Dropdown>
Khi bạn muốn tùy chỉnh button trong Menu.Item, bạn phải đối mặt với các selector CSS phức tạp như:
.ant-dropdown .ant-menu .ant-menu-item .ant-btn {
/* Các tùy chỉnh của bạn */
}
Selector này rất dễ gây xung đột với các thành phần khác trong ứng dụng.
Vấn đề: Nhiều component của antd có cấu trúc DOM phức tạp và được đóng gói chặt chẽ, khó tùy chỉnh các phần tử bên trong mà không ảnh hưởng đến chức năng.
Ví dụ: Tùy chỉnh Table columns với content phức tạp
<Table
columns={[
{
title: 'Name',
dataIndex: 'name',
render: (text, record) => (
// Styling cho các phần tử trong ô rất khó
<div className="custom-cell">
<Avatar src={record.avatar} />
<span>{text}</span>
{record.status === 'online' && <Badge status="success" />}
</div>
)
}
]}
dataSource={data}
/>
Khi cần tùy chỉnh hover state hoặc làm highlight một phần cụ thể của nội dung cell, bạn phải đối mặt với các vấn đề phức tạp về CSS và DOM manipulation.
Vấn đề: Hệ thống theme của antd chủ yếu dựa trên màu đơn, khó hỗ trợ các gradient phức tạp hoặc các hiệu ứng màu sắc tiên tiến.
Ví dụ: Button với gradient phức tạp
/* Gradient này rất khó tích hợp vào hệ thống theme của antd */
.custom-button {
background: linear-gradient(90deg, #FF8A00 0%, #DA1B60 50%, #8A16C1 100%);
color: white;
border: none;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
}
// Khi sử dụng với component Button
<Button className="custom-button">
Gradient Button
</Button>
Vấn đề là khi hover hoặc focus, Ant Design sẽ áp dụng các style mặc định của nó, có thể ghi đè hoặc xung đột với gradient của bạn. Các trạng thái hover/focus/active của Button sẽ làm mất hiệu ứng gradient hoặc gây ra các hiệu ứng không mong muốn.
Vấn đề: Các component như Modal, Drawer khó thay đổi cấu trúc cơ bản (ví dụ: thay đổi vị trí nút đóng, thay đổi cách hiển thị overlay).
Ví dụ: Tùy chỉnh Modal với footer bên trái và nút tùy chỉnh ở góc trên bên trái
// Ant Design không cung cấp API để dễ dàng thay đổi vị trí footer và thêm nút tùy chỉnh
<Modal
title="Custom Modal"
visible={visible}
onCancel={handleCancel}
footer={[
<Button key="back" onClick={handleCancel}>
Hủy bỏ
</Button>,
<Button key="submit" type="primary" onClick={handleOk}>
Xác nhận
</Button>,
]}
// Không có prop cho footer trái và nút góc trái
>
Nội dung modal
</Modal>
Để đặt footer sang trái, bạn cần phải ghi đè CSS hoặc tạo wrapper component phức tạp, cả hai cách đều dễ bị ảnh hưởng khi antd cập nhật.
Vấn đề: Form của antd tuân theo bố cục tiêu chuẩn, gặp khó khăn khi cần thiết kế form với bố cục đặc biệt.
Ví dụ: Form với layout phức tạp không chuẩn
// Form với các trường xếp theo kiểu lưới không đều
<Form>
<Row>
<Col span={6}><Form.Item name="field1" label="Field 1" /></Col>
<Col span={10}><Form.Item name="field2" label="Field 2" /></Col>
<Col span={8}>
<Row>
<Col span={12}><Form.Item name="field3" label="Field 3" /></Col>
<Col span={12}><Form.Item name="field4" label="Field 4" /></Col>
</Row>
</Col>
</Row>
{/* Form với các section xen kẽ và nhóm field phức tạp */}
<Divider />
<section className="special-section">
<h3>Thông tin đặc biệt</h3>
<Row gutter={[16, 24]}>
<Col span={12}>
<Form.Item
name="specialField"
label="Trường đặc biệt"
// Các validation rule phức tạp
>
<Input.TextArea rows={4} />
</Form.Item>
</Col>
<Col span={12}>
{/* Input group lồng nhau phức tạp */}
<div className="custom-input-group">
<Form.Item name="subField1" label="Sub Field 1" noStyle>
<Input style={{ width: '50%' }} />
</Form.Item>
<Form.Item name="subField2" label="Sub Field 2" noStyle>
<Input style={{ width: '50%' }} />
</Form.Item>
</div>
</Col>
</Row>
</section>
</Form>
Form của antd được thiết kế để hoạt động tốt với các layout chuẩn (horizontal, vertical, inline). Khi bạn cần các bố cục phức tạp hơn với nhiều section khác nhau, form lồng nhau, hoặc các nhóm trường hình thức đặc biệt, bạn sẽ gặp khó khăn trong việc duy trì cả styling và functionality (như validation, field dependencies).
Vấn đề: Tùy chỉnh Table cho các cấu trúc hiển thị phức tạp (như bảng lồng nhau, nhóm cột động, hoặc các cell có nội dung đa dạng) rất khó thực hiện.
Ví dụ: Bảng với cấu trúc lồng nhau và cột động
const dynamicColumns = generateDynamicColumns(); // Columns được tạo động từ dữ liệu
// Table với nested structure và dynamic columns
<Table
columns={[
{
title: 'Thông tin',
children: [
{ title: 'Tên', dataIndex: 'name', key: 'name' },
{ title: 'Tuổi', dataIndex: 'age', key: 'age' },
]
},
{
title: 'Metrics',
children: dynamicColumns // Số lượng cột con thay đổi theo dữ liệu
},
{
title: 'Actions',
render: (_, record) => (
// Custom complex cell với nhiều tương tác
<div className="action-cell">
<Button type="link" onClick={() => showDetails(record)}>Chi tiết</Button>
<Dropdown overlay={<Menu>...</Menu>}>
<Button>Thêm</Button>
</Dropdown>
<Progress percent={record.progress} size="small" />
</div>
)
}
]}
dataSource={data}
expandable={{
expandedRowRender: record => (
// Nested table trong expanded row
<Table
columns={nestedColumns}
dataSource={record.children}
pagination={false}
/>
)
}}
/>
Table của antd có thể xử lý các cấu trúc cơ bản, nhưng khi cần các cấu trúc phức tạp như cột động dựa trên dữ liệu, cột lồng nhau nhiều cấp, hoặc các expanded row với nội dung phức tạp, bạn sẽ gặp nhiều khó khăn trong cả việc styling và xử lý logic.
Vấn đề: antd sử dụng các breakpoint cố định, khó điều chỉnh các breakpoint tùy chỉnh cho thiết kế responsive phức tạp.
Ví dụ: Cần breakpoint tùy chỉnh cho layout đặc biệt
// Antd sử dụng breakpoints cố định: xs, sm, md, lg, xl, xxl
<Row>
<Col xs={24} sm={12} md={8} lg={6} xl={4}>
Cột 1
</Col>
<Col xs={24} sm={12} md={8} lg={6} xl={4}>
Cột 2
</Col>
{/* Không có cách đơn giản để thêm breakpoint tùy chỉnh như 920px */}
</Row>
Nếu thiết kế của bạn yêu cầu breakpoint ở 920px (không phải một trong các breakpoint mặc định của antd), bạn phải xử lý thông qua CSS media queries bên ngoài hệ thống Grid của antd, dẫn đến mã không nhất quán và khó bảo trì.
Vấn đề: Hệ thống lưới của antd có thể không đủ linh hoạt cho các bố cục grid phức tạp, đặc biệt là khi cần các tỷ lệ cột đặc biệt.
Ví dụ: Grid với tỷ lệ cột không chuẩn
// Grid 14-column với các tỷ lệ đặc biệt (5:9)
// Antd chỉ hỗ trợ hệ thống 24 cột mặc định
<Row>
<Col span={10}>Column 1</Col> {/* Thực tế muốn 5/14 của width */}
<Col span={14}>Column 2</Col> {/* Thực tế muốn 9/14 của width */}
</Row>
Antd sử dụng hệ thống 24 cột, nhưng nếu thiết kế của bạn yêu cầu hệ thống grid khác (ví dụ: 14 cột), bạn phải chuyển đổi các giá trị, dẫn đến các số lẻ và không trực quan.
Vấn đề: Các component như Menu, Layout khó tùy chỉnh hành vi responsive khi cần những thay đổi đáng kể giữa các kích thước màn hình.
Ví dụ: Menu responsive phức tạp
// Menu cần thay đổi hoàn toàn cấu trúc và hành vi dựa trên kích thước màn hình
const [screenSize, setScreenSize] = useState(window.innerWidth);
const [menuMode, setMenuMode] = useState('horizontal');
useEffect(() => {
// Logic phức tạp để xác định menu mode
if (screenSize > 1024) {
setMenuMode('horizontal');
} else if (screenSize > 768) {
setMenuMode('vertical');
} else {
// Trên mobile cần drawer với cấu trúc khác
setMenuMode('inline');
}
}, [screenSize]);
return (
<>
{screenSize <= 768 ? (
<Drawer visible={menuVisible} onClose={toggleMenu}>
<Menu mode="inline">
{/* Cấu trúc menu mobile */}
<Menu.Item key="home">Trang chủ - Mobile</Menu.Item>
<Menu.Item key="special">Mục đặc biệt chỉ hiện trên mobile</Menu.Item>
</Menu>
</Drawer>
) : (
<Menu mode={menuMode}>
{/* Cấu trúc menu desktop/tablet */}
<Menu.Item key="home">Trang chủ</Menu.Item>
<SubMenu key="products" title="Sản phẩm">
{/* Submenu chỉ hiển thị trên desktop/tablet */}
</SubMenu>
</Menu>
)}
{/* Button hiển thị menu trên mobile */}
{screenSize <= 768 && (
<Button onClick={toggleMenu}>
<MenuOutlined />
</Button>
)}
</>
);
Component Menu của antd hỗ trợ các mode cơ bản (horizontal, vertical, inline), nhưng khi cần các thay đổi phức tạp giữa các kích thước màn hình (như thay đổi hoàn toàn cấu trúc menu hoặc thay đổi các mục menu dựa trên kích thước màn hình), bạn phải viết nhiều mã tùy chỉnh và điều kiện, làm cho code phức tạp và khó bảo trì.
Vấn đề: Khi tùy chỉnh nhiều, việc tối ưu bundle size trở nên khó khăn, dẫn đến ứng dụng nặng hơn.
Ví dụ: Import nhiều component và tùy chỉnh chúng
// Mỗi khi tùy chỉnh, bạn thường phải import nhiều component
import { Button, DatePicker, Form, Input, Select, Table, Tabs, Modal, Drawer, Menu } from 'antd';
import { UserOutlined, HomeOutlined, SettingOutlined, ... } from '@ant-design/icons';
// CSS tùy chỉnh cho mỗi component
import './CustomButton.less';
import './CustomTable.less';
import './CustomForm.less';
// ...nhiều file CSS tùy chỉnh khác
Ngay cả với tree-shaking, việc tùy chỉnh nhiều component thường dẫn đến bundle size lớn hơn vì bạn cần import nhiều component và viết nhiều mã CSS tùy chỉnh.
Vấn đề: Một số component phức tạp như Table, Tree re-render toàn bộ khi chỉ cần thay đổi một phần, gây vấn đề hiệu suất khi tùy chỉnh.
Ví dụ: Re-render không cần thiết với Table lớn
// Re-render problem với Table customization
function ComplexTable() {
const [data, setData] = useState(largeDataset); // 1000+ rows
const [selectedRow, setSelectedRow] = useState(null);
// Mỗi lần selectedRow thay đổi, toàn bộ bảng re-render
return (
<Table
dataSource={data}
rowClassName={record => record.id === selectedRow ? 'selected-row' : ''}
columns={[
{
title: 'Name',
dataIndex: 'name',
render: (text, record) => (
// Complex custom cell với nhiều thành phần
<div className="custom-cell">
<Avatar src={record.avatar} />
<span>{text}</span>
{record.id === selectedRow && (
<Badge status="processing" />
)}
</div>
)
},
// ... nhiều cột khác
]}
onRow={record => ({
onClick: () => setSelectedRow(record.id)
})}
/>
);
}
Khi người dùng click vào một hàng, selectedRow
thay đổi, khiến toàn bộ component re-render. Table của antd không tối ưu để chỉ re-render các cell bị ảnh hưởng, dẫn đến tình trạng giật lag với dataset lớn.
Vấn đề: Khó tùy chỉnh các triển khai virtual scroll cho danh sách lớn khi cần thiết kế đặc biệt.
Ví dụ: Virtual scroll với item phức tạp
// List với virtual scroll và item design phức tạp
<List
itemLayout="vertical"
dataSource={largeDataset} // 10,000+ items
renderItem={item => (
<List.Item>
<div className="custom-item-with-animation">
<div className="item-header">
<h3>{item.title}</h3>
<Badge count={item.notificationCount} />
</div>
<div className="item-content">
{/* Nội dung phức tạp với nhiều thành phần */}
<Descriptions layout="vertical">
<Descriptions.Item label="Field 1">{item.field1}</Descriptions.Item>
<Descriptions.Item label="Field 2">{item.field2}</Descriptions.Item>
</Descriptions>
<Progress percent={item.progress} />
</div>
{/* Custom animations khi item xuất hiện trong viewport */}
</div>
</List.Item>
)}
pagination={false}
// Virtual scroll khó tùy chỉnh với item height không cố định
/>
Antd's List không có tích hợp tốt cho virtual scrolling với các item có chiều cao động hoặc các item có thiết kế phức tạp với animation. Để triển khai virtual scroll hiệu quả trong trường hợp này, bạn thường phải sử dụng thư viện bên thứ ba như react-virtualized
hoặc react-window
và tích hợp thủ công.
Vấn đề: antd có các animation mặc định đơn giản, khó thực hiện các animation phức tạp hoặc các hiệu ứng chuyển tiếp đặc biệt.
Ví dụ: Carousel với hiệu ứng 3D
// Carousel với hiệu ứng 3D cube không được hỗ trợ sẵn
<Carousel effect="slide"> {/* Không có effect="3d-cube" */}
<div>
<h3>Slide 1</h3>
<img src="image1.jpg" alt="Slide 1" />
</div>
<div>
<h3>Slide 2</h3>
<img src="image2.jpg" alt="Slide 2" />
</div>
</Carousel>
Antd Carousel chỉ hỗ trợ các hiệu ứng cơ bản như "fade" và "slide". Khi cần các hiệu ứng phức tạp hơn như 3D cube, card flip, hoặc các hiệu ứng tùy chỉnh, bạn phải dùng thư viện khác hoặc viết CSS/JS phức tạp để override hành vi mặc định.
Vấn đề: Khó thay đổi các đường cong timing function của animation mà không ảnh hưởng đến chức năng component.
Ví dụ: Drawer với animation tùy chỉnh
// Drawer với animation tùy chỉnh
<Drawer
visible={visible}
onClose={onClose}
// Không có prop để tùy chỉnh animation timing function
>
Nội dung drawer
</Drawer>
/* Attempt to customize drawer animation */
.ant-drawer.custom-drawer .ant-drawer-content-wrapper {
transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) !important;
}
Khi bạn muốn thay đổi timing function của animation Drawer từ linear thành elastic, bạn phải override CSS mặc định, có thể gây ra các vấn đề không mong muốn với tương tác và chức năng.
Vấn đề: Hiệu ứng như parallax, morph transitions, 3D transformations khó tích hợp với các component antd.
Ví dụ: Card với hiệu ứng parallax và 3D
// Card với hiệu ứng parallax và 3D tilt
<Card
className="parallax-card"
cover={<img alt="example" src="https://example.com/image.jpg" />}
// Không có prop cho hiệu ứng đặc biệt
>
<Card.Meta
title="Card title"
description="This is the description"
/>
</Card>
// Custom JS cần thiết cho hiệu ứng 3D tilt
useEffect(() => {
const card = document.querySelector('.parallax-card');
const handleMouseMove = (e) => {
const rect = card.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const centerX = rect.width / 2;
const centerY = rect.height / 2;
const rotateX = (y - centerY) / 10;
const rotateY = (centerX - x) / 10;
card.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
};
card.addEventListener('mousemove', handleMouseMove);
card.addEventListener('mouseleave', () => {
card.style.transform = 'perspective(1000px) rotateX(0) rotateY(0)';
});
return () => {
card.removeEventListener('mousemove', handleMouseMove);
card.removeEventListener('mouseleave', () => {});
};
}, []);
Để thêm hiệu ứng 3D tilt vào Card, bạn phải viết mã JavaScript tùy chỉnh và CSS phức tạp. Antd không cung cấp API cho các hiệu ứng nâng cao này, và tích hợp chúng có thể gây xung đột với các animation mặc định.
Vấn đề: Khó tùy chỉnh thông báo và hành vi của trình đọc màn hình cho các trường hợp đặc biệt.
Ví dụ: Form với các thông báo accessibility tùy chỉnh
// Form với các thông báo accessibility tùy chỉnh
<Form>
<Form.Item
label="Username"
name="username"
// Khó tùy chỉnh thông báo accessibility khi validation thất bại
>
<Input />
</Form.Item>
<Form.Item
label="Custom Field"
name="customField"
// Cần thông báo custom cho screen reader khi field thay đổi
>
<Select>
<Select.Option value="option1">Option 1</Select.Option>
<Select.Option value="option2">Option 2</Select.Option>
</Select>
</Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form>
Antd không cung cấp API đầy đủ để tùy chỉnh các thông báo ARIA và hành vi của trình đọc màn hình cho các tình huống phức tạp, như thông báo tùy chỉnh khi validation thất bại hoặc khi dữ liệu form thay đổi theo cách đặc biệt.
Vấn đề: Kiểm soát thứ tự tab phức tạp trong các form hoặc layout tùy chỉnh có thể gặp khó khăn.
Ví dụ: Form với thứ tự tab logic khác với thứ tự hiển thị
// Form với thứ tự tab khác thứ tự hiển thị trực quan
<Form>
<div className="form-section">
<Form.Item name="field1" label="Field 1" tabIndex={3} />
<Form.Item name="field2" label="Field 2" tabIndex={1} />
<Form.Item name="field3" label="Field 3" tabIndex={2} />
</div>
<div className="form-section">
{/* Form phức tạp với nhiều section và logic tabbing tùy chỉnh */}
<Form.Item name="field4" label="Field 4" tabIndex={6} />
<Form.Item name="field5" label="Field 5" tabIndex={4} />
<Form.Item name="field6" label="Field 6" tabIndex={5} />
</div>
</Form>
Mặc dù bạn có thể đặt thuộc tính tabIndex cho các Form.Item, việc quản lý thứ tự tab phức tạp trên toàn bộ ứng dụng, đặc biệt là khi có nhiều phần của form với các logic tab phụ thuộc vào ngữ cảnh, rất khó khăn với API của antd.
Vấn đề: Khó tùy chỉnh các trạng thái focus, hover tuân thủ WCAG mà không ảnh hưởng đến hành vi mặc định.
Ví dụ: Button với focus state đặc biệt
// Button với focus state đặc biệt (glow effect + outline tùy chỉnh)
<Button className="custom-focus-button">
Click me
</Button>
/* CSS for custom focus state */
.custom-focus-button:focus {
box-shadow: 0 0 0 3px rgba(24, 144, 255, 0.5);
outline: none;
}
/* Nếu bạn thay đổi outline, bạn phải thay thế bằng giải pháp khác cho a11y */
.custom-focus-button:focus-visible {
outline: 2px solid #1890ff;
outline-offset: 2px;
}
Khi tùy chỉnh trạng thái focus, bạn phải đảm bảo tuân thủ các tiêu chuẩn WCAG về độ tương phản và khả năng hiển thị, đồng thời không làm mất các chức năng a11y quan trọng. Antd không cung cấp API rõ ràng để tùy chỉnh những trạng thái này mà vẫn duy trì khả năng tiếp cận.
Vấn đề: antd không cung cấp giải pháp tích hợp cho các biểu đồ tùy chỉnh cao.
Ví dụ: Biểu đồ tương tác phức tạp
// Cần tích hợp thư viện biểu đồ riêng biệt
import { Line, Bar, Pie } from '@ant-design/charts'; // Thư viện riêng biệt, không phải core antd
function ComplexDashboard() {
return (
<div className="dashboard">
<Line
data={lineData}
xField="date"
yField="value"
// Các tùy chỉnh phức tạp
/>
<Bar
data={barData}
xField="category"
yField="value"
// Tích hợp với UI antd có thể phức tạp
/>
{/* Tích hợp với component khác của antd có thể khó khăn */}
<Card>
<Pie
data={pieData}
angleField="value"
colorField="type"
/>
</Card>
</div>
);
}
Antd core không có component biểu đồ tích hợp. Mặc dù có @ant-design/charts
, nhưng đây là thư viện riêng biệt và có thể gặp khó khăn khi tích hợp liền mạch với các component core và theme của antd.
Vấn đề: Không có hỗ trợ tích hợp cho bản đồ, heatmaps và các visualizations phức tạp khác.
Ví dụ: Tích hợp bản đồ với giao diện antd
// Cần tích hợp thư viện bản đồ riêng biệt
import { Map, Marker } from 'react-map-gl';
import { Card, Select, Button } from 'antd';
function LocationSelector() {
return (
<Card title="Chọn địa điểm">
<div className="map-container">
<Map
initialViewState={{
longitude: 105.85,
latitude: 21.03,
zoom: 12
}}
style={{width: '100%', height: 400}}
mapStyle="mapbox://styles/mapbox/streets-v11"
>
<Marker longitude={105.85} latitude={21.03} />
{/* Custom markers, layers, etc. */}
</Map>
</div>
<div className="location-controls">
<Select
placeholder="Chọn khu vực"
options={areaOptions}
style={{ width: '100%', marginBottom: 16 }}
/>
<Button type="primary">Xác nhận địa điểm</Button>
</div>
</Card>
);
}
Tích hợp các thư viện bản đồ như Mapbox, Google Maps hoặc Leaflet với antd đòi hỏi nhiều công sức để đảm bảo phong cách nhất quán và tương tác liền mạch giữa các control bản đồ và component antd.
Vấn đề: Không có component tích hợp cho trình soạn thảo văn bản phong phú.
Ví dụ: Tích hợp rich text editor với form antd
// Cần tích hợp thư viện editor riêng biệt
import { Editor } from 'draft-js';
import { Form, Button, Card } from 'antd';
import 'draft-js/dist/Draft.css';
function BlogPostForm() {
const [editorState, setEditorState] = useState(() => EditorState.createEmpty());
const handleSubmit = (values) => {
// Chuyển đổi nội dung editor thành HTML/JSON
const contentState = editorState.getCurrentContent();
const contentHTML = stateToHTML(contentState);
// Gửi form với dữ liệu từ cả antd Form và editor
const formData = {
...values,
content: contentHTML
};
console.log(formData);
};
return (
<Form onFinish={handleSubmit}>
<Form.Item name="title" label="Tiêu đề" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item label="Nội dung" required>
<Card bordered={false} className="editor-card">
<Editor
editorState={editorState}
onChange={setEditorState}
// Khó styling để match với antd design
/>
</Card>
{/* Antd Form không tích hợp trực tiếp với Draft.js */}
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
Đăng bài
</Button>
</Form.Item>
</Form>
);
}
Tích hợp rich text editor như Draft.js, Slate, hoặc Quill với Form của antd yêu cầu nhiều mã tùy chỉnh để xử lý validation, dữ liệu form, và styling nhất quán.
Vấn đề: Thiếu các component cho trình phát media tùy chỉnh cao.
Ví dụ: Tích hợp video player với giao diện antd
// Cần tích hợp thư viện media player riêng biệt
import ReactPlayer from 'react-player';
import { Card, Slider, Button, Space } from 'antd';
import { PlayCircleOutlined, PauseCircleOutlined } from '@ant-design/icons';
function CustomVideoPlayer() {
const [playing, setPlaying] = useState(false);
const [progress, setProgress] = useState(0);
const playerRef = useRef(null);
const handleProgress = (state) => {
setProgress(state.played * 100);
};
const handleSeek = (value) => {
setProgress(value);
playerRef.current.seekTo(value / 100);
};
return (
<Card>
<div className="player-container">
<ReactPlayer
ref={playerRef}
url="https://example.com/video.mp4"
playing={playing}
width="100%"
height="auto"
onProgress={handleProgress}
// Styling không match với antd design
/>
</div>
<div className="player-controls">
<Space>
<Button
type="text"
icon={playing ? <PauseCircleOutlined /> : <PlayCircleOutlined />}
onClick={() => setPlaying(!playing)}
/>
<Slider
value={progress}
onChange={handleSeek}
style={{ width: 300, marginLeft: 16 }}
/>
</Space>
</div>
</Card>
);
}
Tích hợp các trình phát media như video player hoặc audio player với giao diện antd yêu cầu nhiều công sức để đảm bảo điều khiển player khớp với design system, và xử lý các tương tác phức tạp như timeline, subtitles, hoặc các tùy chọn chất lượng.
Ant Design là một thư viện UI mạnh mẽ cho React, nhưng gặp hạn chế trong các trường hợp:
- Yêu cầu thiết kế tùy chỉnh cao với nhiều thay đổi từ design system mặc định
- Cấu trúc component phức tạp với nhiều lớp lồng nhau
- Responsive design với các breakpoint và hành vi tùy chỉnh
- Hiệu suất với dataset lớn và tương tác phức tạp
- Animation và hiệu ứng nâng cao
- Accessibility tùy chỉnh cao
- Các component đặc biệt như biểu đồ, bản đồ, rich text editor
Khi gặp các hạn chế của Ant Design, có thể xem xét các giải pháp sau:
-
Kết hợp với thư viện CSS-in-JS
- Sử dụng styled-components hoặc emotion để tùy chỉnh style mà không ảnh hưởng đến cấu trúc component
- Ví dụ:
const StyledButton = styled(Button)
... với nhiều tùy chỉnh CSS
-
Tạo component wrapper
- Đóng gói component antd trong component tùy chỉnh để thêm chức năng hoặc style
- Giữ API tương thích với antd nhưng thêm các tùy chỉnh cần thiết
-
Sử dụng thư viện chuyên biệt cho các component đặc biệt
- Biểu đồ: recharts, visx, echarts, d3
- Map: react-map-gl, google-map-react, react-leaflet
- Rich text: Draft.js, Slate, Quill
- Media: react-player, video.js
-
Xây dựng component từ đầu cho các trường hợp đặc biệt
- Một số trường hợp, xây dựng component từ đầu có thể hiệu quả hơn việc tùy chỉnh component antd
- Kết hợp các primitive component của antd như Button, Input với cấu trúc tùy chỉnh
-
Sử dụng kết hợp antd với thư viện khác
- Headless UI libraries (radix-ui, react-aria) cho accessibility tốt hơn
- Component animation (framer-motion, react-spring) cho các hiệu ứng nâng cao
-
Hiểu rõ hệ thống thiết kế của bạn trước khi chọn thư viện
- Đánh giá xem thiết kế của bạn có phù hợp với design system của Ant Design không
- Xác định các điểm cần tùy chỉnh từ sớm
-
Xây dựng component library riêng dựa trên antd
- Tạo các component wrapper để đồng nhất hóa các tùy chỉnh trong toàn bộ dự án
- Tạo tài liệu rõ ràng về các component tùy chỉnh
-
Kết hợp các cách tiếp cận
- Sử dụng antd cho các component cơ bản
- Sử dụng thư viện chuyên biệt cho các component phức tạp
- Xây dựng component tùy chỉnh khi cần thiết
-
Tối ưu hóa hiệu suất từ đầu
- Sử dụng React.memo, useMemo, và useCallback cho các component phức tạp
- Tránh re-render không cần thiết bằng cách tái cấu trúc component tree
-
Thiết lập quy trình kiểm tra a11y
- Sử dụng các công cụ như axe, lighthouse để đảm bảo accessibility
- Kiểm tra với screen reader và keyboard navigation
Bằng cách kết hợp Ant Design với các giải pháp tùy chỉnh phù hợp, bạn có thể tận dụng sức mạnh của thư viện này đồng thời vượt qua các hạn chế của nó để đáp ứng các yêu cầu thiết kế tùy chỉnh cao.