Bạn có một form "khủng" với 50 ô input (ví dụ: Form khai báo thuế, Form nhập kho). Khi user gõ 1 ký tự vào ô "Tên", cả 49 ô còn lại... nháy sáng trong React DevTools Profiler.
Đây là thảm họa hiệu năng phổ biến nhất trong React.
1. Controlled Component: Kẻ giết chết Performance
Cách viết React truyền thống (Controlled):
// ❌ BAD: The Performance Killer
export function SlowForm() {
const [values, setValues] = useState({ name: '', email: '', note: '' });
// Mỗi lần gõ 1 phím -> Gọi setValues -> Trigger Re-render toàn bộ Component
// Nếu Component này chứa 50 input con -> Cả 50 input con đều bị Re-render
const handleChange = (e) => {
setValues(prev => ({ ...prev, [e.target.name]: e.target.value }));
};
return (
<form>
<input name="name" value={values.name} onChange={handleChange} />
<HeavyComponent /> {/* Bị re-render oan uổng */}
</form>
);
}Dẫn chứng:
- User gõ "Nguyen Van A" (12 ký tự).
- Component
SlowFormrender lại 12 lần. - Nếu
HeavyComponentmất 50ms để render -> Total blocking time = 12 * 50ms = 600ms. -> Lag lòi mắt.
2. React Hook Form (Uncontrolled): Cứu tinh
Triết lý của RHF: "Hãy để DOM giữ giá trị (Uncontrolled), React chỉ đứng nhìn (Subscribe)".
RHF sử dụng ref để đăng ký trực tiếp với DOM Element.
Khi user gõ phím -> Browser update DOM native (siêu nhanh) -> React KHÔNG HỀ BIẾT (Không Re-render).
// ✅ GOOD: Zero Re-render
export function FastForm() {
const { register, handleSubmit } = useForm();
// Component này chỉ render ĐÚNG 1 LẦN khi mount.
// Gõ phím thoải mái, component vẫn đứng im.
console.log("Render Count");
const onSubmit = (data) => console.log(data); // Chỉ lấy data khi Submit
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("name")} /> {/* Đăng ký Ref ngầm */}
<HeavyComponent /> {/* An toàn tuyệt đối */}
<button>Submit</button>
</form>
);
}3. Bài học xương máu: watch sai cách
Rất nhiều bạn dùng RHF nhưng vẫn làm app chậm. Lý do: Dùng watch bừa bãi.
Sai lầm: Watch ngay tại Root Component.
// ❌ SAI LẦM: Làm mất hết tác dụng của RHF
function WrongUsage() {
const { register, watch } = useForm();
// Lắng nghe thay đổi của name để hiện thông báo
// -> Mỗi lần name đổi -> Root Component re-render -> Lại lôi theo cả form re-render
const name = watch("name");
return (
<form>
<input {...register("name")} />
<p>Hello {name}</p>
<HeavyComponent /> {/* Vẫn bị re-render! */}
</form>
);
}Tối ưu nhất: Cô lập Re-render (Component Isolation). Hãy đẩy phần cần re-render vào một component con riêng biệt.
// ✅ TỐI ƯU: Chỉ component bé tí này re-render
function NamePreview({ control }) {
const name = useWatch({ control, name: "name" }); // Hook chuyên biệt
return <p>Hello {name}</p>;
}
function OptimalForm() {
const { register, control } = useForm();
return (
<form>
<input {...register("name")} />
{/* Component này re-render liên tục (nhẹ) */}
<NamePreview control={control} />
{/* Component này KHÔNG BAO GIỜ re-render (nặng) */}
<HeavyComponent />
</form>
);
}4. Proxy State Mode (formState)
Một cái bẫy khác: Validation Mode.
RHF có cơ chế Proxy thông minh. Nếu bạn không đọc errors, RHF sẽ không re-render khi có lỗi validation.
const { formState: { errors } } = useForm();
// Chỉ khi bạn destructure { errors } ra, RHF mới bắt đầu tracking validation state.
// Nếu bạn xóa chữ `errors` đi, form sẽ chạy nhanh hơn nữa!Kết luận
Để đạt hiệu năng tối đa với Form:
- Ưu tiên Uncontrolled: Dùng
registercho input thuần.Controllercho UI Lib. - Tránh
watchở Root: DùnguseWatchtrong component con để cô lập vùng re-render. - Proxy State: Chỉ lấy những gì bạn cần ở
formState(isValid, isDirty, errors).