如果你是一名开发者,或者对过去几年中的人工智能发展有所了解,那么“Vibe Coding”这个术语对你来说应该并不陌生。这是一种软件开发方法:你用通俗易懂的语言向人工智能模型描述自己的需求,而它则会为你生成相应的源代码。
你不需要逐行手动编写代码,只需专注于功能、外观等设计要素,人工智能就会为你生成实际的代码。这种方法既神奇又高效。
和数百万其他软件开发人员一样,我也广泛使用人工智能技术,并积极倡导将其应用到实际工作中。我们应该把人工智能作为一种工具,用来加快开发进度、完成重复性任务、处理那些繁琐的工作流程——总之,凡是人工智能能够帮助我们提高工作效率的事情,我们都应该利用它。
但是,在使用这些人工智能技术时,我们必须保持警惕,尤其是在将人工智能生成的代码交付给客户之前。
像Claude、Gemini或ChatGPT这样的现代人工智能工具都会事先提醒用户:它们可能会犯错。作为使用者,我们必须在使用这些工具之前仔细核对它们生成的结果。这里有一则来自Claude的类似提示:
![]()
关键信息就是:不要盲目信任人工智能生成的代码。在将其投入实际生产环境之前,你必须进行充分的验证和测试。
为了说明这一点,在这篇文章中,我将分享一个最近做的案例研究——如何利用人工智能为某个分析仪表盘应用程序生成React源代码。
人工智能确实为我生成了一些没有错误的源代码,我可以直接运行这些代码来查看程序的功能。但当我进一步深入研究这些代码时,我发现其中存在一些潜在的错误和需要优化的地方。这些生成的代码远未达到可投入生产的环境标准,还需要进行大量的重构工作。
这份指南也以视频教程的形式提供在Full-Stack: Vibe Coding to Production Ready系列中。如果你感兴趣,可以去观看哦:
让我们开始吧。
目录
提示信息
首先,我们需要一条明确的提示信息,用通俗易懂的英语告诉人工智能系统,它应该为“分析面板”生成相应的源代码。
以下就是这条提示信息,请仔细阅读:
请扮演一位经验丰富的React开发者。
我需要为一个使用React开发的视频平台创建一个功能齐全的“创作者分析面板”。该面板应包含以下内容:
- 1. 一个显示用户个人信息的页头。
- 2. 三张汇总卡片,分别展示总观看次数、视频数量和评论数。
- 3. 一张数据表格,列出最近发布的视频及其各项统计数据。
- 4. 一个包含导航选项的侧边栏。
请使用Tailwind CSS进行样式设计。
通过`fetch`函数获取模拟数据,设置1秒的超时时间来模拟网络请求过程。
最后,请确保生成的代码便于我直接复制粘贴使用。
这条提示信息明确要求使用React和TailwindCSS来创建一个具备多种功能的“创作者分析面板”。
现在,如果我们把这条提示信息提供给ChatGPT、Claude或Gemini等人工智能工具,它们就会自动为我们生成相应的源代码。
生成的React代码
以下是人工智能根据提示信息生成的React代码。可以看到,这是一个包含状态管理、副作用处理以及JSX代码的完整React组件。
import { useEffect, useState } from "react";
export default function CreatorDashboard() {
const [stats, setStats] = useState({ views: 0, subs: 0, revenue: 0 });
const [videos, setVideos] = useState [];
const [comments, setComments] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true;
const fetchData = async () => {
try {
await new Promise((resolve) => setTimeout(resolve, 1000));
if (isMounted) {
setStats({ views: '1.2M', subs: '45K', revenue: '$3,400' });
setVideos([
{ id: 1, title: 'Vibe Coding Explained', views: '100K' },
{ id: 2, title: 'React 19 Features', views: '85K' }
]);
setComments([{ id: 1, text: 'Great video!' });
setIsLoading(false);
}
} catch (err) {
setError('Failed to fetch data');
setIsLoading(false);
}
};
fetchData();
return () => { isMounted = false; };
}, []);
if (isLoading) return 正在加载分析面板…;
if (error) return 发生错误:{error};
return (
tapaScript
- 分析面板
- 数据分析
创作者分析面板
Tapas Adhikary
总观看次数
{stats.views}
最近发布的视频
{videos.map(v => {v.title})}
最新评论
{comments.map(c => )}
);
}
请注意,如果你再次使用相同的提示来请求生成代码,得到的源代码会略有不同。因为大型语言模型的响应具有随机性和不确定性,所以在多次请求相同提示时,它可能会产生不同的结果。
好了,让我们来试试这些生成的代码吧。
仪表盘应用
现在,将这段由人工智能生成的代码复制到任何React项目中。运行后,你应该会看到一个功能齐全的Creator Analytics仪表盘,其各项功能都与提示中描述的一致。

