如果你是一名开发者,或者对过去几年中的人工智能发展有所了解,那么“Vibe Coding”这个术语对你来说应该并不陌生。这是一种软件开发方法:你用通俗易懂的语言向人工智能模型描述自己的需求,而它则会为你生成相应的源代码。

你不需要逐行手动编写代码,只需专注于功能、外观等设计要素,人工智能就会为你生成实际的代码。这种方法既神奇又高效。

和数百万其他软件开发人员一样,我也广泛使用人工智能技术,并积极倡导将其应用到实际工作中。我们应该把人工智能作为一种工具,用来加快开发进度、完成重复性任务、处理那些繁琐的工作流程——总之,凡是人工智能能够帮助我们提高工作效率的事情,我们都应该利用它。

但是,在使用这些人工智能技术时,我们必须保持警惕,尤其是在将人工智能生成的代码交付给客户之前。

像Claude、Gemini或ChatGPT这样的现代人工智能工具都会事先提醒用户:它们可能会犯错。作为使用者,我们必须在使用这些工具之前仔细核对它们生成的结果。这里有一则来自Claude的类似提示:

Claude AI提示

关键信息就是:不要盲目信任人工智能生成的代码。在将其投入实际生产环境之前,你必须进行充分的验证和测试。

为了说明这一点,在这篇文章中,我将分享一个最近做的案例研究——如何利用人工智能为某个分析仪表盘应用程序生成React源代码。

人工智能确实为我生成了一些没有错误的源代码,我可以直接运行这些代码来查看程序的功能。但当我进一步深入研究这些代码时,我发现其中存在一些潜在的错误和需要优化的地方。这些生成的代码远未达到可投入生产的环境标准,还需要进行大量的重构工作。

这份指南也以视频教程的形式提供在Full-Stack: Vibe Coding to Production Ready系列中。如果你感兴趣,可以去观看哦:

让我们开始吧。

目录

  1. 提示语

  2. 生成的React代码

  3. 仪表盘应用程序

  4. 代码分析与问题识别

  5. 重构人工智能生成的代码

  6. 给你的一个小任务

  7. 关键要点总结

  8. 如果你已经读到了这里……

提示信息

首先,我们需要一条明确的提示信息,用通俗易懂的英语告诉人工智能系统,它应该为“分析面板”生成相应的源代码。

以下就是这条提示信息,请仔细阅读:

请扮演一位经验丰富的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行以上,而这些代码将变得难以维护。

我们的任务并不是拒绝人工智能生成的代码,而是要对这些代码进行重构,使其具备上线运行的条件。只有当你掌握了扎实的基础知识,并且真正理解了人工智能时代软件工程的新定义时,才能做到这一点。

如果你已经读到了这里……

非常感谢你!

我很高兴地宣布,我开设了一门全栈免费课程,旨在帮助开发者建立起适合实际生产环境的技术思维模式。如果你能抽时间学习这门课程,我会感到非常高兴。

  • 请订阅我的YouTube频道

  • 也请在LinkedInX上关注我

  • 欢迎阅读我的《React清洁代码规则手册》

  • 本文中使用的所有源代码都可以在我的GitHub仓库中找到。

期待在下一篇文章中与大家再见。在此之前,祝你们一切安好,继续努力学习吧!

Comments are closed.