Frontend Elegant Guidance - 前端优雅指南

我大二开始学习前端,现在研三即将毕业,仍然做这个方向。这有历史原因,但是从整体知识范畴,前后端和运维我都懂点,这篇文章讲我常常喜欢使用的一个前端结构,大家做过前端的应该一眼都明白,似乎过于简单,没什么价值,但在这些年实习兼职过的创业公司中,在这一点上都差很多,后面无数人"金"上雕花,所以除了做过多牛逼的技术外,我更认为最基本的开发素养和习惯是应该的,因为这是我们很多人能直接做到。
文章内容
我大二开始学习前端,现在研三即将毕业,仍然做这个方向。这有历史原因,但是从整体知识范畴,前后端和运维我都懂点,这篇文章讲我常常喜欢使用的一个前端结构,大家做过前端的应该一眼都明白,似乎过于简单,没什么价值,但在这些年实习兼职过的很多创业公司、小厂大厂中,在这一点上都差很多,因为往往是一开始就没有计划好,后面无数人"金"上雕花,所以除了做过多牛逼的技术外,我更认为最基本的开发素养和习惯是应该的,因为这是我们很多人能直接做到。
AI的出现的确能提交,Vibe Coding的几款工具我基本都用过,让Claude Code写代码能写,UI也比手撸好看,但是它会埋下很多的隐患,尤其是对于我这种有强迫症的人来说,巨难受,比我手写代码量增加三四成。另外就是,和AI对话多了,我发现我真的不想写代码了,且和他对话非常暴躁,会骂他。所以,提到这个也凸显了架构、规范的重要性,我在最后也放了一个
claude.md,当然你可以修改,help yourself。提到这个话有点多,那就是AI能助力,但绝不像你每天刷到的几个小时一天完成一个app上线,一周赚了多少刀,多尝试没错,但擦亮眼睛,脚踏实地。
整体架构
前后端开发时,先搞清楚要做什么,然后再考虑用什么框架和模块,接着可以在AI帮助下完善整体架构,在开发时遵循这个架构和规范,步子迈的不要太大,包括命名、样式、请求、缓存等等。而前后端分离是最常见的开发模式,NextJS 是典型的面向前端的全栈框架,它很适合SEO和SSR,但是假如已经具备后端和Gateway,那么在使用的时候需要注意不要再二次转发API,前端处理好跨域即可。
这里以NextJS框架为例,大概说明每个文件/文件夹的作用,后面我会针对每个模块来详细说明应该注意的事项和做法。注意,开发时一定要把新文件放到对应的位置,在没有AI的时候,假如定义不明,别人是很难阅读的。
NextJS-Frontend
├── README.md # You Know It!
├── components.json # React组件库Shadcn-UI的配置文件
├── docs # Vide Coding的需求文档、DEBUG文档等
├── next-env.d.ts # Next.js TypeScript 类型全局声明
├── next.config.js # Next.js 框架配置
├── package.json # You Know It!
├── pnpm-lock.yaml # pnpm 包管理器锁定文件(不建议npm)
├── pnpm-workspace.yaml # pnpm workspace 配置(Never Mind)
├── postcss.config.js # PostCSS 配置(CSS 处理)
├── public # 公共静态资源
│ ├── animations # 动画库
│ ├── favicon.ico # 网站标签页左侧显示的icon
│ ├── fonts # 字体文件
│ ├── icons # 专有设计icon图标
│ └── images # 图片资源
├── src
│ ├── app # 各个页面视图文件(NextJS特性:约定式路由)
│ │ ├── page.tsx # 页面文件
│ │ └── layout.tsx # 布局文件
│ ├── components # 公共组件库
│ │ ├── animate-ui # 动画库
│ │ └── ui # shadcn-ui组件库
│ ├── apis # api封装调用模块
│ │ ├── base.ts # 基础配置
│ │ └── endpoints # 定义各个接口调用
│ ├── hooks # hook钩子函数
│ ├── constants # 静态配置、常量存储
│ ├── utils # 工具库
│ ├── stores # 状态管理(Zustand)
│ ├── styles # 公共样式
│ └── types # 存储类型定义
│ │ ├── apis # 存储API的request和response类型定义
│ │ └── entity # 存储实体定义,例如User信息等
├── tailwind.config.js # Tailwind CSS 配置
└── tsconfig.json # TypeScript 编译配置
变量命名
- 前端绝大部分的命名都是驼峰命名法,后端则下划线(Python)和驼峰命名(Go、JAVA)都有涉及,而在接口对接上,字段常常是下划线。
- 变量命名不宜过长过短,同时语义性要强。AI喜欢起特别长的名字,不别扭吗?
- 变量名前后必须保持一致,假如叫Card,那就不要改为Widget或者其他别的。
风格样式
前端三件套,分别是html、css、js。html是人的骨架和血肉,css是人的穿搭衣物,js则是人的动作行为。爱美之心人皆有之,因此,美观的一定是前端人所追求的东西之一。
最初上手写的肯定是原始的css,这是必经之路;接着是用法简单的css预处理器,less、sass、stylus,基本上拿来就用,看一遍就会;然后,样式文件体积和数量的增大,于是聪明人就做出了 Tailwind,它将css融入了class类中,导致我们代码的区块划分为两部分:js和html,同时大量减少手写的样式内容,这将是我主推的一个语法。
<div class="flex flex-col items-center gap-6 p-7 md:flex-row md:gap-8 rounded-2xl">
<div>
<img class="size-48 shadow-xl rounded-md" alt="" src="/img/cover.png" />
</div>
<div class="flex items-center md:items-start">
<span class="text-2xl font-medium">Class Warfare</span>
<span class="font-medium text-sky-500">The Anti-Patterns</span>
<span class="flex gap-2 font-medium text-gray-600 dark:text-gray-400">
<span>No. 4</span>
<span>·</span>
<span>2025</span>
</span>
</div>
</div>
- Tailwind用来处理通用的样式
- Pure CSS用来表达写动画、字体或者定义全局样式
- 尽量避免内联CSS Style,除非需要动态调整宽高等内容
- 过于复杂可以使用
clsx函数来处理 - 全局样式文件还建议分类放置
├── styles # 公共样式
│ ├── custom.css # 自定义样式写到这里
│ ├── fonts.css # 引入的字体样式
│ ├── index.css
│ ├── scrollbar.css # 滚动条覆盖样式
│ └── tailwind.css # tailwind样式
图标库
在项目开发中,常常要用到众多的图标,像jpg、png这样的图标不能定制颜色,都是通过下载到本地来完成的,所以图标使用常以svg为主,它的用法主要有以下三类:
- 纯SVG内容复制注入代码中
- 图标下载到本地,通过img注入
- 图标库,例如lucide,通过组件方式注入
- 【推荐】类图标,使用iconify,可以通过class类注入,例如material design icons图标库中的播放图标,可以通过class类加上
i-mdi-play来生效,这个图标库中也包括了现在在使用的lucide-react图标,目前已经通过unocss配置。
状态管理
对前端项目来说,它需要在浏览器内存中存储大量的临时数据,网页之间以及组件之间会涉及大量的数据交互,我们不可能层层传递变量(称为props drilling),过于丑陋。
这时候就需要状态管理,它们主要用于全局存储数据和定义行为,例如常见的用户信息,在vue里的解决方案从 vuex 到官方推荐的 pinia,在react中则百花争鸣,原生自带的Context/Provider,或轻量级的zustand,这会是我们项目主推去使用的。
- 定义公共变量,例如
authToken和userInfo - 定义公共行为,例如
loginlogoutupdateUserInfo - 定义缓存策略,不要操作localstorage,这些模块都配置有插件,例如zustand的
persist - 定义更新策略,如果涉及多重嵌套更新状态,可以使用
zustand/immer模块 - 相关性能力聚合,这指的是例如要对用户信息做频繁操作,那这个函数就可以放到store中,而不是单独写到页面中
格式化
每个语言都有它的检查和格式化规则,问题就在于每个人的格式化风格不同,甚至于一个文件敲完之后没有格式化,像python有autopep、blank等;另外便是JS仍然是弱类型语言,缺少编译流程,这样就难以发现各种潜在的问题,直到你运行build或者在页面里开始做操作。
这方面最常用的模块就是 Eslint 和 Prettier。
- Eslint是一个代码检查工具(linter),主要关注代码质量和语法正确性,以及代码风格的一致性,它会分析代码的语法结构和逻辑,检测潜在的错误(如未定义变量、不合理的类型转换)、不符合最佳实践的写法(如使用
var而非let/const),以及团队约定的代码风格(如缩进、命名规范)。 - Prettier是一个代码格式化工具(formatter),专注于代码的格式美化,不关心代码的逻辑或语法正确性。它会统一代码的格式细节(如缩进、换行、引号类型、分号等),消除团队成员之间因格式偏好产生的差异。
目前更推荐 biome :它是一个现代化的前端开发工具链,旨在整合代码检查(Lint)、格式化(Format)、语法解析(Parse)等功能,提供比传统工具(如 ESLint、Prettier 等)更高效、更统一的开发体验。可以在VSCODE上同时安装插件 biome 这样就可以边写代码边格式化了。
值得注意的一点是,它还应该配置到 git-hooks 中,在做git操作时会触发检查和格式化,这样诸位在commit之前代码就必须满足这些风格并通过检测了,有很多配合方式例如simple-git-hooks和lint-staged。
类型管理
Typescript语言的重要性就在于Type,它是JavaScript的超集,增加了类型,使得它在弱类型语言(JIT解释性语言)的基础上增加了一定的健壮性,所以在项目配置之处,还会引入eslint等模块来进行规则校验,它会阻止常见的容易出错的写法,以及不规范的写法。
对于类型的语法,除了常用的 interface 和 type 外,还有很多用于做类型体操的语法,这大多数不需要自己写,我们只需要保证类型按属性、功能整理好。
例如有一个专门的types目录,可以再创建一个文件夹apis用来存储每个接口的入参和回参类型,这类参数的命名常用 XXXParams XXXRequest XXXResponse XXXResult等。也可以再创建一个文件夹 entity 实体表,在UML图中它可以对应数据表,也就是User、Product、Order等,这样的话,例如用户可以专门创建一个 user.ts 文件存储用户信息相关的类型,其他同理。
路由管理
在以往,一个项目的路径常常是通过 vue-router 或者 react-router 来手动配置和管理的,这为我们配置页面增加了很多额外的工作,但是现在约定式路由的出现,使得它节省了我们的人力成本,比例 Next.JS 具备的这个特性。
HOOK钩子
在前端开发中,hook(钩子)和store(状态存储)是两个不同维度的概念,但它们经常结合使用,共同解决状态管理和组件逻辑复用的问题。
例如对于用户信息,它全局只存在一份,那么就应该有一个userStore,但是假如它有能复用的一块功能特性,那就可以定义hook来处理。
关于可复用的 hooks 有很多已经总结好的模块,例如阿里的 ahooks :
useSetState可以创建复杂类型的状态,且便于更新useClickAway用于处理点击某个元素之外的内容
网络请求
其实网络请求主流就http协议和websocket协议请求(在http 1.1基础上upgrade)。在做请求的时候,后端只是在Apifox/Postman/Curl上直接测试接口联通,但是为了保证完善可用,在前端做调用的时候,也要考虑如何统一存储和写入认证信息、统一字段命名、完成请求和响应类型定义(JS是弱类型语言,TS的精髓在于此)、请求和响应拦截器等等。
Http
工欲善其事必先利其器,为了完成前面提到的需要考虑的内容,需要先选择一个合适的http请求库,当然前端自带的 fetch 肯定是可以的,只不过需要做一定的封装,这里建议使用 axios 来封装。我还经常喜欢在响应拦截器里加一个统一的transformCamel,从下划线统一转换为驼峰。
// base.ts
import axios from "axios";
import { transformCamel, transformSnake } from "@/utils/transform";
export const service = axios.create({
baseURL: "/api",
});
service.interceptors.request.use();
service.interceptors.response.use();
然后接口调用的时候再单独写一个函数去声明:
import { service } from "./base";
// 下面的类型也可以专门放到@/types/apis文件夹下
interface GetExampleReq {
id: string;
}
interface GetExampleRes {
id: string;
name: string;
}
export async function getExample(params: GetExampleReq) {
return service.get<any, GetExampleRes>("/example", {
params,
});
}
接着就是接口格式,下面是一个常用的返回值格式:
- message是消息提示,假如后端业务逻辑出差,在这里提示,前端可以直接用Toast组件提示
- code是业务码,0表示调用成功,多用来标识具体的业务错误(status多用于网络状态、调用参数错误、后端未意料的BUG错误)
- data是实际传输的数据本身,所以在调用的时候就可以直接拿data用,通过响应拦截或者包裹except去捕获错误即可。
- data尽量不要多重包裹,例如一个列表接口,如果不涉及分支全部返回,那么data本身就应该是这个数组,而不是data.list是这个数组。
{
"message": "ok",
"code": 0,
"data": {
"id": "gypsophlia",
"username": "走马观花"
}
}
关于接口设计,有很多风格,例如RESTful风格,教了如何定义这个接口的路径,清晰表明接口的作用,如何使用GET/POST/PUT/DELETE去对应不同的动作,这些整个接口设计中看似微不足道,但却是专业性的表现。
Websocket
由于通信的建立会持久化,因此个人认为较好的方式为使用设计模式中的单例模式,写一个全局的 websocketInstance 来处理,假如需要多个的话,那可以创建多个全局或者用Map来存映射。然后在全局中去处理各种事件触发 sendMessage 和事件监听 handleListen 。
谈到这个就想到了我的第一篇文章,讲Electron,这个再更新,先不多说websocket了。
性能优化
对于前端而言,有几种比较关键的指标,例如 FCP 和 LCP ,这和用户体验息息相关,且AI很难发现,需要我们主动关注和优化,比如以下几个方面:
- 资源加载:对于小的资源,例如10B的svg可以存储到本地文件中;但是对于比如字体文件,动辄500KB或者>1MB,最好存储到oss,用cdn方式访问。
- 资源缓存:例如用户信息、登录Token等内容。
- 接口调用:把握好接口调用的时机和次数,例如一个用户访问网站,那么
getUserInfo就只需要触发一次后存储,除非数据更新否则无需再调用;例如页面数据批量更新,可以用定时器+脏标记的方式,减少接口调用且减少全量更新。
Vide Coding
以上各种问题,均可能是Vide Coding碰到的问题,它的代码量相比老艺术家手写代码,会提高复杂性,且行数会增加非常多,难以维护,因此我们可以为项目配置一个 .claude/claude.md 文件,相当于为每个chat增加声明。除此之外,ai写出来的代码自己也要多关注,不是功能OK就可以一键完成提交commit。下面是总结的几条原则和最后配置出来的 .claude/claude.md 文件:
- 按照整体文件架构中去划分、创建和使用对应模块,包括组件、类型等
- 变量统一采用驼峰命名,且简洁易读
- 样式尽量采用
tailwind,减少内联css,过于复杂则使用cn函数 - 图标尽量采用
iconify的mdi和lucide,特殊图标则下载到public/icons来使用 - 减少全局hooks,除非它可在多处复用,多尝试使用
ahooks中的函数保证代码简洁,例如useSetState等。 - 状态管理使用
zustand,如果有需要,可以配置persist和immer - 接口请求均整理在
apis中,并且需要为每个请求在types/apis中定义请求和返回值类型 - 注释尽量精简,减少DEBUG的输入内容
# Claude Code Project Rules
## YAGNI Principle (You Aren't Gonna Need It)
Always adhere to the **YAGNI** principle throughout development:
- **Only implement what’s currently required** – avoid building features that *might* be useful later
- **Avoid over-engineering** – don’t introduce unnecessary complexity
- **Keep it simple** – focus on solving the current problem efficiently
- **Iterate incrementally** – extend functionality only when there’s a real need
---
## Practice Guidelines
### 1. Feature Development
- Implement **only explicitly requested** features.
- Avoid speculative or “future-use” functionality.
- Defer abstractions or refactors until they’re directly justified.
### 2. Code Style & Structure
- Follow the **project structure** below for creating and organizing files.
- **Variable naming**: use concise, readable **camelCase** throughout.
- Keep code logic minimal and avoid unnecessary layers or wrappers.
### 3. Styling
- Prefer **TailwindCSS** for basic and medium-complexity styles.
- For overly complex conditions, use the `cn()` utility for class merging.
- Inline CSS should be minimized.
### 4. Icon Usage
- Prefer **Iconify**’s `mdi` and `lucide` icon sets.
- For special icons, download and store them under `public/icons`.
### 5. Hooks
- Always prefer **`ahooks`** where applicable (e.g. `useSetState`, `useRequest`).
- Reduce the number of **global custom hooks** unless they are reused across multiple pages.
- Keep hooks simple, composable, and context-specific.
### 6. State Management
- Use **Zustand** for all state management.
- Configure **`persist`** and **`immer`** if persistence or immutability is required.
- Keep global state minimal; prefer local state where possible.
### 7. API Layer
- All network requests must be defined under `src/apis`.
- For each request, define corresponding **request and response types** under `src/types/apis`.
- Use consistent API naming and avoid inline axios/fetch calls in components.
### 8. Comments & Debugging
- Keep comments **minimal and essential** — explain *why*, not *what*.
- Avoid excessive `console.log` or debug statements; remove them before commit.
---
## Project Structure
├── public # Public static assets
│ ├── favicon.ico # The icon shown on the browser tab
│ ├── fonts # Font files
│ ├── icons # Custom-designed icon set
│ └── images # Image resources
├── src
│ ├── app # Page view files (Next.js feature: file-based routing)
│ │ ├── page.tsx # Page component file
│ │ └── layout.tsx # Layout component file
│ ├── components # Shared component library
│ │ ├── animate-ui # Animation UI components
│ │ └── ui # shadcn-ui component library
│ ├── apis # API request and response modules
│ ├── hooks # Custom React hooks
│ ├── constants # Static configurations and constants
│ ├── utils # Utility functions
│ ├── stores # State management (using Zustand)
│ ├── styles # Global styles
│ │ ├── custom.css # Custom CSS styles
│ │ ├── fonts.css # Imported font styles
│ │ ├── index.css
│ │ ├── scrollbar.css # Custom scrollbar styles
│ │ └── tailwind.css # Tailwind CSS base styles
│ └── types # Type definitions
│ ├── apis # Type definitions for API requests and responses
│ └── entity # Entity definitions, e.g., User