# 重构快速参考指南 > 常见模式和代码示例的速查表 --- ## 📑 目录 1. [React Query 使用](#react-query-使用) 2. [react-hook-form 使用](#react-hook-form-使用) 3. [shadcn/ui 组件使用](#shadcnui-组件使用) 4. [代码迁移示例](#代码迁移示例) --- ## React Query 使用 ### 基础查询 ```typescript // 定义查询 Hook export const useProvidersQuery = (appType: AppType) => { return useQuery({ queryKey: ['providers', appType], queryFn: async () => { const data = await providersApi.getAll(appType) return data }, }) } // 在组件中使用 function MyComponent() { const { data, isLoading, error } = useProvidersQuery('claude') if (isLoading) return
Loading...
if (error) return
Error: {error.message}
return
{/* 使用 data */}
} ``` ### Mutation (变更操作) ```typescript // 定义 Mutation Hook export const useAddProviderMutation = (appType: AppType) => { const queryClient = useQueryClient() return useMutation({ mutationFn: async (provider: Provider) => { return await providersApi.add(provider, appType) }, onSuccess: () => { // 重新获取数据 queryClient.invalidateQueries({ queryKey: ['providers', appType] }) toast.success('添加成功') }, onError: (error: Error) => { toast.error(`添加失败: ${error.message}`) }, }) } // 在组件中使用 function AddProviderDialog() { const mutation = useAddProviderMutation('claude') const handleSubmit = (data: Provider) => { mutation.mutate(data) } return ( ) } ``` ### 乐观更新 ```typescript export const useSwitchProviderMutation = (appType: AppType) => { const queryClient = useQueryClient() return useMutation({ mutationFn: async (providerId: string) => { return await providersApi.switch(providerId, appType) }, // 乐观更新: 在请求发送前立即更新 UI onMutate: async (providerId) => { // 取消正在进行的查询 await queryClient.cancelQueries({ queryKey: ['providers', appType] }) // 保存当前数据(以便回滚) const previousData = queryClient.getQueryData(['providers', appType]) // 乐观更新 queryClient.setQueryData(['providers', appType], (old: any) => ({ ...old, currentProviderId: providerId, })) return { previousData } }, // 如果失败,回滚 onError: (err, providerId, context) => { queryClient.setQueryData(['providers', appType], context?.previousData) toast.error('切换失败') }, // 无论成功失败,都重新获取数据 onSettled: () => { queryClient.invalidateQueries({ queryKey: ['providers', appType] }) }, }) } ``` ### 依赖查询 ```typescript // 第二个查询依赖第一个查询的结果 const { data: providers } = useProvidersQuery(appType) const currentProviderId = providers?.currentProviderId const { data: currentProvider } = useQuery({ queryKey: ['provider', currentProviderId], queryFn: () => providersApi.getById(currentProviderId!), enabled: !!currentProviderId, // 只有当 ID 存在时才执行 }) ``` --- ## react-hook-form 使用 ### 基础表单 ```typescript import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' // 定义验证 schema const schema = z.object({ name: z.string().min(1, '请输入名称'), email: z.string().email('邮箱格式不正确'), age: z.number().min(18, '年龄必须大于18'), }) type FormData = z.infer function MyForm() { const form = useForm({ resolver: zodResolver(schema), defaultValues: { name: '', email: '', age: 0, }, }) const onSubmit = (data: FormData) => { console.log(data) } return (
{form.formState.errors.name && ( {form.formState.errors.name.message} )}
) } ``` ### 使用 shadcn/ui Form 组件 ```typescript import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form' import { Input } from '@/components/ui/input' import { Button } from '@/components/ui/button' function MyForm() { const form = useForm({ resolver: zodResolver(schema), }) return (
( 名称 )} /> ) } ``` ### 动态表单验证 ```typescript // 根据条件动态验证 const schema = z.object({ type: z.enum(['official', 'custom']), apiKey: z.string().optional(), baseUrl: z.string().optional(), }).refine( (data) => { // 如果是自定义供应商,必须填写 baseUrl if (data.type === 'custom') { return !!data.baseUrl } return true }, { message: '自定义供应商必须填写 Base URL', path: ['baseUrl'], } ) ``` ### 手动触发验证 ```typescript function MyForm() { const form = useForm() const handleBlur = async () => { // 验证单个字段 await form.trigger('name') // 验证多个字段 await form.trigger(['name', 'email']) // 验证所有字段 const isValid = await form.trigger() } return
...
} ``` --- ## shadcn/ui 组件使用 ### Dialog (对话框) ```typescript import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from '@/components/ui/dialog' import { Button } from '@/components/ui/button' function MyDialog() { const [open, setOpen] = useState(false) return ( 标题 描述信息 {/* 内容 */}
对话框内容
) } ``` ### Select (选择器) ```typescript import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' function MySelect() { const [value, setValue] = useState('') return ( ) } ``` ### Tabs (标签页) ```typescript import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' function MyTabs() { return ( 标签1 标签2 标签3
标签1的内容
标签2的内容
标签3的内容
) } ``` ### Toast 通知 (Sonner) ```typescript import { toast } from 'sonner' // 成功通知 toast.success('操作成功') // 错误通知 toast.error('操作失败') // 加载中 const toastId = toast.loading('处理中...') // 完成后更新 toast.success('处理完成', { id: toastId }) // 或 toast.dismiss(toastId) // 自定义持续时间 toast.success('消息', { duration: 5000 }) // 带操作按钮 toast('确认删除?', { action: { label: '删除', onClick: () => handleDelete(), }, }) ``` --- ## 代码迁移示例 ### 示例 1: 状态管理迁移 **旧代码** (手动状态管理): ```typescript const [providers, setProviders] = useState>({}) const [currentProviderId, setCurrentProviderId] = useState('') const [loading, setLoading] = useState(false) const [error, setError] = useState(null) useEffect(() => { const load = async () => { setLoading(true) setError(null) try { const data = await window.api.getProviders(appType) const currentId = await window.api.getCurrentProvider(appType) setProviders(data) setCurrentProviderId(currentId) } catch (err) { setError(err as Error) } finally { setLoading(false) } } load() }, [appType]) ``` **新代码** (React Query): ```typescript const { data, isLoading, error } = useProvidersQuery(appType) const providers = data?.providers || {} const currentProviderId = data?.currentProviderId || '' ``` **减少**: 从 20+ 行到 3 行 --- ### 示例 2: 表单验证迁移 **旧代码** (手动验证): ```typescript const [name, setName] = useState('') const [nameError, setNameError] = useState('') const [apiKey, setApiKey] = useState('') const [apiKeyError, setApiKeyError] = useState('') const validate = () => { let valid = true if (!name.trim()) { setNameError('请输入名称') valid = false } else { setNameError('') } if (!apiKey.trim()) { setApiKeyError('请输入 API Key') valid = false } else if (apiKey.length < 10) { setApiKeyError('API Key 长度不足') valid = false } else { setApiKeyError('') } return valid } const handleSubmit = () => { if (validate()) { // 提交 } } return (
setName(e.target.value)} /> {nameError && {nameError}} setApiKey(e.target.value)} /> {apiKeyError && {apiKeyError}}
) ``` **新代码** (react-hook-form + zod): ```typescript const schema = z.object({ name: z.string().min(1, '请输入名称'), apiKey: z.string().min(10, 'API Key 长度不足'), }) const form = useForm({ resolver: zodResolver(schema), }) return (
( )} /> ( )} /> ) ``` **减少**: 从 40+ 行到 30 行,且更健壮 --- ### 示例 3: 通知系统迁移 **旧代码** (自定义通知): ```typescript const [notification, setNotification] = useState<{ message: string type: 'success' | 'error' } | null>(null) const [isVisible, setIsVisible] = useState(false) const showNotification = (message: string, type: 'success' | 'error') => { setNotification({ message, type }) setIsVisible(true) setTimeout(() => { setIsVisible(false) setTimeout(() => setNotification(null), 300) }, 3000) } return ( <> {notification && (
{notification.message}
)} {/* 其他内容 */} ) ``` **新代码** (Sonner): ```typescript import { toast } from 'sonner' // 在需要的地方直接调用 toast.success('操作成功') toast.error('操作失败') // 在 main.tsx 中只需添加一次 import { Toaster } from '@/components/ui/sonner' ``` **减少**: 从 20+ 行到 1 行调用 --- ### 示例 4: 对话框迁移 **旧代码** (自定义 Modal): ```typescript const [isOpen, setIsOpen] = useState(false) return ( <> {isOpen && (
setIsOpen(false)}>
e.stopPropagation()}>

