1. 项目概述当AI组件生成器撞上现实最近两年前端圈子里最热闹的话题之一就是各种AI驱动的React组件生成工具。从GitHub Copilot的代码补全到Vercel v0、Cursor这类号称“一句话生成完整UI”的平台再到各种基于GPT的代码生成插件它们承诺能极大提升开发效率让你从重复的脚手架代码中解放出来。我自己也跟风试过不少初期确实很惊艳——输入一句“生成一个带搜索、分页和排序的用户数据表格”几秒钟后一个看起来功能齐全的DataTable组件就摆在了眼前。这种“魔法”般的体验让很多团队尤其是初创公司或需要快速验证想法的项目开始认真考虑将AI生成器纳入工作流。但问题往往在项目进入中后期或者当你试图把那个“完美”的生成组件集成到现有复杂应用时才开始暴露。你会发现生成的组件在独立的沙盒环境里跑得好好的一旦放进真实的代码库就开始出现各种“水土不服”样式和现有的设计系统冲突、状态管理与全局Store无法协同、性能莫名其妙地卡顿、或者代码结构让后续接手维护的同事直挠头。这背后的根本原因并不是AI技术本身不行而在于大多数生成器的工作模式与真实、复杂的软件工程实践之间存在着一道尚未逾越的鸿沟。它们擅长基于公开、通用的模式生成“标准答案”却难以理解你项目里那些独特的、隐性的约束和约定。这篇文章我想结合自己最近半年在几个中大型React项目中系统化尝试并最终“规训”AI生成组件的实战经验来聊聊为什么这些看起来很美的工具在实际项目中容易“翻车”更重要的是分享一套经过验证的、能让AI生成代码真正落地、融入团队协作的“修复”方法论。这不是对AI的批判而是探讨如何让它从一个“炫技的玩具”变成一名靠谱的“初级工程师”。2. 核心问题拆解AI生成代码的“理想”与“现实”要解决问题首先得看清问题出在哪。AI组件生成器在真实项目中失效通常不是单一原因导致的而是多个维度上的错位叠加的结果。2.1 上下文缺失与项目特异性理解的鸿沟这是最核心、也最致命的问题。当你对AI说“生成一个登录表单”它基于海量的公开代码如GitHub上的开源项目、Stack Overflow问答进行模式匹配生成一个最“常见”的登录表单。这个表单可能使用内联样式、简单的useState管理表单状态、用alert弹出错误信息。对于一个全新的、独立的演示项目这完全没问题。但你的真实项目呢它很可能有一套严格的设计规范所有颜色来自tokens.primary间距使用theme.spacing(2)按钮必须用封装好的Button variantoutlined组件。表单状态管理可能已经接入了React Hook Form或Formik并配有统一的验证规则库。错误提示需要集成到项目顶层的通知系统如react-toastify或自定义的Snackbar上下文中而不是简单的alert。这些约束是你的项目在长期迭代中形成的“方言”而AI生成器对此一无所知。它输出的是“普通话”代码自然无法无缝融入你的“方言”环境。注意许多开发者会尝试在提示词里加入这些约束比如“使用Material-UI的TextField组件”。这有一定效果但提示词的长度和理解深度是有限的。AI很难通过一次提示就完全领会你项目中那些复杂的、嵌套的上下文依赖比如“表单提交后需要触发一个定义在src/hooks/useAuth中的自定义hook并处理其返回的特定错误格式”。2.2 代码质量与可维护性的隐形债务AI生成的代码在语法正确性上通常做得不错但在代码质量Code Quality和可维护性Maintainability方面往往埋下了“技术债”的种子。1. 过度耦合与关注点分离缺失AI倾向于生成一个庞大的、单一的文件将样式可能是内联的、逻辑、副作用useEffect和渲染JSX全部塞进一个函数组件里。这违反了React社区推崇的“关注点分离”和“单一职责”原则。在小型组件中或许可以忍受但对于稍复杂的组件这会让单元测试变得极其困难也阻碍了逻辑的复用。2. 脆弱的样式策略生成器偏爱内联样式style{{}}或简单的CSS模块导入。在需要响应式设计、主题切换、或与现有CSS-in-JS方案如Styled-components, Emotion集成的项目中这些样式会成为迁移的噩梦。它们无法享受设计系统的变量也难以进行动态主题适配。3. 状态管理的“幼稚”选择对于任何涉及跨组件状态或服务器状态同步的场景AI通常会给出最基础的useStateuseEffect组合。这在简单场景下可行但在数据流复杂后极易导致冗余请求、状态不同步Stale Closure或难以调试的无限重渲染循环。它不会主动建议你使用更合适的工具如useReducer、Context或与TanStack Query (React Query)、SWR、Zustand、Redux Toolkit等流行状态库集成。4. 缺乏错误边界与防御性编程生成的代码通常假设一切都会顺利进行缺少对网络请求失败、数据格式异常、边界条件如空数组、null/undefined值的健壮处理。在真实环境中这些缺失的防御性代码是应用崩溃的常见根源。2.3 性能陷阱与副作用管理性能问题是另一个“ silent killer ”。AI生成的组件在渲染优化方面考虑甚少。1. 不必要的重渲染由于大量使用内联函数如onClick{() doSomething(id)}和对象字面量如style{{color: ‘red‘}}且缺乏React.memo、useMemo、useCallback等优化手段生成的组件及其子组件会在父组件每次渲染时都跟着重新渲染即使实际依赖的数据并未改变。在列表渲染等场景下这会迅速导致性能劣化。2.useEffect的滥用与依赖数组问题AI对useEffect的使用往往很机械。它可能生成多个独立的useEffect来处理本可以合并的逻辑或者设置错误的依赖数组deps导致副作用在不应触发时触发或在该触发时未触发。调试这类问题非常耗时。3. 资源泄漏风险在涉及订阅如WebSocket、事件监听器或异步操作的useEffect中AI生成的清理函数cleanup可能不完整或缺失这会在组件卸载时导致内存泄漏或状态更新到已卸载组件的警告。2.4 测试与类型安全的缺失一个可交付的组件不仅仅是能运行。它还需要可测试Testable和类型安全TypeSafe。AI生成器在这两方面几乎是“盲区”。1. 测试友好性为零生成的组件逻辑与视图高度耦合且常常缺少关键元素的>// UserTable.jsx - AI生成初稿 import React, { useState, useEffect } from react; function UserTable() { const [users, setUsers] useState([]); const [loading, setLoading] useState(false); const [search, setSearch] useState(); const [page, setPage] useState(1); const [selectedIds, setSelectedIds] useState([]); useEffect(() { fetchUsers(); }, [page, search]); // 依赖项可能有问题 const fetchUsers async () { setLoading(true); try { const response await fetch(/api/users?page${page}search${search}); const data await response.json(); setUsers(data); } catch (error) { alert(Failed to fetch users: error.message); // 错误处理简陋 } finally { setLoading(false); } }; const handleDelete async (id) { if (!window.confirm(Are you sure?)) return; // 交互方式不统一 await fetch(/api/users/${id}, { method: DELETE }); fetchUsers(); // 直接重新获取全部数据低效 }; // ... 其他处理函数和庞大的JSX渲染逻辑超过100行 }按照修复策略进行重构1. 应用“项目上下文”我们假设项目使用TanStack Query、Ant Design组件库和自定义的通知Hook。2. 执行“重构清单”拆分关注点将数据获取逻辑移到自定义HookuseUsers中将表格渲染拆分为子组件UserTableRow。集成基础设施用useQuery和useMutation替换fetch和useEffect。用Ant Design的Table、Input、Button、Modal和message组件替换原生元素和alert。优化状态与性能用useCallback包装事件处理函数。利用useQuery的缓存和后台刷新能力避免handleDelete后的全量重拉。增强健壮性为查询和变更添加正确的加载和错误状态UI。使用Modal.confirm代替window.confirm以保持UI一致。重构后的核心代码结构// hooks/useUsers.ts - 提取的数据逻辑Hook import { useQuery, useMutation, useQueryClient } from tanstack/react-query; import { userApi } from /api/user; // 项目封装的API模块 import { notification } from /utils/notification; // 统一通知 export const useUsers (params: { page: number; search: string }) { return useQuery({ queryKey: [users, params], queryFn: () userApi.getList(params), }); }; export const useDeleteUser () { const queryClient useQueryClient(); return useMutation({ mutationFn: userApi.delete, onSuccess: () { notification.success(用户删除成功); // 使相关的查询失效触发后台静默重拉而非立即重拉 queryClient.invalidateQueries({ queryKey: [users] }); }, onError: (error) { notification.error(删除失败: ${error.message}); }, }); }; // components/features/user/UserTable.tsx - 主组件 import React, { useCallback, useState } from react; import { Table, Input, Button, Space, Modal } from antd; import { useUsers, useDeleteUser } from /hooks/useUsers; import { User } from /types/user; import { UserTableActions } from ./UserTableActions; // 拆分的操作栏组件 import { columns } from ./columns; // 拆分的列配置 const { Search } Input; export const UserTable: React.FC () { const [searchText, setSearchText] useState(); const [currentPage, setCurrentPage] useState(1); const [selectedRowKeys, setSelectedRowKeys] useStateReact.Key[]([]); const { data, isLoading, error } useUsers({ page: currentPage, search: searchText, }); const { mutate: deleteUser, isPending: isDeleting } useDeleteUser(); const handleSearch useCallback((value: string) { setSearchText(value); setCurrentPage(1); // 搜索时重置到第一页 }, []); const handleDelete useCallback((id: number) { Modal.confirm({ title: 确认删除, content: 此操作不可恢复确定删除该用户吗, onOk: () deleteUser(id), }); }, [deleteUser]); // 渲染逻辑变得清晰简洁 if (error) return div加载失败.../div; return ( Space directionvertical sizemiddle style{{ display: flex }} UserTableActions selectedKeys{selectedRowKeys} onSearch{handleSearch} // ... 其他props / Table rowKeyid columns{columns(handleDelete)} // 列配置接收处理函数 dataSource{data?.list || []} loading{isLoading || isDeleting} rowSelection{{ selectedRowKeys, onChange: setSelectedRowKeys, }} pagination{{ current: currentPage, total: data?.total || 0, onChange: setCurrentPage, }} / /Space ); };通过这一系列重构我们将一个脆弱、耦合的AI初稿转变为了一个符合现代React最佳实践、易于测试和维护的生产级组件。AI完成了繁重的“起草”工作定义大致结构、字段、基础交互而我们通过系统化的“修复”流程注入了项目的灵魂架构、规范、健壮性。5. 常见问题与排查技巧实录在实际“规训”AI生成代码的过程中你会反复遇到一些典型问题。这里记录一份速查表。问题现象可能原因排查与修复技巧组件样式完全错乱AI使用了内联样式或与项目不同的CSS方案如生成的是style{{}}而项目用styled-components。1.预防在提示词中明确样式方案如“使用Emotion的styled语法编写样式”。2.修复将内联样式逐一替换为项目中的样式变量或styled组件。优先检查布局、颜色、间距等通用属性。状态更新无效或出现陈旧值1.useState的更新是异步的连续调用可能未按预期工作。2. 在useEffect或事件回调中依赖了未正确列入依赖数组的状态/函数。1. 对于连续状态更新使用函数式更新setCount(prev prev 1)。2. 使用ESLint的exhaustive-deps规则检查所有useEffect和useCallback的依赖数组。确保所有在内部使用的变量都正确声明。组件无限重渲染1. 在渲染函数中直接创建了新的对象/函数并作为props传递给子组件。2.useEffect的依赖数组为空[]但其内部函数引用了会变化的状态或props。1. 使用useMemo缓存对象使用useCallback缓存函数。2. 将useEffect内部用到的函数或值要么移入useEffect内部要么正确加入依赖数组。使用useCallback包装函数以稳定其引用。TypeScript报一堆类型错误AI使用了宽泛的any类型或引用了不存在的类型/模块。1.定位从第一个错误开始修后面的错误可能随之解决。2.修复根据项目实际类型定义手动替换any。检查导入路径是否正确。可以临时使用ts-ignore但需后续补上正确定义。网络请求重复发送或竞态条件AI使用了多个独立的useEffect来获取数据且依赖管理不当。1.根治用TanStack Query或SWR替代手动的useEffectfetch。它们内置了缓存、去重和竞态处理。2.临时方案使用AbortController在组件卸载或依赖变化时取消未完成的请求。生成的代码无法被测试组件逻辑与UI耦合太紧且没有提供测试查询的选择器。1. 将业务逻辑抽离到自定义Hook中Hook是纯函数极易测试。2. 在关键DOM元素上添加>
AI生成React组件实战:从翻车到落地的四步修复策略
1. 项目概述当AI组件生成器撞上现实最近两年前端圈子里最热闹的话题之一就是各种AI驱动的React组件生成工具。从GitHub Copilot的代码补全到Vercel v0、Cursor这类号称“一句话生成完整UI”的平台再到各种基于GPT的代码生成插件它们承诺能极大提升开发效率让你从重复的脚手架代码中解放出来。我自己也跟风试过不少初期确实很惊艳——输入一句“生成一个带搜索、分页和排序的用户数据表格”几秒钟后一个看起来功能齐全的DataTable组件就摆在了眼前。这种“魔法”般的体验让很多团队尤其是初创公司或需要快速验证想法的项目开始认真考虑将AI生成器纳入工作流。但问题往往在项目进入中后期或者当你试图把那个“完美”的生成组件集成到现有复杂应用时才开始暴露。你会发现生成的组件在独立的沙盒环境里跑得好好的一旦放进真实的代码库就开始出现各种“水土不服”样式和现有的设计系统冲突、状态管理与全局Store无法协同、性能莫名其妙地卡顿、或者代码结构让后续接手维护的同事直挠头。这背后的根本原因并不是AI技术本身不行而在于大多数生成器的工作模式与真实、复杂的软件工程实践之间存在着一道尚未逾越的鸿沟。它们擅长基于公开、通用的模式生成“标准答案”却难以理解你项目里那些独特的、隐性的约束和约定。这篇文章我想结合自己最近半年在几个中大型React项目中系统化尝试并最终“规训”AI生成组件的实战经验来聊聊为什么这些看起来很美的工具在实际项目中容易“翻车”更重要的是分享一套经过验证的、能让AI生成代码真正落地、融入团队协作的“修复”方法论。这不是对AI的批判而是探讨如何让它从一个“炫技的玩具”变成一名靠谱的“初级工程师”。2. 核心问题拆解AI生成代码的“理想”与“现实”要解决问题首先得看清问题出在哪。AI组件生成器在真实项目中失效通常不是单一原因导致的而是多个维度上的错位叠加的结果。2.1 上下文缺失与项目特异性理解的鸿沟这是最核心、也最致命的问题。当你对AI说“生成一个登录表单”它基于海量的公开代码如GitHub上的开源项目、Stack Overflow问答进行模式匹配生成一个最“常见”的登录表单。这个表单可能使用内联样式、简单的useState管理表单状态、用alert弹出错误信息。对于一个全新的、独立的演示项目这完全没问题。但你的真实项目呢它很可能有一套严格的设计规范所有颜色来自tokens.primary间距使用theme.spacing(2)按钮必须用封装好的Button variantoutlined组件。表单状态管理可能已经接入了React Hook Form或Formik并配有统一的验证规则库。错误提示需要集成到项目顶层的通知系统如react-toastify或自定义的Snackbar上下文中而不是简单的alert。这些约束是你的项目在长期迭代中形成的“方言”而AI生成器对此一无所知。它输出的是“普通话”代码自然无法无缝融入你的“方言”环境。注意许多开发者会尝试在提示词里加入这些约束比如“使用Material-UI的TextField组件”。这有一定效果但提示词的长度和理解深度是有限的。AI很难通过一次提示就完全领会你项目中那些复杂的、嵌套的上下文依赖比如“表单提交后需要触发一个定义在src/hooks/useAuth中的自定义hook并处理其返回的特定错误格式”。2.2 代码质量与可维护性的隐形债务AI生成的代码在语法正确性上通常做得不错但在代码质量Code Quality和可维护性Maintainability方面往往埋下了“技术债”的种子。1. 过度耦合与关注点分离缺失AI倾向于生成一个庞大的、单一的文件将样式可能是内联的、逻辑、副作用useEffect和渲染JSX全部塞进一个函数组件里。这违反了React社区推崇的“关注点分离”和“单一职责”原则。在小型组件中或许可以忍受但对于稍复杂的组件这会让单元测试变得极其困难也阻碍了逻辑的复用。2. 脆弱的样式策略生成器偏爱内联样式style{{}}或简单的CSS模块导入。在需要响应式设计、主题切换、或与现有CSS-in-JS方案如Styled-components, Emotion集成的项目中这些样式会成为迁移的噩梦。它们无法享受设计系统的变量也难以进行动态主题适配。3. 状态管理的“幼稚”选择对于任何涉及跨组件状态或服务器状态同步的场景AI通常会给出最基础的useStateuseEffect组合。这在简单场景下可行但在数据流复杂后极易导致冗余请求、状态不同步Stale Closure或难以调试的无限重渲染循环。它不会主动建议你使用更合适的工具如useReducer、Context或与TanStack Query (React Query)、SWR、Zustand、Redux Toolkit等流行状态库集成。4. 缺乏错误边界与防御性编程生成的代码通常假设一切都会顺利进行缺少对网络请求失败、数据格式异常、边界条件如空数组、null/undefined值的健壮处理。在真实环境中这些缺失的防御性代码是应用崩溃的常见根源。2.3 性能陷阱与副作用管理性能问题是另一个“ silent killer ”。AI生成的组件在渲染优化方面考虑甚少。1. 不必要的重渲染由于大量使用内联函数如onClick{() doSomething(id)}和对象字面量如style{{color: ‘red‘}}且缺乏React.memo、useMemo、useCallback等优化手段生成的组件及其子组件会在父组件每次渲染时都跟着重新渲染即使实际依赖的数据并未改变。在列表渲染等场景下这会迅速导致性能劣化。2.useEffect的滥用与依赖数组问题AI对useEffect的使用往往很机械。它可能生成多个独立的useEffect来处理本可以合并的逻辑或者设置错误的依赖数组deps导致副作用在不应触发时触发或在该触发时未触发。调试这类问题非常耗时。3. 资源泄漏风险在涉及订阅如WebSocket、事件监听器或异步操作的useEffect中AI生成的清理函数cleanup可能不完整或缺失这会在组件卸载时导致内存泄漏或状态更新到已卸载组件的警告。2.4 测试与类型安全的缺失一个可交付的组件不仅仅是能运行。它还需要可测试Testable和类型安全TypeSafe。AI生成器在这两方面几乎是“盲区”。1. 测试友好性为零生成的组件逻辑与视图高度耦合且常常缺少关键元素的>// UserTable.jsx - AI生成初稿 import React, { useState, useEffect } from react; function UserTable() { const [users, setUsers] useState([]); const [loading, setLoading] useState(false); const [search, setSearch] useState(); const [page, setPage] useState(1); const [selectedIds, setSelectedIds] useState([]); useEffect(() { fetchUsers(); }, [page, search]); // 依赖项可能有问题 const fetchUsers async () { setLoading(true); try { const response await fetch(/api/users?page${page}search${search}); const data await response.json(); setUsers(data); } catch (error) { alert(Failed to fetch users: error.message); // 错误处理简陋 } finally { setLoading(false); } }; const handleDelete async (id) { if (!window.confirm(Are you sure?)) return; // 交互方式不统一 await fetch(/api/users/${id}, { method: DELETE }); fetchUsers(); // 直接重新获取全部数据低效 }; // ... 其他处理函数和庞大的JSX渲染逻辑超过100行 }按照修复策略进行重构1. 应用“项目上下文”我们假设项目使用TanStack Query、Ant Design组件库和自定义的通知Hook。2. 执行“重构清单”拆分关注点将数据获取逻辑移到自定义HookuseUsers中将表格渲染拆分为子组件UserTableRow。集成基础设施用useQuery和useMutation替换fetch和useEffect。用Ant Design的Table、Input、Button、Modal和message组件替换原生元素和alert。优化状态与性能用useCallback包装事件处理函数。利用useQuery的缓存和后台刷新能力避免handleDelete后的全量重拉。增强健壮性为查询和变更添加正确的加载和错误状态UI。使用Modal.confirm代替window.confirm以保持UI一致。重构后的核心代码结构// hooks/useUsers.ts - 提取的数据逻辑Hook import { useQuery, useMutation, useQueryClient } from tanstack/react-query; import { userApi } from /api/user; // 项目封装的API模块 import { notification } from /utils/notification; // 统一通知 export const useUsers (params: { page: number; search: string }) { return useQuery({ queryKey: [users, params], queryFn: () userApi.getList(params), }); }; export const useDeleteUser () { const queryClient useQueryClient(); return useMutation({ mutationFn: userApi.delete, onSuccess: () { notification.success(用户删除成功); // 使相关的查询失效触发后台静默重拉而非立即重拉 queryClient.invalidateQueries({ queryKey: [users] }); }, onError: (error) { notification.error(删除失败: ${error.message}); }, }); }; // components/features/user/UserTable.tsx - 主组件 import React, { useCallback, useState } from react; import { Table, Input, Button, Space, Modal } from antd; import { useUsers, useDeleteUser } from /hooks/useUsers; import { User } from /types/user; import { UserTableActions } from ./UserTableActions; // 拆分的操作栏组件 import { columns } from ./columns; // 拆分的列配置 const { Search } Input; export const UserTable: React.FC () { const [searchText, setSearchText] useState(); const [currentPage, setCurrentPage] useState(1); const [selectedRowKeys, setSelectedRowKeys] useStateReact.Key[]([]); const { data, isLoading, error } useUsers({ page: currentPage, search: searchText, }); const { mutate: deleteUser, isPending: isDeleting } useDeleteUser(); const handleSearch useCallback((value: string) { setSearchText(value); setCurrentPage(1); // 搜索时重置到第一页 }, []); const handleDelete useCallback((id: number) { Modal.confirm({ title: 确认删除, content: 此操作不可恢复确定删除该用户吗, onOk: () deleteUser(id), }); }, [deleteUser]); // 渲染逻辑变得清晰简洁 if (error) return div加载失败.../div; return ( Space directionvertical sizemiddle style{{ display: flex }} UserTableActions selectedKeys{selectedRowKeys} onSearch{handleSearch} // ... 其他props / Table rowKeyid columns{columns(handleDelete)} // 列配置接收处理函数 dataSource{data?.list || []} loading{isLoading || isDeleting} rowSelection{{ selectedRowKeys, onChange: setSelectedRowKeys, }} pagination{{ current: currentPage, total: data?.total || 0, onChange: setCurrentPage, }} / /Space ); };通过这一系列重构我们将一个脆弱、耦合的AI初稿转变为了一个符合现代React最佳实践、易于测试和维护的生产级组件。AI完成了繁重的“起草”工作定义大致结构、字段、基础交互而我们通过系统化的“修复”流程注入了项目的灵魂架构、规范、健壮性。5. 常见问题与排查技巧实录在实际“规训”AI生成代码的过程中你会反复遇到一些典型问题。这里记录一份速查表。问题现象可能原因排查与修复技巧组件样式完全错乱AI使用了内联样式或与项目不同的CSS方案如生成的是style{{}}而项目用styled-components。1.预防在提示词中明确样式方案如“使用Emotion的styled语法编写样式”。2.修复将内联样式逐一替换为项目中的样式变量或styled组件。优先检查布局、颜色、间距等通用属性。状态更新无效或出现陈旧值1.useState的更新是异步的连续调用可能未按预期工作。2. 在useEffect或事件回调中依赖了未正确列入依赖数组的状态/函数。1. 对于连续状态更新使用函数式更新setCount(prev prev 1)。2. 使用ESLint的exhaustive-deps规则检查所有useEffect和useCallback的依赖数组。确保所有在内部使用的变量都正确声明。组件无限重渲染1. 在渲染函数中直接创建了新的对象/函数并作为props传递给子组件。2.useEffect的依赖数组为空[]但其内部函数引用了会变化的状态或props。1. 使用useMemo缓存对象使用useCallback缓存函数。2. 将useEffect内部用到的函数或值要么移入useEffect内部要么正确加入依赖数组。使用useCallback包装函数以稳定其引用。TypeScript报一堆类型错误AI使用了宽泛的any类型或引用了不存在的类型/模块。1.定位从第一个错误开始修后面的错误可能随之解决。2.修复根据项目实际类型定义手动替换any。检查导入路径是否正确。可以临时使用ts-ignore但需后续补上正确定义。网络请求重复发送或竞态条件AI使用了多个独立的useEffect来获取数据且依赖管理不当。1.根治用TanStack Query或SWR替代手动的useEffectfetch。它们内置了缓存、去重和竞态处理。2.临时方案使用AbortController在组件卸载或依赖变化时取消未完成的请求。生成的代码无法被测试组件逻辑与UI耦合太紧且没有提供测试查询的选择器。1. 将业务逻辑抽离到自定义Hook中Hook是纯函数极易测试。2. 在关键DOM元素上添加>