现在很多前端开发遇到 Bug,第一反应已经不是先去 Stack Overflow,也不是翻半天官方文档,而是直接把代码丢给 AI。
但问题也随之出现了:
明明都是 AI,为什么有时候一个模型能直接指出问题,另一个模型却会给你改一大堆代码,最后 Bug 还在?
这次就拿一个 React 开发里非常常见、而且特别容易被 AI “带跑偏”的问题,做一次同题模拟对比。
为了避免把不同版本、不同时间的模型表现写死,下面主要比较 DeepSeek、豆包和 GPT 在这类问题中的典型分析路径和解决思路,不把它当成绝对排名。
一、先看这个 React Bug
假设页面里有这样一段代码:
import React, { useEffect, useState } from "react"; export default function UserList() { const [users, setUsers] = useState([]); const [keyword, setKeyword] = useState(""); const params = { keyword, page: 1 }; useEffect(() => { fetch("/api/users?" + new URLSearchParams(params)) .then(res => res.json()) .then(data => { setUsers(data.list); }); }, [params]); return ( <div> <input value={keyword} onChange={e => setKeyword(e.target.value)} placeholder="搜索用户" /> {users.map(user => ( <div key={user.id}>{user.name}</div> ))} </div> ); }乍一看,好像没什么问题。
逻辑也非常简单:
用户输入关键词 →keyword改变 → 请求接口 → 更新列表。
但实际运行以后,你可能会发现一个很离谱的现象:
接口一直请求。
哪怕用户什么都没操作,Network 里也可能连续出现请求。
如果接口响应比较快,甚至有机会直接把后端接口刷爆。
二、问题到底出在哪?
真正的问题其实就在这里:
const params = { keyword, page: 1 };以及:
useEffect(() => { // ... }, [params]);很多刚开始写 React 的开发者第一眼并不会意识到问题。
因为从人的角度看:
{ keyword: "", page: 1 }和下一次渲染得到的:
{ keyword: "", page: 1 }内容明明完全一样。
但 React 在判断依赖是否变化的时候,并不会帮你做对象内容的深度比较。
每次组件重新渲染时:
const params = { keyword, page: 1 };都会创建一个新的对象引用。
也就是说:
{} !== {}所以 React 会认为:
params 又变了。
于是整个过程变成:
组件渲染 ↓ 创建新的 params ↓ useEffect 发现 params 变化 ↓ 请求接口 ↓ setUsers ↓ 组件重新渲染 ↓ 又创建新的 params ↓ useEffect 再执行 ↓ 继续请求这才是真正的根因。
三、把这个问题交给不同 AI,会发生什么?
这里才是我觉得比较有意思的地方。
因为这种 Bug 本身并不难。
真正能拉开体验差距的,往往不是:
AI 知不知道 useEffect。
而是:
AI 能不能迅速判断“哪一行代码才是真正的根因”。
四、DeepSeek:答案通常比较直接
如果把整段代码和“为什么接口一直请求”这个问题交给 DeepSeek,比较容易得到类似这样的分析:
params是一个对象,每次组件重新渲染都会创建新的引用,而useEffect依赖了params,因此 React 会认为依赖发生变化,导致 Effect 重复执行。
然后一般会给出几个解决方案。
例如直接修改依赖:
useEffect(() => { const params = { keyword, page: 1 }; fetch("/api/users?" + new URLSearchParams(params)) .then(res => res.json()) .then(data => { setUsers(data.list); }); }, [keyword]);这个答案其实已经能解决问题了。
对于这种单文件、问题明确、上下文比较少的 Bug,DeepSeek 的表现通常已经完全够用。
而且它的优势很明显:
中文解释比较自然。
对于国内开发者来说,直接把报错、代码和需求一起扔进去,使用门槛很低。
五、豆包:解释会更偏“教学型”
豆包面对这种问题时,一个比较明显的特点是:
它往往会把问题拆得更细。
比如不仅告诉你:
params 每次渲染都会重新创建。
还可能顺便解释:
- 什么叫引用类型
- useEffect 怎么比较依赖
- 为什么数组和对象容易触发类似问题
useMemo是干什么的- 为什么不能随便把对象放进依赖数组
例如可能给出这样的改法:
const params = useMemo(() => { return { keyword, page: 1 }; }, [keyword]);然后:
useEffect(() => { fetch("/api/users?" + new URLSearchParams(params)) .then(res => res.json()) .then(data => setUsers(data.list)); }, [params]);这同样是正确方案。
对于刚接触 React 的开发者来说,这种回答其实挺舒服。
因为它不是只告诉你:
“改这里。”
而是会解释:
“为什么要改这里。”
六、GPT 的区别,往往不是“知道答案”
再看 GPT。
如果只是问:
为什么这个 useEffect 会无限请求?
其实三个模型都有很大概率答出来。
所以真正值得看的,并不是这一道题谁会做。
而是继续往下追问:
这个项目实际开发中应该怎么改?
这时候答案开始出现差异。
比较成熟的分析通常不会急着告诉你:
useMemo而是先判断:
这个 params 有必要存在吗?
因为从当前代码看:
const params = { keyword, page: 1 };只是为了发请求。
那最简单的方案其实不是增加一个useMemo。
而是直接把真正影响请求的状态作为依赖:
useEffect(() => { const searchParams = new URLSearchParams({ keyword, page: "1" }); fetch(`/api/users?${searchParams}`) .then(res => res.json()) .then(data => { setUsers(data.list); }); }, [keyword]);反而更简单。
这也是我觉得 AI 编程里一个很容易被忽略的问题:
能修好 Bug,不等于修改方案就是最合理的。
七、为什么“疯狂 useMemo”不一定是好答案?
看到对象引用变化以后,很多人会下意识写:
const params = useMemo(() => ({ keyword, page: 1 }), [keyword]);技术上当然没问题。
但如果整个项目里一碰到对象就useMemo,代码很快就会变成:
const params = useMemo(...) const options = useMemo(...) const config = useMemo(...) const filter = useMemo(...)最后组件里到处都是缓存。
这其实属于:
为了 Hooks 而 Hooks。
真正应该考虑的是:
这个对象是不是一定需要成为组件渲染阶段的变量?
如果只是某一个 Effect 内部使用,很多时候直接放进 Effect 里反而更加清晰。
这类“代码能不能继续简化”的判断,才是我认为现在不同 AI 编程体验真正容易拉开差距的地方。
八、再继续追问一步:其实还有隐藏问题
如果这是一段真实业务代码,我还会继续问 AI:
用户快速输入关键词怎么办?
比如用户连续输入:
a ab abc abcd那就可能瞬间发送四次请求。
甚至还可能出现:
abcd 的请求先回来 abc 的请求后回来结果旧数据把新数据覆盖了。
这时候问题已经不再是一个简单的:
useEffect 无限循环。
而变成:
- 是否需要防抖
- 是否需要取消旧请求
- 如何避免竞态
- loading 状态怎么处理
- 请求失败怎么处理
- 组件卸载以后是否还需要更新状态
真正用于生产环境的代码,可能会继续演进成:
useEffect(() => { const controller = new AbortController(); const timer = setTimeout(async () => { try { const searchParams = new URLSearchParams({ keyword, page: "1" }); const response = await fetch( `/api/users?${searchParams}`, { signal: controller.signal } ); const data = await response.json(); setUsers(data.list); } catch (error) { if (error.name !== "AbortError") { console.error(error); } } }, 300); return () => { clearTimeout(timer); controller.abort(); }; }, [keyword]);你会发现:
最开始明明只是一个:
“为什么接口一直请求?”
最后其实可以延伸出完整的前端请求设计问题。
九、这才是我现在判断 AI 编程能力的方法
以前大家喜欢问:
哪个 AI 写代码最强?
但我现在越来越觉得,这个问题本身就太宽泛了。
因为如果只是:
写一个冒泡排序或者:
写一个 React TodoList现在很多模型都能完成。
真正应该测试的是:
第一,能不能找到根因
不是看到报错就开始改代码。
第二,能不能减少无意义修改
一个 Bug 最怕 AI 一次改十几个文件。
最后:
原来的问题没解决,又创造三个新问题。
第三,能不能理解真实项目环境
Demo 和真实业务完全是两回事。
真实项目还有:
- 状态管理
- 权限
- 接口封装
- TypeScript
- ESLint
- 路由
- 请求缓存
- 历史代码
- 团队规范
第四,能不能继续往下发现问题
优秀的 AI 不应该只回答:
“这一行错了。”
而应该能进一步提醒你:
即使修改这一行,当前请求逻辑还有竞态风险。
这种能力在真正开发项目时,价值要高很多。
十、DeepSeek、豆包和 GPT,到底怎么选?
如果只是这一个 React Bug,我觉得完全没必要神化任何模型。
DeepSeek 能解决。
豆包也能解决。
GPT 同样能解决。
真正的区别,往往会随着任务复杂度增加才逐渐出现。
如果你的使用场景只是:
- 看报错
- 写 SQL
- 查 API
- 写一个函数
- 解释代码
国内 AI 对很多开发者来说已经非常够用了。
但如果开始进入:
- 大段代码分析
- 连续 Debug
- 多轮修改
- 复杂业务逻辑
- 项目级重构
- 多文件上下文
- 长时间协作开发
这时候才更值得认真比较不同模型。
我自己比较建议的是:不要一上来就纠结“谁最强”,而是把自己平时最真实的代码丢进去跑几天。
尤其是一直在用 DeepSeek、豆包,但还没有认真体验过 GPT 的开发者,实际拿同一个项目对比一次,感受会比看各种模型跑分直观得多。
这类东西没必要跟风。
能不能真正提高自己的开发效率,才是最重要的。
最后
这次这个 React Bug 本身其实很简单。
真正让我觉得有意思的,反而是一个变化:
以前程序员遇到 Bug,会搜索“这个问题怎么解决”;现在越来越多的人开始思考“这个问题应该交给哪个 AI 解决”。
而下一阶段,真正拉开效率差距的可能也不是:
你有没有使用 AI。
而是:
你能不能让 AI 真正理解你的项目,而不是只会让它生成几段看起来能运行的代码。
下一篇我准备继续拿一个更接近真实项目的问题测试:
“同一个 Spring Boot 接口响应变慢的问题,DeepSeek、豆包和 GPT 谁能真正定位到原因?”