这真是太神奇、太强大啦。作为开发者,我们必须充分利用这一工具。但同时,我们也需要发挥“人类监督者”的作用,确保生成的代码具有模块化结构、可扩展性,并且没有漏洞。
现在,让我们来逐步分析这段由人工智能生成的代码吧。
代码分析与问题识别
在继续阅读之前,请先再次仔细阅读生成的源代码。这次要像进行代码审查一样,慢慢地、认真地阅读每一行代码。
你发现了什么问题吗?让我们看看你的发现是否与我案例研究中的问题列表相符。
问题1:“上帝组件综合征”
在软件工程中,我们有单一职责原则。这个原则意味着一个函数或组件应该只负责完成一件事情。
但在我们的CreatorDashboard中,它却扮演了“上帝组件的角色”。它既负责管理状态,也从网络中获取数据,还要渲染侧边栏、页眉、卡片、表格……几乎所有的内容。
如果市场部门要求你在营销页面上重复使用那个统计图表,你是根本无法做到的。因为这段代码被锁在了那个庞大的文件里,你必须重新编写它。
问题2:状态管理混乱的问题
看看这个组件的顶部,有五处不同的useState声明。当一个组件被渲染时,要弄清楚到底是哪一段代码导致了它的渲染,简直是一件麻烦事。这些状态应该被归类整理,或者更好的做法是,让像TanStack Query这样的专用数据获取库来负责管理它们。
请记住,在你的组件中管理的状态越少,作为React开发者,你的工作就会越轻松。
问题3:错误的数据获取方式
人工智能很喜欢使用useEffect来处理数据获取操作。但实际上,这是现代React开发中最大的反模式之一。因为useEffect本来就不是为数据获取而设计的——它不支持缓存机制,也无法在网络连接中断时重新尝试请求数据;而且当用户离开页面后再返回时,系统每次都会强制重新加载数据。
现代版的React提供了更高效的数据获取机制。我编写了一篇《关于如何使用Suspense和Error Boundary来处理数据获取的操作指南》,你可以去阅读一下。
问题4:类型缺失的问题
在初始要求中我们并没有明确提到TypeScript,因此AI默认生成的是JavaScript代码。现在的问题是:我们能确定`videos`数组中会包含什么类型的元素吗?视频对象的具体结构又是怎样的呢?我们并不知道,我们的编辑器也无法帮助我们解决这些问题。
重构AI生成的代码
既然我们已经发现了这些问题,下一步理所当然的就是对代码进行重构,使其更加完善。
重构策略
下图展示了我们将采用的重构策略。我们会将这个庞大的AI生成组件拆分成一些结构更清晰、规模更小的组件,比如Header、Sidebar、RecentComments等等。
我们还需要处理组件外部的数据,并使数据获取机制能够被应用程序中的其他组件重复使用。为此,我们将采用自定义钩子模式来实现这一目标。