标题

{/* 内容 */}
)} ) ``` **新代码** (shadcn/ui Dialog): ```typescript import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog' const [isOpen, setIsOpen] = useState(false) return ( <> 标题 {/* 内容 */} ) ``` **优势**: - 无需自定义样式 - 内置无障碍支持 - 自动管理焦点和 ESC 键 --- ### 示例 5: API 调用迁移 **旧代码** (window.api): ```typescript // 添加供应商 const handleAdd = async (provider: Provider) => { try { await window.api.addProvider(provider, appType) await loadProviders() showNotification('添加成功', 'success') } catch (error) { showNotification('添加失败', 'error') } } ``` **新代码** (React Query Mutation): ```typescript // 在组件中 const addMutation = useAddProviderMutation(appType) const handleAdd = (provider: Provider) => { addMutation.mutate(provider) // 成功和错误处理已在 mutation 定义中处理 } ``` **优势**: - 自动处理 loading 状态 - 统一的错误处理 - 自动刷新数据 - 更少的样板代码 --- ## 常见问题 ### Q: 如何在 mutation 成功后关闭对话框? ```typescript const mutation = useAddProviderMutation(appType) const handleSubmit = (data: Provider) => { mutation.mutate(data, { onSuccess: () => { setIsOpen(false) // 关闭对话框 }, }) } ``` ### Q: 如何在表单中使用异步验证? ```typescript const schema = z.object({ name: z.string().refine( async (name) => { // 检查名称是否已存在 const exists = await checkNameExists(name) return !exists }, { message: '名称已存在' } ), }) ``` ### Q: 如何手动刷新 Query 数据? ```typescript const queryClient = useQueryClient() // 方式1: 使缓存失效,触发重新获取 queryClient.invalidateQueries({ queryKey: ['providers', appType] }) // 方式2: 直接刷新 queryClient.refetchQueries({ queryKey: ['providers', appType] }) // 方式3: 更新缓存数据 queryClient.setQueryData(['providers', appType], newData) ``` ### Q: 如何在组件外部使用 toast? ```typescript // 直接导入并使用即可 import { toast } from 'sonner' export const someUtil = () => { toast.success('工具函数中的通知') } ``` --- ## 调试技巧 ### React Query DevTools ```typescript // 在 main.tsx 中添加 import { ReactQueryDevtools } from '@tanstack/react-query-devtools' ``` ### 查看表单状态 ```typescript const form = useForm() // 在开发模式下打印表单状态 console.log('Form values:', form.watch()) console.log('Form errors:', form.formState.errors) console.log('Is valid:', form.formState.isValid) ``` --- ## 性能优化建议 ### 1. 避免不必要的重渲染 ```typescript // 使用 React.memo export const ProviderCard = React.memo(({ provider, onEdit }: Props) => { // ... }) // 或使用 useMemo const sortedProviders = useMemo( () => Object.values(providers).sort(...), [providers] ) ``` ### 2. Query 配置优化 ```typescript const { data } = useQuery({ queryKey: ['providers', appType], queryFn: fetchProviders, staleTime: 1000 * 60 * 5, // 5分钟内不重新获取 gcTime: 1000 * 60 * 10, // 10分钟后清除缓存 }) ``` ### 3. 表单性能优化 ```typescript // 使用 mode 控制验证时机 const form = useForm({ mode: 'onBlur', // 失去焦点时验证 // mode: 'onChange', // 每次输入都验证(较慢) // mode: 'onSubmit', // 提交时验证(最快) }) ``` --- **提示**: 将此文档保存在浏览器书签或编辑器中,方便随时查阅!