1. 事件處理
function EventDemo() {
// Inline handler
return <button onClick={() => console.log('clicked')}>Click</button>;
// Named handler (khuyến khích)
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
console.log('clicked', e.currentTarget);
};
// Handler with parameters
const handleDelete = (id: number) => () => {
console.log('delete', id);
};
return (
<div>
<button onClick={handleClick}>Click me</button>
<button onClick={handleDelete(1)}>Delete #1</button>
</div>
);
}
2. 受控形式
function LoginForm() {
const [form, setForm] = useState({ email: '', password: '' });
const [errors, setErrors] = useState<Record<string, string>>({});
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setForm(prev => ({ ...prev, [name]: value }));
};
const validate = () => {
const newErrors: Record<string, string> = {};
if (!form.email) newErrors.email = 'Email is required';
if (!form.password) newErrors.password = 'Password is required';
if (form.password.length < 8) newErrors.password = 'Min 8 characters';
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (validate()) {
console.log('Submit:', form);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<input name="email" value={form.email} onChange={handleChange} />
{errors.email && <span className="error">{errors.email}</span>}
</div>
<div>
<input name="password" type="password" value={form.password} onChange={handleChange} />
{errors.password && <span className="error">{errors.password}</span>}
</div>
<button type="submit">Login</button>
</form>
);
}
3.React Hook形式+Zod
npm install react-hook-form @hookform/resolvers zod
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
// Schema definition
const registerSchema = z.object({
name: z.string().min(2, 'Tên ít nhất 2 ký tự').max(50),
email: z.string().email('Email không hợp lệ'),
password: z.string().min(8, 'Mật khẩu ít nhất 8 ký tự'),
confirmPassword: z.string(),
role: z.enum(['user', 'admin']),
agreeTerms: z.literal(true, {
errorMap: () => ({ message: 'Bạn phải đồng ý điều khoản' }),
}),
}).refine(data => data.password === data.confirmPassword, {
message: 'Mật khẩu không khớp',
path: ['confirmPassword'],
});
type RegisterForm = z.infer<typeof registerSchema>;
function RegisterPage() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
reset,
} = useForm<RegisterForm>({
resolver: zodResolver(registerSchema),
defaultValues: {
role: 'user',
},
});
const onSubmit = async (data: RegisterForm) => {
const res = await fetch('/api/register', {
method: 'POST',
body: JSON.stringify(data),
});
if (res.ok) reset();
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label>Name</label>
<input {...register('name')} />
{errors.name && <p className="error">{errors.name.message}</p>}
</div>
<div>
<label>Email</label>
<input {...register('email')} type="email" />
{errors.email && <p className="error">{errors.email.message}</p>}
</div>
<div>
<label>Password</label>
<input {...register('password')} type="password" />
{errors.password && <p className="error">{errors.password.message}</p>}
</div>
<div>
<label>Confirm Password</label>
<input {...register('confirmPassword')} type="password" />
{errors.confirmPassword && <p className="error">{errors.confirmPassword.message}</p>}
</div>
<div>
<select {...register('role')}>
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
</div>
<div>
<label>
<input {...register('agreeTerms')} type="checkbox" />
Đồng ý điều khoản sử dụng
</label>
{errors.agreeTerms && <p className="error">{errors.agreeTerms.message}</p>}
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Đang xử lý...' : 'Đăng ký'}
</button>
</form>
);
}
4. 條件渲染模式
function Dashboard({ user, notifications, isLoading, error }: Props) {
// Pattern 1: Early return
if (isLoading) return <Skeleton />;
if (error) return <ErrorMessage error={error} />;
if (!user) return <LoginPrompt />;
// Pattern 2: Logical AND (&&)
// ⚠️ Cẩn thận với falsy values (0, '')
return (
<div>
{notifications.length > 0 && (
<Badge count={notifications.length} />
)}
{/* Pattern 3: Ternary */}
{user.role === 'admin' ? <AdminPanel /> : <UserPanel />}
{/* Pattern 4: Object mapping */}
{{
active: <ActiveStatus />,
pending: <PendingStatus />,
inactive: <InactiveStatus />,
}[user.status]}
</div>
);
}
5. 列表和鍵
interface Todo {
id: string;
title: string;
completed: boolean;
}
function TodoList({ todos }: { todos: Todo[] }) {
return (
<ul>
{todos.map(todo => (
// ✅ Key phải unique, stable, từ data
<li key={todo.id}>
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.title}
</span>
</li>
))}
</ul>
);
}
// ❌ KHÔNG dùng index làm key khi list thay đổi (add/remove/reorder)
// ❌ <li key={index}> → lỗi khi sort/filter
// ✅ <li key={item.id}> → unique identifier từ data
下一篇: 上下文 API 和狀態管理 — Zustand、TanStack 查詢和狀態管理策略。