定义类型
首先,我们需要为各种数据对象定义相应的类型。例如,视频的状态、评论信息以及创作者的整体状态等,都需要有明确的类型定义。
// 在TypeScript中,我们可以使用“type”或“interface”来定义对象的结构。
export interface CreatorStat {
label: string;
value: string | number;
}
export interface VideoStats {
id: string; // ID应该是字符串(UUID)或数字,这里我们强制规定它必须是字符串
title: string;
views: number;
publishedAt: string;
}
export interface Comment {
id: string;
author: string;
text: string;
createdAt: string;
}
拆分庞大的组件结构
接下来,我们要解决违反单一职责原则的问题,以及CreatorDashboard这个组件过于复杂、难以维护的问题。我们需要将这个庞大的组件拆分成多个较小的组件:
- Header:这个组件用于表示分析面板的顶部标题。
function Header() {
return (
创作者分析面板
塔帕斯·阿迪卡里
);
}
export default Header;
- 侧边栏:侧边栏组件用于显示导航链接。
export default function Sidebar() {
return (
tapaScript
-
仪表盘
-
分析数据
);
}
- 统计信息卡片:该组件接收状态标签和值,并将它们显示出来。请注意,我们在标签和值的属性上应用了类型约束。
// 1. 我们定义了Props接口。
// “Props”是指传递给React组件的参数。
// 我们要求使用该组件的所有人都必须提供标签和值。
interface StatCardProps {
label: string;
value: string | number;
}
// 2. 我们通过解构赋值清晰地提取这些属性:{ label, value }
function StatCard({ label, value }: StatCardProps) {
return (
{label}
{value}
);
}
export default StatCard;
- 视频列表组件:该组件用于显示所有视频信息,因此它接收一个视频数组作为参数。请注意,这里已经解决了类型匹配的问题——现在我们明确知道,数组中的每个元素都属于之前定义的
VideoStats类型。
import type { VideoStats } from '../types';
interface VideoTableProps {
// 我们期望接收一个VideoStats对象数组。
videos: VideoStats[];
}
function VideoTable({ videos }: VideoTableProps) {
if (videos.length === 0) {
return 尚未上传任何视频。;
}
return (
最新视频
{videos.map((video) => (
-
{video.title}
{video.views.toLocaleString()} 次观看
))}
);
}
export default VideoTable;
- 最近评论:用于显示评论列表的组件。
import type { Comment } from "../types";
interface RecentCommentProps {
// 我们期望收到一个由 Comment 对象组成的数组。
videos: Comment[];
}
function RecentCommentList({ comments }: RecentCommentProps) {
if (comments.length === 0) {
return (
您还没有发布任何评论。
);
}
return (
最近评论
{comments.map((c) => (
{c.text}
))}
);
}
export default RecentCommentList;
用于处理数据的自定义钩子
现在我们已经定义了所有的组件,而这些组件都只是用于展示数据的组件,因此它们需要数据才能在仪表板上显示信息。同时,我们也不希望在这些组件内部处理所有的状态变化。在这种情况下,使用自定义钩子是一个非常好的选择。
这个钩子会负责发起请求来获取分析数据,并通过状态变量来跟踪这些数据。我们从钩子中返回所需的状态值,这样任何在使用这个钩子的地方都能获得这些信息。这个钩子是完全可复用的。
import { useEffect, useState } from "react";
import type { Comment, CreatorStat, VideoStats } from "./types";
export function useDashboardData() {
const [stats, setStats] = useState:();
const [videos, setVideos] = useState);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true;
const fetchData = async () => {
try {
// 模拟 API 请求
await new Promise((resolve) => setTimeout(resolve, 1000));
if (isMounted) {
setStats([
{ label: "观看次数", value: "120万" },
{ label: "订阅者数量", value: "4.5万" },
{ label: "收入", value: "$3,400" },
]);
setVideos([
{
id: 1,
title: "Vibe Coding Explained",
views: "10万",
},
{ id: 2, title: "React 19的新特性", views: "8.5万" },
];
setComments([
{ id: 1, text: "很棒的视频!" },
{ id: 2, text: "非常精彩的视频!" },
]);
setIsLoading(false);
}
} catch (err) {
setError(`获取数据失败:${err?.message}`);
setIsLoading(false);
}
};
fetchData();
return () => {
isMounted = false;
};
}, []);
return {
stats,
videos,
comments,
isLoading,
error
};
}
整体重构
最后,是时候对那个庞大的CreatorDashboard组件进行改造了。首先,我们需要导入所有之前创建的子组件,然后通过钩子函数获取统计数据、视频内容、评论信息以及加载状态和错误信息。之后,就可以将这些数据运用到页面中了。
import Header from "@/components Header";
import Sidebar from 「@components/Sidebar」;
import RecentCommentList from "./components/RecentComments";
import StatCard from "./components/StatCard";
import VideoTable from "./components/VideoTable";
import { useDashboardData } from "./hooks/useDashboardData";
export default function CreatorDashboard() {
const { stats, videos, comments, isLoading, error } = useDashboardData();
if (isLoading)
return (
正在加载数据...
);
if (error) return {error}</div>>;
return (
{/* 侧边栏导航 */}
{/* 页头 */}
{/* 统计数据展示 */}
{stats.map((stat) => (
))}
{/* 数据表与评论信息合并显示 */}
);
}
就这样。我们现在已经成功地将这个庞大的AI生成组件拆分成了多个可复用的子组件,并将数据获取逻辑与状态处理功能分离到了这些子组件之外。
给你的一项任务
这项任务是可选的,但我鼓励你尝试一下。它的目标是将之前的重构工作提升到一个新的层次。
你能否去掉useDashboardData这个钩子函数,而是直接使用Suspense和Error Boundary模式来处理数据获取逻辑呢?我很乐意与你一起探讨解决方案。请通过下面的社交渠道联系我,或者加入我的Discord服务器。
另外,请继续关注我即将发布的文章,在那篇文章中,我会使用TanStack Query来重新构建同一个应用,并向你介绍数据获取、数据修改以及缓存的相关技术。
关键要点
这就是人工智能生成的代码的现实面貌。表面上看,这些代码就像是已完成的产品;但实际上,它们就像是一堆脆弱的多米诺骨牌结构。如果你试图对这些代码进行扩展,比如添加认证机制、对数据表进行排序,或者实现实时评论功能,那么文件中的代码行数将会增加到1000行以上,而这些代码将变得难以维护。
我们的任务并不是拒绝人工智能生成的代码,而是要对这些代码进行重构,使其具备上线运行的条件。只有当你掌握了扎实的基础知识,并且真正理解了人工智能时代软件工程的新定义时,才能做到这一点。
如果你已经读到了这里……
非常感谢你!
我很高兴地宣布,我开设了一门全栈免费课程,旨在帮助开发者建立起适合实际生产环境的技术思维模式。如果你能抽时间学习这门课程,我会感到非常高兴。
期待在下一篇文章中与大家再见。在此之前,祝你们一切安好,继续努力学习吧!



