1. 项目概述:一个普通前端的半年“氛围感”实践
去年下半年,我给自己定了个小目标:不再只是被动地完成业务需求,而是主动去探索一些能让自己保持兴奋感、同时又能沉淀为真实技能的东西。我管这叫“氛围感编码”,或者更时髦点,叫Vibe Coding。这个词最近在前端圈子里挺火的,它指的是一种状态——不是为了赶Deadline而焦虑地敲代码,而是沉浸在创造、学习和解决问题的愉悦节奏里。对我而言,这就是用半年时间,围绕四个具体的、有“氛围感”的小项目,系统地走一遍从前端到“泛全栈”的实践路径。这四个项目分别覆盖了现代前端框架深度应用、TypeScript类型体操、全栈开发实战以及AI辅助编码工具链探索。它们不一定是多么宏大的系统,但每一个都试图解决一个真实的小痛点,或探索一个有趣的技术方向。接下来,我就把这半年的实践、踩过的坑和收获的“ vibe ”分享给你。
2. 核心项目一:Next.js 应用现代化重构与部署实战
2.1 项目背景与核心诉求
我接手了一个历史包袱较重的营销活动页面项目,最初是基于纯React + Webpack构建的。随着活动迭代,页面性能逐渐下降,首屏加载时间过长,且开发体验不佳。我的目标是用Next.js 15(当时最新稳定版)对其进行现代化重构,核心诉求非常明确:提升性能、改善开发者体验、并建立一套可靠的自动化部署流程。Next.js 的 App Router、服务端组件(RSC)和内置的优化功能(如图像、字体、脚本优化)是吸引我的关键。
2.2 架构升级与核心配置解析
重构的第一步是搭建新的Next.js项目骨架。这里有几个关键决策点:
- 使用 App Router 而非 Pages Router:这是面向未来的选择。App Router 基于 React Server Components,允许更灵活的数据获取和渲染策略。对于大量静态或轻度动态的营销页面,可以在服务端完成渲染,直接将HTML发送给客户端,极大减少客户端JavaScript包体积。
- TypeScript 严格模式:在
tsconfig.json中启用“strict”: true。这虽然在初期会带来不少类型错误,但能从根本上避免许多运行时错误,是提升代码质量的基石。 - Tailwind CSS 作为样式方案:为了与旧项目的样式平稳迁移并提升开发效率,选择了Tailwind。Next.js 与 Tailwind 集成非常顺畅,配合
clsx或tailwind-merge工具库,可以优雅地处理条件样式。
一个关键的配置在于next.config.js。对于从旧项目迁移的静态资源,我配置了自定义的图片优化域名,并开启了experimental.optimizeCss以进一步优化CSS输出。
// next.config.js 部分配置 const nextConfig = { images: { remotePatterns: [ { protocol: 'https', hostname: 'assets.old-project.com', // 旧项目资源域名 pathname: '/**', }, ], // 根据业务需求设置合适的设备尺寸 deviceSizes: [640, 750, 828, 1080, 1200, 1920], }, // 开启实验性CSS优化(在Next.js 15中已相对稳定) experimental: { optimizeCss: true, }, };2.3 性能优化实践与效果
性能提升是本次重构的重中之重,主要从以下几个维度入手:
- 图片优化:将所有
<img>标签替换为next/image。这是收益最明显的改动之一。Next.js Image组件会自动将图片转换为现代格式(如WebP)、按需懒加载并生成响应式尺寸。一个页面的图片总加载体积从原来的数MB下降到了几百KB。 - 字体优化:使用
next/font加载本地字体文件,它会在构建时自动将字体文件预处理并注入到静态资源中,消除布局偏移(CLS)。 - 代码分割与懒加载:利用React的
lazy和Suspense,以及Next.js动态导入import(),将非首屏必需的组件(如复杂的图表库、弹窗内容)进行懒加载。 - 服务端渲染策略选择:对于数据实时性要求不高的页面(如活动介绍页),使用
generateStaticParams进行静态生成(SSG),部署后就是纯粹的静态文件,访问速度极快。对于需要用户交互、带搜索参数的列表页,则采用服务端组件在请求时渲染。
实操心得:在迁移过程中,最大的挑战是旧组件与新的服务端组件模式的兼容。一些在客户端组件中常用的钩子(如
useState,useEffect)和浏览器API(如window,document)在服务端组件中无法使用。解决方案是使用“use client”指令将这类组件明确标记为客户端组件,并仔细规划组件树,让服务端组件尽可能位于顶层,负责数据获取和骨架渲染,客户端组件负责交互。
2.4 自动化部署与监控
项目采用 Vercel 进行部署,因其与 Next.js 的无缝集成。我将仓库与 Vercel 关联,实现了 Git 主分支推送即自动部署。更重要的是配置了性能监控:
- Core Web Vitals 监控:Vercel Analytics 提供了免费的 Core Web Vitals(LCP, FID, CLS)数据看板,让我能直观看到重构后性能的提升。
- 自定义性能打点:在关键业务链路(如页面跳转、按钮点击到数据展示)使用
performance.mark()和performance.measure()API 进行打点,并将数据上报到自建的监控平台(如使用 Sentry 或 Umami),用于分析真实用户环境下的性能表现。
部署后,首屏加载时间(LCP)平均减少了52%,交互延迟(FID)也有显著改善。整个重构过程,就像给一台老车换上了新的引擎和底盘,代码跑的更顺畅,我开发的“ vibe ”也回来了。
3. 核心项目二:深入TypeScript类型系统构建工具库
3.1 为什么需要自己的工具类型库?
在日常开发中,我们经常需要处理一些重复的类型操作,比如从API响应中提取某个字段的类型、深度可选(Partial)一个嵌套对象、或者构造复杂的表单验证类型。虽然有一些优秀的开源工具类型库(如type-fest),但自己动手实现一遍,是理解 TypeScript 类型系统最有效的方式。这个项目的目标,就是构建一个贴合自身业务习惯的TypeScript 工具类型集合。
3.2 从Utility Types源码学起
我并没有从零开始瞎写,而是选择了“站在巨人的肩膀上”——阅读并模仿lib.es5.d.ts和主流工具库中的内置类型实现。例如,实现一个DeepPartial<T>:
// 深度 Partial,让嵌套对象的所有属性都可选 type DeepPartial<T> = T extends object ? { [P in keyof T]?: DeepPartial<T[P]>; } : T; // 使用示例 interface User { id: number; profile: { name: string; address: { city: string; }; }; } type PartialUser = DeepPartial<User>; // 等价于 // { // id?: number; // profile?: { // name?: string; // address?: { // city?: string; // }; // }; // }关键点在于条件类型T extends object和递归调用DeepPartial<T[P]>。这让我深刻理解了分布式条件类型和递归类型别名的用法。
3.3 解决实际业务场景的类型挑战
接下来,我尝试用类型解决一些业务中的具体问题。场景一:基于API定义自动生成Hook的入参和返回类型。假设后端使用Swagger或类似工具生成了API类型定义,我们有一个Paths类型,描述了所有接口的路径、方法和响应。我可以写一个类型来提取特定接口的类型:
// 假设的API路径定义类型(简化版) type ApiPaths = { '/user/{id}': { get: { response: { id: number; name: string }; params: { id: number }; }; }; '/posts': { post: { body: { title: string; content: string }; response: { id: number }; }; }; }; // 提取特定接口请求参数和响应类型的工具类型 type GetApiParams<Path extends keyof ApiPaths, Method extends keyof ApiPaths[Path]> = ApiPaths[Path][Method] extends { params: infer P } ? P : never; type GetApiBody<Path extends keyof ApiPaths, Method extends keyof ApiPaths[Path]> = ApiPaths[Path][Method] extends { body: infer B } ? B : never; type GetApiResponse<Path extends keyof ApiPaths, Method extends keyof ApiPaths[Path]> = ApiPaths[Path][Method] extends { response: infer R } ? R : never; // 使用 type GetUserParams = GetApiParams<'/user/{id}', 'get'>; // { id: number } type CreatePostBody = GetApiBody<'/posts', 'post'>; // { title: string; content: string }这样,在编写请求Hook时,就能获得完美的类型提示和校验。
场景二:构造表单验证类型。结合Zod或Yup这样的运行时验证库,我们可以实现类型安全的表单。例如,定义一个表单Schema,其类型能同时用于前端TS校验和后端DTO。
import { z } from 'zod'; const loginSchema = z.object({ email: z.string().email(), password: z.string().min(6), rememberMe: z.boolean().optional(), }); // 直接导出类型供TS使用 export type LoginFormData = z.infer<typeof loginSchema>; // 在组件中使用 const LoginForm: React.FC = () => { const { register, handleSubmit } = useForm<LoginFormData>({ // ... 配置, resolver: zodResolver(loginSchema) }); // `register` 的字段名将获得完整的类型提示 };3.4 类型体操的“心流”体验与避坑指南
沉浸在这种纯粹的逻辑构建中,很容易进入“心流”状态。但也会遇到一些棘手问题:
- 类型实例化过深或无限递归:这在编写复杂的递归类型时很常见。解决方案是增加递归深度限制检查,或者重构类型逻辑,避免无限循环。TypeScript 4.5+ 对尾递归优化有更好支持。
- 性能问题:过于复杂的类型操作在大型项目中进行类型检查时可能会拖慢IDE或编译速度。应对策略是将最复杂的工具类型单独放在一个文件中,并仅在需要时导入,避免全局类型文件过于庞大。
- 可读性与文档:复杂的类型如同魔法,别人(甚至一个月后的自己)很难看懂。我的习惯是为每个复杂的工具类型添加详细的JSDoc注释,说明其用途、泛型参数和示例。
这个项目没有直接产出可见的UI,但它极大地提升了我代码的健壮性和开发效率。当看到自己编写的类型在VSCode中完美地提示出所有可能的状态和属性时,那种成就感是独特的“技术 vibe”。
4. 核心项目三:基于Supabase的全栈小应用开发
4.1 技术选型:为什么是Supabase?
我想快速验证一个全栈想法——一个简单的“个人书影音记录”应用。核心需求是:用户认证、数据CRUD、实时订阅(如新评论通知)。传统的方案需要自建或租赁服务器、配置数据库、编写API,运维成本高。Supabase进入了我的视野,它是一个开源的Firebase替代品,基于 PostgreSQL,提供了即时可用的数据库、身份认证、实时订阅、存储和边缘函数。对于个人项目或小团队快速原型,它几乎是完美的选择。其与Next.js的集成尤其出色。
4.2 项目初始化与数据库设计
首先在Supabase官网创建项目,获取API密钥和项目URL。然后在Next.js项目中安装@supabase/supabase-js和@supabase/ssr(用于服务端和客户端兼容的认证)。 数据库设计直接在Supabase的在线SQL编辑器中完成。我创建了两张表:
-- 电影记录表 create table movies ( id bigint primary key generated always as identity, user_id uuid references auth.users(id) on delete cascade not null, title text not null, rating smallint check (rating >= 1 and rating <= 5), watched_at date default now(), created_at timestamptz default now() ); -- 启用行级安全(RLS) alter table movies enable row level security; -- 创建策略:用户只能操作自己的记录 create policy "用户可管理自己的电影记录" on movies for all using (auth.uid() = user_id); -- 评论表(支持实时) create table comments ( id bigint primary key generated always as identity, movie_id bigint references movies(id) on delete cascade not null, user_id uuid references auth.users(id) on delete cascade not null, content text not null, created_at timestamptz default now() ); alter table comments enable row level security; create policy "用户可查看和创建所有评论,但只能修改自己的" on comments for all using ( auth.uid() = user_id or -- 可修改自己的 true -- 可查看所有(FOR SELECT) ) with check (auth.uid() = user_id); -- 创建/更新时必须是自己Supabase的Row Level Security (RLS)功能非常强大,通过SQL策略就能在数据库层面实现精细的权限控制,无需在后端逻辑中重复编写权限校验代码。
4.3 前端集成与实时功能实现
在Next.js中,我创建了一个lib/supabase.ts文件来初始化客户端和服务端客户端。
// lib/supabase.ts import { createBrowserClient } from '@supabase/ssr' import { createServerClient } from '@supabase/ssr' import { cookies } from 'next/headers' export function createClient() { return createBrowserClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! ) } export async function createServerSideClient() { const cookieStore = await cookies() return createServerClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, { cookies: { getAll() { return cookieStore.getAll() }, setAll(cookiesToSet) { cookiesToSet.forEach(({ name, value, options }) => cookieStore.set(name, value, options) ) }, }, } ) }实时评论功能的实现令人惊艳地简单。在电影详情页组件中:
import { createClient } from '@/lib/supabase'; import { useEffect, useState } from 'react'; export function CommentList({ movieId }: { movieId: number }) { const [comments, setComments] = useState([]); const supabase = createClient(); useEffect(() => { // 1. 初始获取评论 const fetchComments = async () => { const { data } = await supabase .from('comments') .select('*, profiles(username)') .eq('movie_id', movieId) .order('created_at', { ascending: false }); if (data) setComments(data); }; fetchComments(); // 2. 订阅该电影评论的实时变化 const channel = supabase .channel(`comments:movie=${movieId}`) .on( 'postgres_changes', { event: 'INSERT', // 监听插入事件 schema: 'public', table: 'comments', filter: `movie_id=eq.${movieId}`, }, (payload) => { // 当有新评论插入时,更新本地状态 setComments((prev) => [payload.new, ...prev]); } ) .subscribe(); // 清理订阅 return () => { supabase.removeChannel(channel); }; }, [movieId, supabase]); return ( <div> {comments.map((comment) => ( <div key={comment.id}>{comment.content}</div> ))} </div> ); }几行代码就实现了类似聊天室的实时更新体验,这极大地增强了应用的互动感。
4.4 身份认证与部署考量
Supabase 提供了完整的邮箱/密码、OAuth(GitHub, Google等)认证流程。我直接使用了其React Auth UI组件库,快速搭建了登录注册页面。对于部署,由于Supabase处理了后端和数据库,前端Next.js应用可以静态导出(如果主要是SSG)或部署在Vercel上,几乎零运维。
避坑指南:
- RLS策略调试:初期经常遇到“权限被拒绝”的错误。务必在Supabase Dashboard的“Authentication” -> “Policies”界面仔细检查和测试每条策略。可以使用
supabase.rpc(‘auth.uid’)在SQL编辑器中模拟用户上下文进行测试。- 实时订阅的性能:不要过度订阅。确保在组件卸载时清理频道 (
supabase.removeChannel),避免内存泄漏和多余的网络连接。- 本地开发:使用Supabase CLI可以在本地运行完整的Supabase环境,包括数据库迁移和种子数据,这对开发调试至关重要。
这个全栈小项目让我在极短的时间内体验了从数据库设计、API(通过Supabase客户端)、实时功能到前端展示的完整闭环,极大地提振了我向全栈发展的信心。
5. 核心项目四:探索AI辅助编码(Vibe Coding)工作流
5.1 Vibe Coding工具链的搭建
“Vibe Coding” 不仅仅是心态,也需要工具辅助,以保持流畅的创作状态。我探索的核心是AI 结对编程。我的工具链核心是Cursor + GitHub Copilot + 自定义的代码片段/提示词库。
- Cursor:作为主力编辑器,其强大的AI对话和代码编辑能力(如“在项目中搜索”、“编辑此函数”)远超传统辅助。
- GitHub Copilot:作为补充,在编写重复模式代码或需要快速补全时非常高效。
- 自定义提示词:我建立了一个Markdown文件,记录针对不同场景(如“重构一个React组件”、“编写一个TypeScript工具类型”、“调试Next.js API路由错误”)的有效提示词。
5.2 实战:用AI加速复杂组件开发
以开发一个可拖拽、支持嵌套的树形列表组件为例。这是一个复杂的前端交互,涉及状态管理、DOM操作和性能优化。
- 需求拆解与提示:我不会直接问“写一个树形组件”。而是先拆解:
- 数据结构:向AI描述节点
{id, label, children?}的结构。 - 核心交互:“使用
@dnd-kit库实现拖拽排序和嵌套。父节点可折叠/展开。” - 状态管理:“使用Zustand管理树的状态,包括展开/折叠状态、节点列表。”
- 性能:“使用
React.memo优化节点渲染,仅在必要时更新。” 我会将这些拆解后的需求,分步骤、清晰地输入给Cursor。
- 数据结构:向AI描述节点
- 迭代与调试:AI生成的初始代码往往不完美。当拖拽后状态更新不正确时,我会将错误现象和关键代码片段发给AI:“拖拽后,
onDragEnd事件里,active和over节点的ID打印出来是对的,但UI没有更新。这是我的handleDragEnd函数和Zustand store的代码,请帮我分析可能的原因。” AI通常会指出我可能直接修改了状态(违反了不可变性),或者setState的用法有误。 - 代码审查与优化:AI还可以充当第一轮代码审查员。我会将一段自认为写的不错的代码发给它,并提问:“从代码风格、性能、TypeScript类型安全和潜在bug的角度,审查这段代码,并提出改进建议。” 它常常能发现一些我忽略的边缘情况或更优雅的写法。
5.3 提示词工程与思维引导
我意识到,让AI写出高质量代码的关键在于“高质量的提问”。这本身就是一种编程思维的锻炼。我总结了几条原则:
- 提供上下文:告诉AI我们正在做什么项目(Next.js 15, TypeScript),使用什么库(Tailwind, Zustand)。
- 明确约束:指定代码风格(如函数式组件、使用特定的Hook)、避免什么(如避免
any类型)。 - 分步骤引导:复杂任务分解为多个简单指令,像在指导一位初级程序员。
- 要求解释:在AI生成代码后,追问“为什么这里要这样处理?”或“这个参数的作用是什么?”,这能加深我对代码的理解。
5.4 AI辅助的边界与价值反思
AI不是银弹。它擅长基于模式生成代码、提供建议和快速检索信息,但在架构设计、深度调试、理解复杂业务逻辑方面仍有局限。我的体会是:
- 不要盲目接受:AI生成的代码一定要经过自己的理解和测试,特别是涉及安全、性能和核心业务逻辑的部分。
- 它是加速器,不是替代品:它把我从繁琐的样板代码和简单的bug查找中解放出来,让我能更专注于更高层次的设计和问题解决。
- 保持学习:过度依赖AI会导致基础技能退化。我仍然坚持手动编写核心逻辑、阅读源码和文档,确保自己理解背后的原理。
这个探索项目,让我的编码过程从“搜索-复制-修改”的被动模式,转变为“构思-提问-迭代-理解”的主动创造模式,真正找回了编程最初的乐趣和“ vibe ”。
6. 半年总结:技能提升与心态转变
回顾这半年,四个项目像四个不同的支点,共同撬动了我的能力成长。Next.js重构让我对现代Web性能优化和全栈框架有了实战级理解;TypeScript类型体操将我的静态类型思维提升到了新的高度,写代码时更有“安全感”;Supabase全栈实践让我亲身体验了云原生BaaS的便捷,消除了对后端的神秘感;AI辅助工作流则革新了我的开发习惯,提升了效率。
更重要的是心态的转变。我不再仅仅把自己看作一个“前端切图仔”或“业务逻辑实现者”,而是开始以“产品建造者”和“问题解决者”的视角思考。我会主动问:这个功能的技术选型是否最优?用户体验能否更好?代码是否足够健壮以应对未来变化?这种主动探索和创造的状态,就是我认为最棒的“Vibe Coding”。它无关乎你使用了多新的框架,而在于你是否能从代码中找到持续的热情和成就感。如果你也感到有些倦怠,不妨试着给自己设定一个这样的小项目周期,沉浸进去,你可能会收获意想不到的成长和快乐。