重新思考AI时代的前端架构
我们正进入一个前端系统不仅被人类使用,还被AI工具解析、生成和解释的时代。现代框架如shadcn/ui、设计到代码工具如V0.dev以及IDE代理如Junie正在重新定义我们的构建方式。但随着这种加速,对结构的需求也更大。
本指南概述了一个实用架构,用于:
- 使用AI工具增强UI创建
- 设计开发者与AI的协作协议
- 保持可测试性和可扩展性
1. 为代码库定义AI指南
大多数AI工具缺乏直觉——所以给它们一个直觉。创建一个guidelines.md文件来描述前端中使用的规则、技术栈和模式。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
Framework: Next.js
Styling: Tailwind CSS 4
Language: TypeScript
Tests: Vitest + Playwright
Architecture:
- Use Case模式
- 中间件链
Standards:
- 仅使用命名导出
- 使用联合类型,不使用枚举
- 使用带有PropsWithChildren的FC
|
像Junie这样的IDE代理将使用这个来遵循您的约定,而无需重复提示。查看真实示例。
2. 借助AI辅助采用组件驱动UI
使用V0.dev等工具,您可以在几秒钟内从提示转到JSX:
“创建一个带有两个输入字段和CTA按钮的暗模式注册表单”
V0使用shadcn/ui响应可访问的样式化JSX,准备放入您的仓库。结合Storybook、MDX文档和Junie指南,您可以在AI和设计系统之间获得反馈循环。
AI生成。您的系统验证。
3. 业务逻辑的用例模式
在AI增强的开发中,生成UI只是等式的一部分。真正的价值在于将业务逻辑与接口层解耦,以便AI代理(或人类)可以安全且可预测地触发行为。这就是用例模式变得至关重要的地方。
什么是用例模式?
其核心是,每个用例封装一个单一的业务操作。例如:RegisterUser、CreatePost、SubmitFeedback等。它定义了这样的契约:
1
2
3
|
export interface UseCase<In = unknown, Out = unknown> {
handle(param?: In, meta?: UseCaseOptions): Promise<Out>
}
|
每个用例都是一个隔离、可测试和可注入的单元,对输入进行操作并返回输出。它与UI框架或事件源完全解耦——意味着它可以被以下方式触发:
- UI按钮
- 后台作业
- AI代理
- Webhook
- 或所有组合
为什么这对AI很重要?
通过提供稳定接口并隐藏内部逻辑,用例模式允许AI编排应用程序行为而无需了解实现细节。
它防止AI工具(或人类)将逻辑与组件或视图紧密耦合,这通常会导致脆弱、不可维护的代码。
使用useCaseService集中执行
要执行用例,我们使用一个服务层来抽象编排逻辑并允许中间件组合(我们将在后面的部分看到中间件):
1
2
3
4
|
await useCaseService.execute(RegisterUserUseCase, {
email: "test@example.com",
password: "secure-password",
})
|
此调用不暴露RegisterUserUseCase内部的逻辑——AI只知道它需要传递有效载荷并接收结果。
AI增强系统的优势
- 可测试性:每个用例都可以使用模拟或存根进行隔离测试
- 安全性:可以通过中间件添加访问控制或权限检查
- 可扩展性:可以分层添加新行为(例如分析、错误报告)而无需触及用例逻辑
- AI就绪性:代理可以通过稳定、定义良好的API表面安全地与您的后端交互
4. 中间件链:通过可组合性扩展逻辑
在结构良好的前端架构中,横切关注点——如日志记录、错误处理和缓存——如果管理不当,很容易污染业务逻辑。中间件提供了一种受责任链模式启发的清洁、模块化方法。
不是将此逻辑嵌入每个用例中,而是使用中间件类包装它,这些类在核心逻辑运行之前和之后拦截执行:
1
2
3
4
5
6
7
|
interface Middleware {
intercept<In, Out>(
param: In,
next: UseCase<In, Out>,
options: UseCaseOptions
): Promise<Out>
}
|
每个中间件负责单一关注点并沿链传递控制。
错误中间件
无需编写重复的try/catch块,您可以将错误管理委托给中间件。当发生异常时,它会分派错误事件,您的UI可以监听——显示toast或记录问题——而无需触及业务逻辑。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
export class ErrorMiddleware implements Middleware {
constructor(private readonly eventEmitter: EventEmitter) {}
async intercept(params: unknown, next: UseCase, options: UseCaseOptions): Promise<unknown> {
try {
return await next.handle(params)
} catch (error) {
if (!options.silentError) {
this.eventEmitter.dispatch(EventType.ERROR, error)
}
throw error
}
}
}
|
日志中间件
此中间件记录每个用例执行,包括名称和参数。您可以插入不同的记录器实现——开发中的控制台,生产中的远程日志记录——而无需更改任何用例代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
export class LogMiddleware implements Middleware {
constructor(private readonly logger: Logger) {}
intercept(params: unknown, useCase: UseCase): Promise<unknown> {
this.logger.log(
`[${DateTime.fromNow().toISO()}] ${this.getName(useCase)} / ${this.printResult(params)}`
)
return useCase.handle(params)
}
private getName(useCase: UseCase): string {
if (useCase instanceof UseCaseHandler) {
return this.getName(useCase.useCase)
}
return useCase.constructor.name
}
private printResult(result: unknown) {
return JSON.stringify(result, null, 2)
}
}
|
缓存中间件
如果您使用CQRS来分离查询和命令,此中间件可以自动缓存查询结果。提供cacheKey选项的用例将在设定的生存时间内返回缓存结果,减少不必要的处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
type CacheEntry = {
value: unknown
expiresAt: number
}
export class CacheMiddleware implements Middleware {
private readonly store = new Map<string, CacheEntry>()
constructor(private readonly ttlInSeconds: number = 60) {}
async intercept<In, Out>(
params: In,
next: UseCase<In, Out>,
options: UseCaseOptions,
): Promise<Out> {
const key = options.cacheKey
if (!key) return next.handle(params, options)
const now = Date.now()
const cached = this.store.get(key)
if (cached && now < cached.expiresAt) {
return cached.value as Out
}
const result = await next.handle(params, options)
this.store.set(key, { value: result, expiresAt: now + this.ttlInSeconds * 1000 })
return result
}
}
|
使用UseCaseService组合中间件
要应用多个中间件,您使用服务将它们包装在用例周围。此服务从最外层到最内层组合中间件链。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
export class UseCaseService {
constructor(
private middlewares: Middleware[],
private readonly container: Container,
) {}
async execute<In, Out>(
useCase: Type<UseCase<In, Out>>,
param?: In,
options?: UseCaseOptions,
): Promise<Out> {
const requiredOptions = options ?? { silentError: false }
let next = UseCaseHandler.create({
next: this.container.create(useCase),
middleware: this.container.get<EmptyMiddleware>(EmptyMiddleware.name),
options: requiredOptions,
})
for (let i = this.middlewares.length - 1; i >= 0; i--) {
const current = this.middlewares[i]
next = UseCaseHandler.create({
next,
middleware: current,
options: requiredOptions,
})
}
return next.handle(param) as Promise<Out>
}
}
|
为什么这很重要
有了中间件,您的系统获得:
- 集中式错误处理
- 一致的日志记录
- 自动缓存
- 零重复的更清洁用例
您可以在应用程序中扩展功能,而不会增加核心业务逻辑的复杂性。
总结表:AI增强架构
| 层 |
工具/模式 |
AI角色 |
| UI原型设计 |
V0.dev + shadcn/ui |
从提示生成JSX |
| UI文档 |
MDX + Storybook |
从示例学习 |
| 逻辑层 |
用例模式 |
创建和调用遵循定义模式的逻辑 |
| 粘合层 |
中间件链 |
增强而不耦合 |
| IDE协作 |
.junie/guidelines.md |
将AI与您的约定对齐 |
最终思考
我们不再仅仅为浏览器构建——我们正在构建人类和智能代理都能理解和扩展的系统。
前端的未来不是自动化的。它是增强的。
为以下方面设计您的架构:
- 可解释性(对人类和机器)
- 结构(通过约定和模式)
- 可扩展性(通过用例和中间件)
这就是我们与AI一起编码的方式,而不是尽管有AI。
想在您的团队中实施此架构吗?我提供前端架构和AI驱动工作流的咨询。联系或在cesalberca.com阅读更多关于我的方法。