Hướng Dẫn Sử Dụng Controller, useController() và register() Trong react-hook-form
Khi tôi bắt gặp react-hook-form qua lệnh yarn shadcn@latest add form, tôi đã thấy nhiều thành phần như <Form />, <FormField />, <FormControl />, <FormLabel />, và <FormMessage />. Lúc đầu, tôi không hiểu rõ cách hoạt động của chúng. Nếu bạn đang gặp khó khăn với Radix Primitive hoặc bất kỳ thư viện headless nào và muốn kết hợp cùng với react-hook-form, bài viết này sẽ giúp bạn hiểu rõ hơn.
Thành Phần Điều Khiển: Controlled Component và Uncontrolled Component
Sự khác biệt giữa controlled và uncontrolled component phụ thuộc vào nơi quản lý trạng thái. Nếu trạng thái được quản lý bởi DOM và bạn cần truy cập trạng thái bằng ref, đó là một uncontrolled component. Ngược lại, nếu React quản lý trạng thái và bạn cần cập nhật nó mỗi khi một sự kiện xảy ra, đó là một controlled component.
Để tìm hiểu thêm, hãy tham khảo tài liệu chính thức của react-hook-form.
Hàm register()
Hàm register() quản lý trạng thái trong DOM, do đó thành phần có thể được xem là một uncontrolled component. Điều này có nghĩa là React không kiểm soát giá trị của trường dữ liệu một cách trực tiếp.
Thành Phần <Controller />
Thành phần <Controller /> quản lý trạng thái trong React, vì vậy nó có thể được xem là một controlled component. Điều này cho phép bạn có thể kiểm soát giá trị của các trường dữ liệu một cách dễ dàng hơn.
Hook useController
Hook useController() cho phép bạn quản lý trạng thái bằng cách sử dụng hook, giúp cho việc tích hợp giữa React và các thư viện khác trở nên dễ dàng hơn.
So Sánh register() và <Controller />
- Nếu bạn cần xây dựng các UI như
Slidervới giá trị min-max,Rating, hoặcColor Picker, giá trị của những phần tử này không thể được mô tả như là các thành phần HTML nguyên bản.
Ví Dụ: Slider.tsx
typescript
export function PriceRangeSlider() {
const { control } = useFormContext();
const { field: { value, onChange } } = useController({
name: 'priceRange',
control,
});
const handleLeft = (e: ChangeEvent) => {
onChange([ Number(e.target.value), value[1] ]);
}
const handleRight = (e: ChangeEvent) => {
onChange([ value[0], Number(e.target.value) ]);
}
return <Slider onLeft={handleLeft} onRight={handleRight} />;
}
- Nếu trạng thái cần được biết ở quy mô toàn cục, bạn nên sử dụng
<Controller />thay vìregister(). Ví dụ, mọi thay đổi trong UI phụ thuộc vào theme (chế độ tối/sáng). Trạng thái này cần được tiêm vào mọi thành phần trong React, vì vậy việc sử dụng<Controller />là tốt hơn.
Ví Dụ: ThemeContext.tsx
typescript
type Theme = 'light' | 'dark';
interface ThemeContext {
theme: Theme;
toggle: () => void;
}
const [Provider, useContext] = buildContext<ThemeContext>('ThemeContext', null);
interface ThemeProviderProps {
children: ReactNode;
}
export function ThemeProvider({ children }: ThemeProviderProps) {
const [theme, setTheme] = useStorageState<'dark' | 'light'>('theme', {
defaultValue: 'dark',
});
const toggle = () => {
setTheme((prev) => prev === 'dark' ? 'light' : 'dark');
}
return <Provider theme={theme} toggle={toggle}>{children}</Provider>;
}
export const useTheme = () => useContext();
Ví Dụ: ThemeRow.tsx
typescript
export function ThemeSwitch() {
const { theme, toggle } = useTheme();
return <Stack.Vertical width="full" justify="space-between" align="center" padding={16}>
<Label>{theme === 'dark' ? 'Chuyển sang chế độ sáng' : 'Chuyển sang chế độ tối'}</Label>
<Toggle theme={theme} toggle={toggle} />
</Stack.Vertical>;
}
Ví Dụ: Card.tsx
typescript
interface Props {
title?: ReactNode;
content?: ReactNode;
footer?: ReactNode;
}
export function Card({ title, content, footer }: Props) {
const { theme } = useTheme();
return <Stack.Horizontal css={{
backgroundColor: theme === 'dark' ? colors.dark : colors.white
}}>
{title}
{title != null && <Divider />}
<div>
<Spacing size={24} />
{content}
<Spacing size={24} />
{footer != null && <Divider />}
{footer}
</div>
</Stack.Horizontal>;
}
So Sánh <Controller /> và useController
Việc sử dụng <Controller /> có thể làm cho JSX của bạn trở nên phức tạp hơn. Điều này có thể khiến mắt bạn phải di chuyển nhiều hơn để tìm kiếm các props.
Tuy nhiên, nếu có nhiều trường trong cùng một component, việc sử dụng nhiều useController() có thể gây rối và dài dòng. Điều này làm cho việc tìm kiếm props trong các khai báo tương tự trở nên khó khăn hơn. Tôi thường sử dụng <Controller /> để tránh sự nhầm lẫn, mặc dù JSX có thể trông phức tạp hơn. Nếu bạn có thể tách component thành nhiều phần, đó sẽ là một chiến lược tốt hơn.
Các Thực Hành Tốt Nhất
- Nên sử dụng
<Controller />cho các thành phần cần quản lý trạng thái một cách hiệu quả và rõ ràng. - Sử dụng
register()cho các trường hợp đơn giản và khi không cần kiểm soát trạng thái toàn cục.
Những Cạm Bẫy Thường Gặp
- Không nên lạm dụng
register()cho những trường hợp cần quản lý trạng thái phức tạp. - Đảm bảo rằng bạn hiểu rõ khi nào cần sử dụng controlled và uncontrolled component để tránh lỗi trong quá trình phát triển.
Mẹo Tối Ưu Hiệu Suất
- Hạn chế việc sử dụng quá nhiều
<Controller />trong một component để giảm độ phức tạp. - Tối ưu hóa các hook để tránh việc render lại không cần thiết.
Khắc Phục Sự Cố
- Nếu bạn gặp sự cố khi sử dụng
useController(), hãy kiểm tra xem bạn đã truyền đúngcontroltừuseFormContext()hay chưa.
Kết Luận
Sử dụng react-hook-form với <Controller /> và useController() giúp bạn dễ dàng quản lý trạng thái trong ứng dụng React của mình. Hãy áp dụng các thực hành tốt nhất để tối ưu hóa hiệu suất và trải nghiệm người dùng. Nếu bạn có thắc mắc hoặc cần thêm thông tin, đừng ngần ngại để lại câu hỏi bên dưới!
Câu Hỏi Thường Gặp (FAQ)
- Khi nào nên sử dụng
register()thay vì<Controller />?
Nên sử dụngregister()cho các trường hợp đơn giản mà không cần kiểm soát trạng thái toàn cục. - Có cách nào để tối ưu hóa hiệu suất khi sử dụng react-hook-form không?
Hạn chế sử dụng quá nhiều<Controller />trong một component và tối ưu hóa các hook.
Hy vọng bài viết này đã giúp bạn hiểu rõ hơn về cách sử dụng react-hook-form và các thành phần liên quan. Hãy thử nghiệm và áp dụng vào dự án của bạn ngay hôm nay!