关于 swiftui-view-refactor
一项将 SwiftUI 视图重构为更小、更明确类型的技能,并指导将业务逻辑留在视图之外。内容涵盖状态管理、子视图、稳定视图结构和 Observation 用法。
主要功能
- 01视图属性排序
- 02MV 优先的状态管理
- 03提取独立子视图
- 04将操作移出 body
- 05保持视图树稳定
- 06Observation 包装器建议
适用场景
- 01将较长的 SwiftUI 页面拆分为子视图
- 02从视图 body 中移出业务逻辑
- 03重构使用可选视图模型的视图
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ openai/ plugins --skill swiftui-view-refactor安装后在 Claude Code 中输入/swiftui-view-refactor确认已加载。
附带文件agents/openai.yamlreferences/mv-patterns.md
详细介绍
SwiftUI 视图重构
概述
重构 SwiftUI 视图,使其成为小巧、明确且稳定的视图类型。默认采用原生 SwiftUI 方案:在视图中管理本地状态,通过环境共享依赖项,将业务逻辑放在服务/模型中,并且仅在请求或现有代码明确需要时使用视图模型。
核心指南
1) 视图中的排列顺序(从上到下)
- 除非现有文件有必须保留的、更强的局部约定,否则请遵循此顺序。
- 环境
private/publiclet@State/其他存储属性- 计算属性
var(非视图) initbody- 计算视图构建器/其他视图辅助项
- 辅助函数/异步函数
2) 默认采用 MV,而非 MVVM
- 视图应是轻量的状态表达和协调点,而不是业务逻辑的容器。
- 优先使用
@State、@Environment、@Query、.task、.task(id:)和onChange,之后再考虑视图模型。 - 通过
@Environment注入服务和共享模型;将领域逻辑放在服务/模型中,而不是视图的body中。 - 不要仅仅为了映射本地视图状态或封装环境依赖项而引入视图模型。
- 如果屏幕变得庞大,应先将 UI 拆分为子视图,再考虑创建新的视图模型层。
3) 强烈建议使用专用子视图类型,而非计算属性 some View 辅助项
- 标记长度超过大约一屏或包含多个逻辑部分的
body属性。 - 对于非简单部分,优先提取专用的
View类型,尤其是包含状态、异步工作、分支逻辑或值得拥有单独预览的部分。 - 尽量少用计算属性
some View辅助项,并保持其简短。不要用private var header: some View这类片段构建整个屏幕。 - 将少量、明确的输入(数据、绑定、回调)传递给提取出的子视图,而不是传递整个父视图状态。
- 如果提取出的子视图变得可复用或具有独立意义,应将其移至单独的文件。
优先采用:
var body: some View {
List {
HeaderSection(title: title, subtitle: subtitle)
FilterSection(
filterOptions: filterOptions,
selectedFilter: $selectedFilter
)
ResultsSection(items: filteredItems)
FooterSection()
}
}
private struct HeaderSection: View {
let title: String
let subtitle: String
var body: some View {
VStack(alignment: .leading, spacing: 6) {
Text(title).font(.title2)
Text(subtitle).font(.subheadline)
}
}
}
private struct FilterSection: View {
let filterOptions: [FilterOption]
@Binding var selectedFilter: FilterOption
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(filterOptions, id: \.self) { option in
FilterChip(option: option, isSelected: option == selectedFilter)
.onTapGesture { selectedFilter = option }
}
}
}
}
}
避免:
var body: some View {
List {
header
filters
results
footer
}
}
private var header: some View {
VStack(alignment: .leading, spacing: 6) {
Text(title).font(.title2)
Text(subtitle).font(.subheadline)
}
}
3b) 将操作和副作用从 body 中提取出来
- 不要将复杂的按钮操作内联在视图的
body中。 - 不要将业务逻辑藏在
.task、.onAppear、.onChange或.refreshable中。 - 优先从视图调用简短的私有方法,并将真正的业务逻辑移至服务/模型中。
body应呈现 UI,而不是视图控制器。
Button("Save", action: save)
.disabled(isSaving)
.task(id: searchText) {
await reload(for: searchText)
}
private func save() {
Task { await saveAsync() }
}
private func reload(for searchText: String) async {
guard !searchText.isEmpty else {
results = []
return
}
await searchService.search(searchText)
}
4) 保持视图树稳定(避免在顶层切换条件视图)
- 避免让
body或计算视图通过if/else返回完全不同的根分支。 - 优先采用单一且稳定的基础视图,并在各个部分/修饰符(
overlay、opacity、disabled、toolbar等)中处理条件。 - 在根级别切换分支会导致标识变化、扩大失效范围并增加重新计算。
优先采用:
var body: some View {
List {
documentsListContent
}
.toolbar {
if canEdit {
editToolbar
}
}
}
避免:
var documentsListView: some View {
if canEdit {
editableDocumentsList
} else {
readOnlyDocumentsList
}
}
5) 视图模型处理(仅限已存在或明确要求的情况)
- 将视图模型视为旧有模式或有明确需求时才采用的模式,而非默认做法。
- 除非请求或现有代码明确需要,否则不要引入视图模型。
- 如果已有视图模型,尽可能将其设为非可选。
- 通过视图的
init传入依赖项,然后在视图的init中创建视图模型。 - 避免使用
bootstrapIfNeeded模式和其他延迟设置的变通方案。
示例(基于 Observation):
@State private var viewModel: SomeViewModel
init(dependency: Dependency) {
_viewModel = State(initialValue: SomeViewModel(dependency: dependency))
}
6) Observation 的用法
- 对于 iOS 17 及更高版本上的
@Observable引用类型,在所属视图中将其存储为@State。 - 显式向下传递可观察对象;除非 UI 确实需要,否则避免使用可选状态。
- 如果部署目标包含 iOS 16 或更早版本,则在所属视图中使用
@StateObject,并在注入旧版可观察模型时使用@ObservedObject。
工作流
- 按照排序规则重新排列视图。
- 从
body中移除内联操作和副作用;将业务逻辑移入服务/模型,视图中只保留精简的协调逻辑。 - 将较长的
body拆分为专用子视图类型;避免通过许多计算属性some View辅助方法重建屏幕。 - 确保视图结构稳定:避免在顶层通过
if切换分支;将条件移至局部区域或修饰符中。 - 如果存在视图模型或明确要求使用视图模型,请将可选视图模型替换为非可选的
@State视图模型,并在init中初始化。 - 确认 Observation 的用法:在 iOS 17 及更高版本中,根
@Observable模型使用@State;只有在部署目标要求时才使用旧式包装器。 - 保持行为不变:除非收到要求,否则不要更改布局或业务逻辑。
说明
- 优先使用小巧、明确的视图类型,避免使用大型条件代码块和大型计算属性
some View。 - 将计算视图构建器放在
body之后,将非视图计算变量放在init之前。 - 优秀的 SwiftUI 重构应让视图从上到下呈现数据流和布局,而不是混杂布局与命令式逻辑。
- 关于以 MV 为先的指导和原因,请参阅
references/mv-patterns.md。 - 除上述参考资料外,当 SwiftUI API、Observation 行为或平台指导可能发生变化时,请使用网络搜索查阅最新的 Apple Developer 文档。
大型视图的处理
当 SwiftUI 视图文件超过约 300 行时,应积极拆分。将有意义的区域提取为专用 View 类型,而不是将复杂逻辑藏在多个计算属性中。可以使用带有 // MARK: - 注释的 private 扩展来组织操作和辅助方法,但不要用扩展代替将大型屏幕拆分为较小的视图类型。如果提取的子视图会被复用或本身具有独立意义,请将其移至单独的文件中。
评论
支持 Markdown