最近在跟风用Cursor写一个内部管理系统的前端,用的React 18 + TypeScript。但发现AI生成的代码里,动不动就给我整class组件和componentDidMount这种老写法,或者用ReactDOM.render那种已经废弃的API。我明明在项目里设了React版本和tsconfig,也试过在prompt里强调“用hooks”,但效果不稳定。是不是我项目配置哪里没给对?还是说应该加个什么规则文件?求老哥指点一下,不想每次都得手动改一遍。
用Cursor写React组件,AI老爱生成过时语法怎么破?
全部回复
共 3 条这个坑我太熟了,去年带团队做内部工具平台的时候,前后被AI生成的class组件坑了不下二十次,搞得我们一度想放弃Cursor回归纯手写,后来花了两周时间专门研究怎么驯服这个模型,才把生成准确率从不到60%拉到85%以上。先直接回答你那个“是不是项目配置没给对”的问题——大概率不是,因为Cursor读取的项目配置(tsconfig、package.json里的react版本、甚至你写的当前文件import语句)其实对生成风格的影响非常有限,它本质上是一个基于对话上下文的概率模型,你给它的prompt越模糊,它就越倾向于输出训练数据里出现频率最高的那些代码模式,而class组件和ReactDOM.render在它的训练语料里占据了绝对优势,因为我们都知道过去五年网上流传的React教程、StackOverflow答案、开源项目里,class组件至少占了七成以上。
我踩过的第一个大坑就是以为只要在项目根目录放一个.cursorrules文件或者写清楚系统提示就能一劳永逸。实际上,.cursorrules对生成风格的控制力远没有文档里吹的那么强,它更像是一个“建议”而非“约束”。我最开始写的是“Always use functional components with React Hooks, never use class components. Use createRoot instead of ReactDOM.render.” 结果它该生成class组件还是生成,只是偶尔会在生成完后补一句“Note: you could refactor this to use hooks”之类的废话。后来我测试了二十多种prompt写法,发现一个比较有效的模式是:在每次写新组件之前,先让它生成一段“当前项目的技术栈摘要”,比如你可以在对话里先打一段“This project uses React 18.2.0 with TypeScript 5.3. We strictly follow functional component pattern with useState, useEffect, useCallback hooks. All rendering uses ReactDOM.createRoot. No class components allowed. Here is an example of our preferred component structure:”然后贴一个你自己手写的典型组件。这个做法之所以有效,是因为它利用了模型的“few-shot learning”能力——你给了它一个非常具体的参照物,它后续生成的代码会更倾向于模仿那个样例的风格,而不是去翻它记忆库里的老代码。
但光靠这个还不够,因为模型有时候会在生成长序列的中途“遗忘”你的风格要求。我后来摸索出来的一个比较稳的套路是:把组件拆成小块来生成。比如你要写一个表格组件,不要让它一口气生成整个文件,而是先让它生成类型定义和接口,生成完了你确认没问题,再让它生成状态管理部分,最后才是渲染逻辑。每次只给它5到10行的上下文,并且每次都在prompt里重申一次“use functional component with hooks”。虽然听起来很繁琐,但实际上做习惯了之后,生成一个中等复杂度的组件只需要多花两三分钟,但后期省掉的手动改代码时间是以小时计的。
另外还有一个很少有人提到的点是:Cursor对React版本的“理解”其实依赖于它内部的知识截止日期。我查过一些资料,目前主流的大模型训练数据基本截止在2023年初,而React 18的createRoot是在2022年3月正式发布的,class组件虽然在React 18里仍然能用,但官方文档从17开始就已经把createRoot作为推荐方式了。问题是,模型在训练时看到的代码样本里,ReactDOM.render出现的频率可能是createRoot的几十倍,所以它天然倾向于输出那个它更“熟悉”的API。这个问题的本质不是Cursor的bug,而是训练数据的统计偏差。解决思路有两个方向:一是你可以考虑在项目里安装一个eslint插件,比如eslint-plugin-react-hooks和eslint-plugin-react的jsx-uses-react规则,并且在Cursor的设置里启用“Lint on save”或者“Diagnostics”功能。这样当AI生成了class组件或者过时API时,eslint会立刻报红,你甚至不用手动检查就能发现。我试过配合cursor的“fix with AI”功能,让AI自己根据eslint错误去修正代码,效果比直接生成要好很多,因为eslint的错误信息给了一个非常明确的修正目标,模型反而不会跑偏。
还有一个更进阶的技巧是写一个自定义的代码片段模板。我在团队内部推了一个做法:在项目里建一个名为.cursor-templates的文件夹,里面放几个我们最常用的组件模板文件,比如BaseTable.tsx.template、BaseForm.tsx.template,每个模板都是最规范的React 18 + TypeScript写法,并且用注释标记出动态部分。每次需要生成新组件时,先让AI“参考”这个模板文件来生成,prompt写成“Refer to the template in .cursor-templates/BaseTable.tsx.template and create a similar component for user list with columns: id, name, email, status, actions”。这个方法的好处是,模板本身是你自己手写的最优实践,AI只能模仿结构而无法篡改风格,基本上杜绝了class组件冒出来的可能。
我印象最深的一次翻车是做一个权限管理页面,AI一口气生成了大概200多行代码,里面混了三个class组件和一个用componentWillReceiveProps的生命周期方法,而那个生命周期早在React 16.3就被标记为不安全了。那一次我花了将近一个小时才把所有问题改完,当时气得差点把电脑砸了。后来我反思了一下,那次的失败是因为我在prompt里只说了“用React 18”,但没有明确禁止class组件,而且我是在一个已经有很多历史代码的项目里用Cursor,那个项目的早期部分确实有class组件,模型可能误以为那是我想要的风格。从那以后我养成了一个习惯:每次开始新的对话前,先手动清理一下对话历史——如果之前聊过class组件相关的内容,最好直接开一个新对话,否则模型会认为你在延续之前的代码风格。
还有一个容易被忽略的点是Cursor的模型选择。目前Cursor默认用的是GPT-4或者Claude 3.5,但不同模型的React知识更新程度不同。我个人测试下来,Claude 3.5在生成React 18代码时的准确率比GPT-4略高,尤其是对createRoot和并发模式的掌握更好。如果你用的是Cursor的旧版本,可以检查一下设置里有没有切换模型的选项,如果有的话,建议优先用Claude 3.5或者最新的GPT-4 Turbo。但要注意,即使换了模型,上述的prompt技巧和模板策略依然很重要,因为训练数据的根本问题并没有消失。
最后说一个比较反直觉的经验:不要完全信任AI生成的任何import语句。我见过它在一个文件里同时import了ReactDOM和react-dom/client,然后用ReactDOM.render来渲染,却把createRoot闲置在那儿。这种低级错误在AI生成的长代码里特别常见,因为它可能在生成import时用的是某个上下文,生成渲染逻辑时又忘了前面写了什么。我的做法是每次生成完代码后,手动检查并整理import部分,把所有废弃的API替换掉,顺便删掉那些AI乱加的未使用引用。这个检查过程其实很快,熟练了之后一分钟就能搞定,但能避免九成以上的运行时错误。
总结一下我现在的标准流程:新对话开始前,先写一段包含精确技术栈描述和禁止class组件的系统提示,附上一个手写的组件模板作为few-shot示例;生成时采用小步快跑的策略,每次只生成一个功能模块,并在每个prompt里重申风格要求;生成后立即用eslint检查并让AI根据lint错误修正;最后手动过一遍import语句。这套流程让我从最初的手动修改率超过40%降到了现在的不到10%。当然,完全消除AI的过时代码输出是不可能的,毕竟它本质是一个统计模型而不是编译器,但通过上面这些方法,至少能把烦人的class组件和ReactDOM.render出现频率压到可以接受的水平。你要是愿意花点时间把这些trick固化到团队的工作流里,甚至可以写到CONTRIBUTING.md里,后面新来的同事用Cursor也能直接上手,不用再走一遍你踩过的坑。
同感,这个问题其实跟项目配置关系不大,主要是Cursor对React 18+的上下文理解不够深。我试过在项目根目录加个.cursorrules,把"使用函数组件和hooks,禁止class组件和ReactDOM.render"写进去,效果比prompt稳定很多。另外建议把eslint-plugin-react-hooks装好并启用规则,AI生成的代码如果不符合规范,保存时直接标红,逼它改。
试试在项目根目录加个.cursorrules文件,把React版本和hooks偏好写进去,效果比prompt稳定多了。