swiftui-view-refactor by dimillian/skills
npx skills add https://github.com/dimillian/skills --skill swiftui-view-refactor将 SwiftUI 视图重构为小型、明确、稳定的视图类型。默认使用原生 SwiftUI:视图中的本地状态、环境中的共享依赖项、服务/模型中的业务逻辑,仅在请求或现有代码明确要求时才使用视图模型。
private/public let@State / 其他存储属性var(非视图)initbody广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
@State、@Environment、@Query、.task、.task(id:) 和 onChange。@Environment 注入服务和共享模型;将领域逻辑保留在服务/模型中,而不是视图主体中。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)
}
}
body 中提取出来.task、.onAppear、.onChange 或 .refreshable 中。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)
}
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
}
}
init 将依赖项传递给视图,然后在视图的 init 中创建视图模型。bootstrapIfNeeded 模式和其他延迟设置的变通方法。示例(基于 Observation):
@State private var viewModel: SomeViewModel
init(dependency: Dependency) {
_viewModel = State(initialValue: SomeViewModel(dependency: dependency))
}
@Observable 引用类型,将其作为 @State 存储在所属视图中。@StateObject,并在注入遗留可观察模型时使用 @ObservedObject。body 中移除内联操作和副作用;将业务逻辑移动到服务/模型中,视图中仅保留薄编排层。some View 辅助函数重建屏幕。if 的分支交换;将条件移动到局部化的部分/修饰符。init 中初始化的非可选 @State 视图模型替换可选视图模型。@Observable 模型使用 @State,仅在部署目标要求时使用遗留包装器。some View 属性。body 下方,将非视图的计算 var 放在 init 上方。references/mv-patterns.md。当 SwiftUI 视图文件超过约 300 行时,请积极拆分它。将有意义的部分提取到专用的 View 类型中,而不是将复杂性隐藏在众多计算属性中。对于操作和辅助函数,使用带有 // MARK: - 注释的 private 扩展,但不要将扩展视为将巨型屏幕拆分为更小视图类型的替代方案。如果提取的子视图被重用或具有独立意义,请将其移动到自己的文件中。
每周安装量
872
代码仓库
GitHub 星标
2.3K
首次出现
2026年1月20日
安全审计
安装于
codex703
opencode676
claude-code666
gemini-cli654
cursor590
github-copilot553
Refactor SwiftUI views toward small, explicit, stable view types. Default to vanilla SwiftUI: local state in the view, shared dependencies in the environment, business logic in services/models, and view models only when the request or existing code clearly requires one.
private/public let@State / other stored propertiesvar (non-view)initbody@State, @Environment, @Query, .task, .task(id:), and onChange before reaching for a view model.@Environment; keep domain logic in services/models, not in the view body.some View helpersbody properties that are longer than roughly one screen or contain multiple logical sections.View types for non-trivial sections, especially when they have state, async work, branching, or deserve their own preview.some View helpers rare and small. Do not build an entire screen out of private var header: some View-style fragments.Prefer:
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 }
}
}
}
}
}
Avoid:
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)
}
}
bodyDo not keep non-trivial button actions inline in the view body.
Do not bury business logic inside .task, .onAppear, .onChange, or .refreshable.
Prefer calling small private methods from the view, and move real business logic into services/models.
The body should read like UI, not like a view controller.
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) }
body or computed views that return completely different root branches via if/else.overlay, opacity, disabled, toolbar, etc.).Prefer:
var body: some View {
List {
documentsListContent
}
.toolbar {
if canEdit {
editToolbar
}
}
}
Avoid:
var documentsListView: some View {
if canEdit {
editableDocumentsList
} else {
readOnlyDocumentsList
}
}
init, then create the view model in the view's init.bootstrapIfNeeded patterns and other delayed setup workarounds.Example (Observation-based):
@State private var viewModel: SomeViewModel
init(dependency: Dependency) {
_viewModel = State(initialValue: SomeViewModel(dependency: dependency))
}
@Observable reference types on iOS 17+, store them as @State in the owning view.@StateObject at the owner and @ObservedObject when injecting legacy observable models.body; move business logic into services/models and keep only thin orchestration in the view.some View helpers.if-based branch swapping; move conditions to localized sections/modifiers.@State view model initialized in init.@State for root @Observable models on iOS 17+, legacy wrappers only when the deployment target requires them.some View properties.body and non-view computed vars above init.references/mv-patterns.md.When a SwiftUI view file exceeds ~300 lines, split it aggressively. Extract meaningful sections into dedicated View types instead of hiding complexity in many computed properties. Use private extensions with // MARK: - comments for actions and helpers, but do not treat extensions as a substitute for breaking a giant screen into smaller view types. If an extracted subview is reused or independently meaningful, move it into its own file.
Weekly Installs
872
Repository
GitHub Stars
2.3K
First Seen
Jan 20, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex703
opencode676
claude-code666
gemini-cli654
cursor590
github-copilot553
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
102,200 周安装