【六大移动UI框架比较学与练】第2课:工程目录结构与基础语法差异对比
【六大移动UI框架比较学与练】第2课:工程目录结构与基础语法差异对比
合集名称:移动开发六大声明式UI框架比较学习合集
合集简介:
聚焦Jetpack Compose、SwiftUI、ArkUI、Compose Multiplatform、React Native、Flutter六大声明式UI框架,以经典示例代码对照讲解,拆解语法与底层渲染原理,区分原生、跨端特性。采用学与练:研习框架原理,每课配套15道多元化习题,理论结合实操,系统掌握现代化移动UI开发体系。
六大框架分类图
全套33课大纲
第一阶段:环境入门与基础认知(1–6)
- 第1课:搭建环境,创建第一个应用
- 第2课:工程目录结构与基础语法差异对比
- 第3课:文本、图片、布局核心组件实战对照
- 第4课:样式修饰、尺寸边距与界面样式开发
- 第5课:声明式UI核心状态机制入门详解
- 第6课:各框架UI渲染更新基础原理初识
第二阶段:状态机制与渲染核心(7–12)
- 第7课:状态提升与单向数据流标准实现对照
- 第8课:状态装饰器作用域与响应机制深度对比
- 第9课:重组刷新底层机制与渲染差异解析
- 第10课:不可变数据设计思想与框架实践规范
- 第11课:懒加载列表组件性能对比与最优写法
- 第12课:UI无效刷新成因与状态性能避坑指南
第三阶段:页面交互与适配(13–17)
- 第13课:页面路由跳转、传参与栈管理对比
- 第14课:点击、输入、手势交互事件体系对照
- 第15课:弹窗、浮层、抽屉通用组件实现对比
- 第16课:主题切换与深色模式适配方案
- 第17课:多设备屏幕适配与响应式布局开发
第四阶段:通用工程核心能力(18–23)
- 第18课:异步并发编程模型与写法全面对照
- 第19课:网络请求封装、拦截与异常统一处理
- 第20课:本地键值存储与数据库方案对比选型
- 第21课:MVVM架构分层规范与落地实践
- 第22课:Repository数据仓库模式统一实现
- 第23课:系统权限适配与原生能力调用对照
第五阶段:性能优化与工程发布(24–28)
- 第24课:UI渲染性能瓶颈与框架优化方案
- 第25课:官方调试工具使用与性能排查实战
- 第26课:图片加载、缓存策略与内存优化
- 第27课:项目模块化与多环境工程配置
- 第28课:项目打包、签名与应用上架流程对比
第六阶段:横向总结与技术选型(29–33)
- 第29课:六大框架全方位优劣与特性横向对比
- 第30课:原生开发与跨端开发核心差异深度总结
- 第31课:不同业务场景框架选型最优策略
- 第32课:声明式UI五层渲染架构模型深度剖析
- 第33课:全系列技术总结与进阶学习路线规划
本文目录
- 一、本课导学
- 二、工程结构的三个根本问题
- 三、六大框架工程结构总览
- 四、Jetpack Compose:Android 模块 + Compose 包
- 五、SwiftUI:职责分层的自由与代价
- 六、ArkUI:强约定下的模块化
- 七、Compose Multiplatform:source set 的共享哲学
- 八、React Native:双壳架构的跨端策略
- 九、Flutter:单 lib + 平台壳的极简结构
- 十、六大框架目录结构深度对比
- 十一、本课核心认知
- 十二、习题
- 十三、知识点总结
- 十四、本课比较后的十个深刻感知
- 下期预告
一、本课导学
1.1 从“能跑”到“看懂”
第1课让六个框架都跑起来了。但一个可运行的工程,不等于一个结构清晰的工程。跑通之后,真正的困惑才开始:
- 六个框架的目录结构为什么差异这么大?哪些是必须的,哪些可以删?
- 源码、资源、配置文件分别放在哪里?各自控制什么?
- 为什么 Flutter 有
lib/,React Native 有android/和ios/,Compose Multiplatform 有commonMain/? - 同一个“Hello World”,六个框架的代码风格为什么完全不同?
这些困惑指向一个核心认知:工程目录结构是框架设计哲学的空间投影。理解了目录结构,就理解了框架如何看待“代码、资源、配置、平台”之间的关系。
1.2 本课目标
学完本课,你应能:
- 说出六大框架的标准工程目录结构及关键文件职责。
- 对比六大框架的入口文件、源码目录、资源目录、配置文件的位置差异。
- 理解 KMP source set、Flutter
lib/、RNandroid/+ios/等典型结构的含义。 - 区分声明式 UI 语法的三种风格:函数式 DSL、装饰器式、Widget 树式。
- 完成15道习题并理解每道题的解析。
- 从目录结构差异中,读出框架的设计哲学与平台策略。
1.3 适用边界
本课适用于:
- 已完成第1课环境搭建的开发者。
- 需要为团队制定工程规范的架构人员。
- 需要从 A 框架迁移到 B 框架的开发者。
本课不适用于:
- 尚未跑通任何一个框架的初学者。
- 需要深入状态管理与性能优化的读者。这些内容将在第7–12课展开。
1.4 术语约定
| 术语 | 定义 |
|---|---|
| Source Set | Kotlin Multiplatform 中按平台组织的源码集合,如 commonMain、androidMain |
| Widget 树 | Flutter 中由 Widget 嵌套形成的 UI 描述树 |
| 声明式 DSL | 用函数调用链描述 UI 结构的编程风格,如 Compose、SwiftUI、ArkUI |
| 组件树 | React Native 中由 React 组件嵌套形成的 UI 描述结构 |
| 入口文件 | 应用启动时最先执行的文件,通常包含应用根组件或启动逻辑 |
| 结构范式 | 框架组织源码、资源、配置、平台代码的总体模式 |
1.5 知识地图
二、工程结构的三个根本问题
每个框架的工程结构,本质上都在回答三个问题。这三个问题构成工程结构的“铁三角”,缺一不可:
这三个问题的重要性在于:
- 问题一决定心智负担。源码目录越分散,开发者越需要理解“什么代码该放哪里”。
- 问题二决定工程门槛。配置文件越复杂,越需要理解构建工具的设计理念。
- 问题三决定跨端代价。平台代码的隔离方式,直接影响代码共享的边界与风险。
本课的核心方法:用这三个问题作为统一的“坐标系”,逐一分析六大框架的工程结构。这样对比才有结构性,而不是零散的目录罗列。
三、六大框架工程结构总览
3.1 六大框架工程结构速查表
| 框架 | 源码目录 | 资源目录 | 配置文件 | 平台特定目录 |
|---|---|---|---|---|
| Jetpack Compose | app/src/main/java/ | app/src/main/res/ | build.gradle.kts、AndroidManifest.xml | 无(Android 单平台) |
| SwiftUI | MyApp/ | MyApp/Assets.xcassets/ | MyApp.xcodeproj、Info.plist | 无(Apple 单平台) |
| ArkUI | entry/src/main/ets/ | entry/src/main/resources/ | app.json5、module.json5、oh-package.json5 | 无(HarmonyOS 单平台) |
| Compose Multiplatform | shared/src/commonMain/kotlin/ | shared/src/commonMain/composeResources/ | build.gradle.kts、settings.gradle.kts | androidMain/、iosMain/、desktopMain/ |
| React Native | src/ | src/assets/ | package.json、metro.config.js | android/、ios/ |
| Flutter | lib/ | assets/ | pubspec.yaml | android/、ios/、web/ 等 |
3.2 两种结构范式
从这张表可以一眼看出:原生框架(Compose、SwiftUI、ArkUI)的工程结构相对统一,跨端框架(CMP、RN、Flutter)的工程结构差异巨大。
为什么原生框架结构统一? 因为它们只服务一个平台,不存在“平台怎么分”的问题。为什么跨端框架差异巨大? 因为它们的跨端策略完全不同——KMP 用 source set 分平台,RN 用双目录分平台,Flutter 用单 lib/ 加平台壳分平台。
这个差异不是随意的工程约定,而是每个框架对“跨端”这个问题的不同回答。后续每一节的逐一剖析,都在回答这个核心问题。
四、Jetpack Compose:Android 模块 + Compose 包
4.1 标准目录树
MyComposeApp/
├── app/ # 应用模块
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/com/example/myapp/
│ │ │ │ ├── MainActivity.kt # 入口 Activity
│ │ │ │ ├── ui/
│ │ │ │ │ ├── theme/ # Material 3 主题
│ │ │ │ │ └── components/ # 自定义 Composable
│ │ │ │ └── data/ # 数据层
│ │ │ ├── res/ # 资源文件
│ │ │ └── AndroidManifest.xml # 应用清单
│ │ ├── test/ # 单元测试
│ │ └── androidTest/ # 仪器测试
│ └── build.gradle.kts # 模块级构建配置
├── build.gradle.kts # 项目级构建配置
├── settings.gradle.kts # 模块与插件管理
└── gradle.properties # Gradle 属性
4.2 关键文件职责
| 文件 | 职责 | 类比 |
|---|---|---|
MainActivity.kt | 应用入口,setContent 启动 Compose | SwiftUI 的 MyApp.swift |
ui/theme/ | Material 3 主题配置 | Flutter 的 ThemeData |
AndroidManifest.xml | 声明 Activity、权限、应用信息 | iOS 的 Info.plist |
build.gradle.kts | 依赖、插件、编译配置 | pubspec.yaml、package.json |
4.3 语法特征:函数式 DSL
setContent {
MaterialTheme {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Hello", fontSize = 24.sp)
Button(onClick = { }) {
Text("Click")
}
}
}
}
四个关键特征:
- 一切 UI 都是
@Composable函数。 - 布局通过嵌套函数调用来表达。
- 修饰符用
Modifier链式调用。 - 没有 XML 布局文件,UI 即代码。
4.4 结构范式:渐进增强
Compose 的目录结构是“标准的 Android 模块结构 + Compose 的 UI 包组织”。它没有为声明式 UI 创造新的目录范式,而是在 Android 既有结构上叠加了 Compose 的代码组织方式。
这说明 Compose 是 Android 生态的“渐进增强”,而非“另起炉灶”。 它不需要新的构建系统、新的资源体系、新的工程结构,只需要在既有 Android 工程中引入 Compose 依赖和 setContent 入口。这种设计降低了 Android 开发者的迁移成本,也是 Compose 能快速普及的重要原因。
五、SwiftUI:职责分层的自由与代价
5.1 标准目录树
MySwiftUIApp/
├── MySwiftUIApp/ # 主源码目录
│ ├── MySwiftUIApp.swift # @main 应用入口
│ ├── ContentView.swift # 根视图
│ ├── Models/ # 数据模型
│ ├── Services/ # 业务逻辑
│ ├── ViewModels/ # 状态管理
│ ├── Views/ # SwiftUI 视图
│ ├── Extensions/ # 扩展
│ └── Assets.xcassets/ # 资源目录
├── MySwiftUIApp.xcodeproj/ # Xcode 工程文件
└── MySwiftUIAppTests/ # 单元测试
5.2 关键文件职责
| 文件 | 职责 |
|---|---|
MySwiftUIApp.swift | @main 标记的应用入口,定义 App 场景 |
ContentView.swift | 默认根视图 |
Assets.xcassets/ | 图片、颜色、图标等资源 |
Info.plist | 应用配置(在 Xcode 中可视化编辑) |
5.3 语法特征:声明式结构体组合
struct ContentView: View {
@State private var count = 0
var body: some View {
VStack(spacing: 16) {
Text("Count: \(count)")
.font(.title)
.foregroundColor(.blue)
Button("Increment") {
count += 1
}
.buttonStyle(.borderedProminent)
}
.padding()
}
}
五个关键特征:
View协议 +body属性描述界面。@State、@Binding、@Observable管理状态。- 修饰符通过
.链式调用。 - 没有 XML,UI 即 Swift 代码。
- 目录按职责划分(Models、Views、ViewModels)。
5.4 结构范式:自由与自律的平衡
SwiftUI 的目录结构是“按职责分层的 MVC/MVVM 结构”。Apple 没有强制约定目录,但社区形成了 Models/、Views/、ViewModels/ 的惯例。
这是六大框架中结构自由度最高的一个,但也因此最依赖团队自律。 Apple 只给了一个空白工程,目录怎么建全靠团队规范。这既是优势——团队可以根据项目特点自由组织;也是风险——没有统一约定时,不同开发者可能形成完全不同的代码组织方式。
六、ArkUI:强约定下的模块化
6.1 标准目录树
MyArkUIApp/
├── AppScope/ # 应用级配置(不可改名)
│ ├── app.json5 # 应用全局配置
│ └── resources/ # 应用级资源
├── entry/ # 主模块(可自定义名)
│ ├── src/
│ │ └── main/
│ │ ├── ets/ # ArkTS 源码
│ │ │ ├── entryability/
│ │ │ │ └── EntryAbility.ets # 入口 Ability
│ │ │ ├── pages/
│ │ │ │ └── Index.ets # 页面
│ │ │ └── components/ # 自定义组件
│ │ ├── resources/ # 模块级资源
│ │ └── module.json5 # 模块配置
│ ├── build-profile.json5 # 模块级构建配置
│ ├── hvigorfile.ts # 构建任务脚本
│ └── oh-package.json5 # 依赖管理
├── build-profile.json5 # 项目级构建配置
├── hvigorfile.ts # 项目级构建脚本
└── oh-package.json5 # 项目级依赖
6.2 关键文件职责
| 文件 | 职责 | 注意 |
|---|---|---|
AppScope/app.json5 | 应用全局配置(bundle name、版本、图标) | 目录名不可修改 |
module.json5 | 模块信息、设备类型、权限 | 每个模块一份 |
EntryAbility.ets | 入口 Ability,管理页面栈 | 类似 Android 的 Activity |
pages/Index.ets | 页面级组件 | @Entry 标记 |
oh-package.json5 | 依赖管理 | 类似 package.json |
6.3 语法特征:装饰器式声明
@Entry
@Component
struct Index {
@State message: string = 'Hello ArkUI'
build() {
Column({ space: 16 }) {
Text(this.message)
.fontSize(24)
.fontWeight(FontWeight.Bold)
Button('Click')
.onClick(() => {
this.message = 'Clicked!'
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
五个关键特征:
@Entry标记页面入口,@Component标记自定义组件。build()方法返回 UI 结构。- 状态用
@State、@Prop、@Link等装饰器。 - 属性通过
.链式调用。 - 目录按模块(
entry/)和 Ability 组织。
6.4 结构范式:强约定保证一致性
ArkUI 的目录结构是“模块化 + Ability 体系”的组合。AppScope/ 不可改名、entry/ 模块化、ets/ 存放源码,处处体现 HarmonyOS 的 Stage 模型设计。
这是六大框架中约束最强的结构之一。 鸿蒙生态需要强约定来保证多设备兼容——手机、平板、手表、车机、智慧屏共用同一套工程结构,如果目录结构过于自由,多设备适配将变得极其困难。约束强的好处是“不容易出错”,坏处是“灵活性受限”。
七、Compose Multiplatform:source set 的共享哲学
7.1 标准目录树(2026 默认布局)
MyKMPApp/
├── shared/ # KMP 共享库模块
│ ├── src/
│ │ ├── commonMain/ # 共享代码(默认放这里)
│ │ │ ├── kotlin/com/example/
│ │ │ │ ├── App.kt # 共享 UI
│ │ │ │ └── Greeting.kt
│ │ │ └── composeResources/ # 共享资源
│ │ ├── androidMain/ # Android 平台特定代码
│ │ ├── iosMain/ # iOS 平台特定代码
│ │ └── desktopMain/ # Desktop 平台特定代码
│ └── build.gradle.kts
├── androidApp/ # Android 应用壳
├── iosApp/ # iOS 应用壳
├── desktopApp/ # Desktop 应用壳
├── build.gradle.kts
├── settings.gradle.kts
└── gradle.properties
7.2 关键目录职责
| 目录 | 放什么 | 不放什么 |
|---|---|---|
commonMain | 共享 UI、ViewModel、业务逻辑、expect 声明 | android.*、platform.UIKit、java.awt |
androidMain | Activity、Android Context 相关实现 | iOS/Desktop 专属 API |
iosMain | ComposeUIViewController 工厂、iOS 平台实现 | android.* |
desktopMain | Desktop 窗口入口、Swing 互操作 | 移动端专属 API |
7.3 语法特征:与 Jetpack Compose 高度一致
// commonMain
@Composable
fun App() {
MaterialTheme {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center
) {
Text("Hello, Compose Multiplatform")
}
}
}
四个关键特征:
- 共享 UI 写在
commonMain,语法与 Jetpack Compose 几乎相同。 expect/actual机制处理平台差异。- 平台入口是“薄壳”,只负责调用共享
App()。 - 资源放在
composeResources/。
7.4 结构范式:共享优先,平台为例外
Compose Multiplatform 的目录结构是 source set 分层的极致体现。“common by default, platform by exception” 的原则贯穿始终——默认放 commonMain,只有必须分平台时才放 androidMain/iosMain。
这种结构的代价是隐蔽的:commonMain 必须保持 import 干净,否则 iOS 链接阶段才会报错,且错误位置远离问题源头。一个 android.* import 就能让 iOS 构建失败,但编译器可能到很晚才提示。这要求开发者对代码的平台无关性有清晰判断。
八、React Native:双壳架构的跨端策略
8.1 标准目录树
MyRNApp/
├── android/ # Android 原生工程
│ ├── app/src/main/
│ │ ├── java/com/myapp/
│ │ └── AndroidManifest.xml
│ └── build.gradle
├── ios/ # iOS 原生工程
│ ├── MyRNApp/
│ │ ├── AppDelegate.swift
│ │ └── Info.plist
│ ├── MyRNApp.xcodeproj/
│ └── Podfile
├── src/ # JS/TS 源码
│ ├── screens/ # 页面
│ ├── components/ # 通用组件
│ ├── services/ # API、工具
│ └── assets/ # 静态资源
├── App.tsx # 根组件
├── index.js # 入口文件
├── package.json # 依赖与脚本
├── metro.config.js # Metro 打包配置
└── babel.config.js # Babel 配置
8.2 关键文件职责
| 文件/目录 | 职责 |
|---|---|
App.tsx | 根 React 组件,描述应用 UI |
index.js | 注册根组件(AppRegistry.registerComponent) |
android/、ios/ | 原生工程,由 react-native init 生成 |
package.json | JS 依赖、脚本、React Native 版本 |
metro.config.js | Metro 打包器配置 |
8.3 语法特征:JSX + React Hooks
import React, { useState } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
export default function App() {
const [count, setCount] = useState(0);
return (
<View style={styles.container}>
<Text style={styles.title}>Count: {count}</Text>
<Button title="Increment" onPress={() => setCount(count + 1)} />
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, alignItems: 'center', justifyContent: 'center' },
title: { fontSize: 24, fontWeight: 'bold' },
});
五个关键特征:
- JSX 描述 UI,React Hooks 管理状态。
View、Text等组件映射到平台原生组件。- 样式用
StyleSheet.create,类似 CSS 但非 CSS。 android/和ios/是原生壳,JS 代码在src/。- Metro 负责 JS 打包,原生构建用 Gradle / Xcode。
8.4 结构范式:JS 驱动原生
React Native 的目录结构是 “双平台原生壳 + JS 核心” 的三层结构。android/ 和 ios/ 是“宿主”,src/ 是“灵魂”,Metro 是“传送带”。
这种结构的本质是:RN 不试图消灭原生工程,而是让 JS 成为原生工程的“上层建筑”。 与 Flutter 不同,RN 承认原生工程是不可或缺的基础——它不替代原生,而是驱动原生。这个设计决策直接决定了 RN 的平台目录是“主动的”,开发者需要理解它们来配置原生依赖、修改原生代码。
九、Flutter:单 lib + 平台壳的极简结构
9.1 标准目录树
my_flutter_app/
├── lib/ # Dart 源码(核心)
│ ├── main.dart # 入口文件
│ ├── app.dart # 根 Widget
│ ├── core/ # 核心工具
│ ├── features/ # 按功能组织
│ │ ├── home/
│ │ │ ├── data/
│ │ │ ├── domain/
│ │ │ └── presentation/
│ │ └── profile/
│ └── shared/ # 共享组件
├── assets/ # 静态资源
├── test/ # 测试
├── android/ # Android 平台壳
├── ios/ # iOS 平台壳
├── web/ # Web 平台壳(可选)
├── pubspec.yaml # 依赖与资源声明
└── analysis_options.yaml # 代码分析配置
9.2 关键文件职责
| 文件/目录 | 职责 |
|---|---|
lib/main.dart | 应用入口,runApp() 启动 |
lib/app.dart | 根 Widget,配置 MaterialApp 或 CupertinoApp |
lib/features/ | 按功能组织代码(Feature-Based) |
pubspec.yaml | 依赖、资源、字体声明 |
android/、ios/ 等 | 平台壳工程,由 flutter create 生成 |
9.3 语法特征:Widget 树 + 命名参数
class HomePage extends StatefulWidget {
const HomePage({super.key});
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int count = 0;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Count: $count'),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => setState(() => count++),
child: const Text('Increment'),
),
],
),
),
);
}
}
五个关键特征:
- 一切皆 Widget,UI 由 Widget 树嵌套描述。
- 状态用
setState(简单场景)或状态库(复杂场景)。 - 样式通过 Widget 的命名参数传入。
lib/是唯一的源码根目录。- 平台壳(
android/、ios/等)由flutter create生成,通常不手动修改。
9.4 结构范式:自绘渲染的底气
Flutter 的目录结构是 “单 lib/ + 多平台壳” 的极简结构。lib/ 是纯粹的 Dart 世界,平台壳是“被生成的”,开发者很少需要碰。
这种结构的底气来自 Flutter 的自绘渲染——既然 UI 不依赖平台控件,平台壳就只需要提供一个“画布”。这与 RN 的“双壳架构”形成鲜明对比:RN 的壳是“主动的”,Flutter 的壳是“被动的”。这个差异不是工程约定,而是渲染路线差异在目录结构上的直接投影。
十、六大框架目录结构深度对比
10.1 全维度对照表
| 对比项 | Jetpack Compose | SwiftUI | ArkUI | Compose Multiplatform | React Native | Flutter |
|---|---|---|---|---|---|---|
| 源码根目录 | app/src/main/java/ | MyApp/ | entry/src/main/ets/ | shared/src/commonMain/kotlin/ | src/ + App.tsx | lib/ |
| 资源目录 | res/ | Assets.xcassets/ | resources/ | composeResources/ | src/assets/ | assets/ |
| 依赖配置 | build.gradle.kts | Xcode 工程设置 | oh-package.json5 | build.gradle.kts | package.json | pubspec.yaml |
| 构建配置 | build.gradle.kts | .xcodeproj | build-profile.json5 + hvigorfile.ts | build.gradle.kts + settings.gradle.kts | metro.config.js + babel.config.js | pubspec.yaml |
| 入口文件 | MainActivity.kt | MyApp.swift | EntryAbility.ets | 各平台薄壳 | index.js → App.tsx | main.dart |
| 平台特定目录 | 无 | 无 | 无 | androidMain/、iosMain/、desktopMain/ | android/、ios/ | android/、ios/ 等 |
| 测试目录 | test/、androidTest/ | MyAppTests/ | 按模块 | 各 source set | __tests__/ | test/ |
| 结构范式 | Android 模块 + Compose 包 | 职责分层(MVC/MVVM) | 模块 + Ability 体系 | KMP source set 分层 | 双平台壳 + JS 核心 | 单 lib/ + 平台壳 |
10.2 结构差异的设计哲学对照
10.3 三个根本问题的回答对比
回到第二章提出的三个根本问题,六个框架的回答如下:
| 框架 | 代码放哪里? | 配置怎么写? | 平台怎么分? |
|---|---|---|---|
| Jetpack Compose | Android 模块内按包组织 | Gradle DSL | 不适用(单平台) |
| SwiftUI | 按职责分层,自由度高 | Xcode 工程设置 | 不适用(单平台) |
| ArkUI | 模块 + Ability,强约定 | oh-package.json5 + hvigor | 不适用(单平台) |
| Compose Multiplatform | 按 source set 分层 | Gradle + KMP 插件 | commonMain + 平台 source set |
| React Native | src/ + 双原生壳 | package.json + Metro | android/ + ios/ 双目录 |
| Flutter | 单 lib/ 目录 | pubspec.yaml | lib/ + 平台壳 |
这张表就是本课的核心结论:六个框架对三个根本问题的回答,决定了它们的工程结构范式。理解了这个对应关系,就理解了“为什么目录长这样”。
十一、本课核心认知
11.1 核心认知
- 工程目录结构是框架设计哲学的空间投影。
- 原生框架结构相对统一,跨端框架结构差异巨大。
- Compose Multiplatform 的 source set 是“共享优先”的体现。
- React Native 的
android/+ios/是“JS 驱动原生”的体现。 - Flutter 的
lib/+ 平台壳是“自绘渲染”的体现。 - 声明式语法有三大风格:函数式 DSL(Compose、SwiftUI)、装饰器式(ArkUI)、Widget 树式(Flutter、React Native)。
11.2 限制条件
- 本课只覆盖标准工程结构,不涉及模块化拆分与多模块工程。
- 不同框架版本可能调整默认结构,请以官方模板为准。
- 本课不展开状态管理、路由、网络等具体实现。
- 目录结构的最佳实践因团队规模、项目复杂度而异。
十二、习题
12.1 选择题(4题)
1. 下列哪个框架使用 source set 机制来组织跨平台代码?
A. Flutter
B. React Native
C. Compose Multiplatform
D. SwiftUI
参考答案:C
解读:Compose Multiplatform 基于 Kotlin Multiplatform,使用 commonMain、androidMain、iosMain 等 source set 组织共享代码与平台特定代码。Flutter 用 lib/ 加平台壳,React Native 用 android/ 和 ios/ 双目录,SwiftUI 是单平台。
2. React Native 项目中,JS/TS 源码通常放在哪个目录?
A. lib/
B. src/ 和 App.tsx
C. app/src/main/java/
D. entry/src/main/ets/
参考答案:B
解读:React Native 的 JS/TS 源码放在 src/ 目录和根级 App.tsx 中。lib/ 是 Flutter 的源码目录,app/src/main/java/ 是 Android/Compose 的目录,entry/src/main/ets/ 是 ArkUI 的目录。
3. ArkUI 工程中,AppScope/ 目录的作用是什么?
A. 存放 ArkTS 源码
B. 存放应用级全局配置与资源
C. 存放测试代码
D. 存放依赖包
参考答案:B
解读:AppScope/ 存放应用级全局配置(app.json5)和资源,由 DevEco Studio 自动生成,目录名不可修改。ArkTS 源码放在 entry/src/main/ets/,测试代码按模块组织,依赖通过 oh-package.json5 管理。
4. Flutter 项目中,lib/ 目录的作用是?
A. 存放平台特定代码
B. 存放 Dart 源码
C. 存放原生依赖
D. 存放构建产物
参考答案:B
解读:Flutter 的 lib/ 目录存放所有 Dart 源码,包括 main.dart 入口和功能代码。平台特定代码在 android/、ios/ 等壳目录中,依赖在 pubspec.yaml 中声明。
12.2 判断题(3题)
5. Jetpack Compose 和 Compose Multiplatform 使用完全相同的语法来描述 UI。
参考答案:正确
解读:两者的 UI 描述语法几乎完全相同,都使用 @Composable 函数和 Modifier 修饰符。区别在于工程结构和平台策略:Compose Multiplatform 把共享 UI 放在 commonMain,Jetpack Compose 是 Android 单平台。
6. React Native 的 android/ 和 ios/ 目录是“可选的”,可以完全删除。
参考答案:错误
解读:android/ 和 ios/ 是 React Native 的原生壳工程,包含平台入口、构建配置、签名等必要内容。虽然开发者很少修改它们,但它们是应用能运行的基础,不能删除。
7. Flutter 的 lib/ 目录和 React Native 的 src/ 目录在功能上完全等价。
参考答案:错误
解读:两者都是源码目录,但 Flutter 的 lib/ 是唯一的 Dart 源码根目录,所有代码都在其中;React Native 的 src/ 只是 JS/TS 代码的组织目录之一,根级还有 App.tsx、index.js 等文件。此外,Flutter 的平台壳是“被生成的”,RN 的平台壳是“被引导的”,含义不同。
12.3 填空题(3题)
8. Compose Multiplatform 中,共享 UI 代码应放在 ________ source set 中。
参考答案:commonMain
解读:commonMain 存放所有平台共享的代码,包括 @Composable 函数、ViewModel、业务逻辑。平台特定代码放在 androidMain、iosMain、desktopMain 等 source set 中。
9. Flutter 项目的依赖与资源配置文件是 ________。
参考答案:pubspec.yaml
解读:pubspec.yaml 声明依赖包、资源文件、字体、应用版本等信息。类似 Android 的 build.gradle.kts、RN 的 package.json、ArkUI 的 oh-package.json5。
10. ArkUI 的 @Entry 装饰器标记的是 ________。
参考答案:页面入口
解读:@Entry 标记页面入口组件,配合 @Component 定义自定义组件。build() 方法返回 UI 结构。
12.4 简答题(2题)
11. 简述 Compose Multiplatform 的 source set 分层结构,以及 commonMain 中“不能放什么”。
参考答案:
Compose Multiplatform 使用 source set 组织跨平台代码:commonMain 存放共享 UI、ViewModel、业务逻辑;androidMain、iosMain、desktopMain 存放平台特定代码。
commonMain 中不能放平台专属的 import,如 android.*、platform.UIKit、java.awt、androidx.activity。这些 import 会破坏其他平台的编译,且错误通常在 iOS 链接阶段才暴露,远离问题源头。
解读:source set 的核心原则是“common by default, platform by exception”——默认放 commonMain,只有必须分平台时才放平台 source set。这要求开发者对代码的平台无关性有清晰判断。
12. 对比 React Native 和 Flutter 在“平台目录”设计上的差异,并说明各自的设计哲学。
参考答案:
React Native 使用 android/ 和 ios/ 两个原生壳工程,它们是应用能运行的基础,包含平台入口、构建配置、签名等。JS/TS 代码在 src/ 和根级文件中,通过 Metro 打包后由原生壳加载。设计哲学:不消灭原生工程,让 JS 成为原生工程的“上层建筑”。
Flutter 也使用 android/、ios/ 等平台壳,但这些壳是 flutter create 生成的,开发者很少需要修改。Dart 代码全部在 lib/ 中,通过自绘渲染不依赖平台控件。设计哲学:UI 由框架自绘,平台壳只需提供“画布”。
解读:RN 的平台目录是“主动的”——开发者需要理解它;Flutter 的平台目录是“被动的”——开发者可以忽略它。这反映了两种跨端策略的根本差异:RN 依赖原生组件,Flutter 自绘一切。
12.5 代码补全 / 实操题(2题)
13. 补全 Compose Multiplatform 的共享 UI 代码:
// commonMain
@Composable
fun App() {
MaterialTheme {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center
) {
Text("Hello, CMP")
}
}
}
参考答案:
// commonMain
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@Composable
fun App() {
MaterialTheme {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Hello, CMP")
}
}
}
解读:共享 UI 代码放在 commonMain,使用与 Jetpack Compose 相同的语法。注意 import 必须来自 Compose 标准库,不能引用平台特定 API。
14. 从六大框架中任选一个,写出其标准工程目录树(至少三层),并标注入口文件。
参考答案:以 Flutter 为例:
my_app/
├── lib/ # Dart 源码
│ ├── main.dart # 入口文件
│ ├── app.dart # 根 Widget
│ └── features/ # 功能模块
│ └── home/
│ ├── data/
│ ├── domain/
│ └── presentation/
├── assets/ # 静态资源
├── android/ # Android 平台壳
├── ios/ # iOS 平台壳
└── pubspec.yaml # 依赖与资源配置
解读:Flutter 的结构特点是“单 lib/ 源码根 + 多平台壳”。main.dart 是入口,pubspec.yaml 是配置文件,平台壳由 flutter create 生成。
12.6 综合题(1题)
15. 从“源码目录、资源目录、配置文件、平台特定目录”四个维度,制作六大框架工程结构对照表,并说明每个框架的“结构范式”。
参考答案:
| 维度 | Jetpack Compose | SwiftUI | ArkUI | Compose Multiplatform | React Native | Flutter |
|---|---|---|---|---|---|---|
| 源码目录 | app/src/main/java/ | MyApp/ | entry/src/main/ets/ | shared/src/commonMain/kotlin/ | src/ + App.tsx | lib/ |
| 资源目录 | res/ | Assets.xcassets/ | resources/ | composeResources/ | src/assets/ | assets/ |
| 配置文件 | build.gradle.kts | Xcode 工程 | oh-package.json5 | build.gradle.kts | package.json | pubspec.yaml |
| 平台特定目录 | 无 | 无 | 无 | androidMain/ 等 | android/、ios/ | android/、ios/ 等 |
| 结构范式 | Android 模块 + Compose 包 | 职责分层 | 模块 + Ability | Source set 分层 | 双平台壳 + JS 核心 | 单 lib/ + 平台壳 |
解读:原生框架的结构范式是“平台内组织”,跨端框架的结构范式是“按共享粒度或 JS/原生边界组织”。理解结构范式,比记住具体目录更重要——因为目录会随版本变化,范式相对稳定。
十三、知识点总结
13.1 工程目录结构知识点
- Jetpack Compose:标准 Android 模块结构,
app/src/main/java/放源码,res/放资源,build.gradle.kts配置构建。 - SwiftUI:按职责分层,
MyApp.swift是入口,Views/、Models/、ViewModels/组织代码,Assets.xcassets/放资源。 - ArkUI:
AppScope/不可改名,entry/src/main/ets/放 ArkTS 源码,resources/放资源,oh-package.json5管理依赖。 - Compose Multiplatform:
commonMain放共享 UI,androidMain/iosMain/desktopMain放平台代码,composeResources/放资源。 - React Native:
src/放 JS/TS 源码,android/和ios/是原生壳,package.json管理依赖,Metro 打包。 - Flutter:
lib/放 Dart 源码,assets/放资源,pubspec.yaml管理依赖,平台壳由flutter create生成。
13.2 语法风格知识点
- 函数式 DSL:Jetpack Compose、SwiftUI、Compose Multiplatform。UI 是函数调用链或结构体组合,修饰符用
.链式调用。 - 装饰器式:ArkUI。
@Entry、@Component、@State装饰器 +build()方法。 - Widget 树式:Flutter。一切皆 Widget,嵌套 Widget 描述 UI,命名参数传样式。
- JSX + Hooks:React Native。JSX 描述 UI,React Hooks 管理状态。
13.3 设计哲学知识点
- 原生框架结构统一,跨端框架结构差异巨大。
- Compose Multiplatform 的 source set 是“共享优先”的体现。
- React Native 的
android/+ios/是“JS 驱动原生”的体现。 - Flutter 的
lib/+ 平台壳是“自绘渲染”的体现。 - ArkUI 的
AppScope/不可改名,体现鸿蒙生态的强约定。
13.4 学习路线知识点
- 先理解“源码、资源、配置、平台”四个维度的组织方式。
- 再理解每个框架的“结构范式”。
- 最后理解结构范式与设计哲学的对应关系。
十四、本课比较后的十个深刻感知
本课完成了六大框架工程目录结构与基础语法的横向对比。在这个具体比较过程中,浮现出十个值得深思的感知。这些感知全部来自本课的实际比较内容,后续每一课都会回到这些感知上,验证、丰富、深化它们。
14.1 目录结构是设计哲学的空间投影
| 框架 | 目录结构特征 | 设计哲学 |
|---|---|---|
| Jetpack Compose | 标准 Android 模块 + Compose 包 | 渐进增强,不另起炉灶 |
| SwiftUI | 职责分层,自由度最高 | 信任开发者自律 |
| ArkUI | 模块 + Ability,AppScope/ 不可改名 | 强约定保证多设备兼容 |
| Compose Multiplatform | source set 分层 | 共享优先,平台为例外 |
| React Native | 双平台壳 + JS 核心 | 不消灭原生,JS 作为上层 |
| Flutter | 单 lib/ + 平台壳 | 自绘渲染,平台壳只是画布 |
深刻感知:你看到的目录树,就是框架设计者眼中的世界地图。Compose 把 UI 挂在 Android 模块里,说明它把自己定位为“Android 生态的一部分”;Flutter 把 Dart 代码全放 lib/,说明它认为“UI 应该独立于平台”;RN 保留 android/ 和 ios/,说明它承认“原生工程是不可或缺的基础”。
14.2 源码目录的数量,暴露了共享粒度
| 框架 | 源码根目录数量 | 共享粒度 |
|---|---|---|
| Jetpack Compose | 1 个 | 单平台,无需共享 |
| SwiftUI | 1 个 | 单平台,无需共享 |
| ArkUI | 1 个 | 单平台,无需共享 |
| Compose Multiplatform | 4 个以上 | 按 source set 共享 |
| React Native | 3 个 | 按 JS/原生边界共享 |
| Flutter | 1 个核心 + 多个平台壳 | 按 Dart/平台边界共享 |
深刻感知:源码目录的数量,就是框架“共享粒度”的度量。Compose Multiplatform 有最多的源码目录,因为它需要精细区分“哪些代码能共享、哪些不能”;Flutter 只有一个 lib/,因为它把所有 UI 代码都视为可共享;RN 有三个源码目录,因为它需要区分 JS 代码和两套原生壳。
14.3 资源目录的差异,反映了对“资源”的定义
| 框架 | 资源目录 | 资源类型 | 特点 |
|---|---|---|---|
| Jetpack Compose | res/ | 图片、字符串、颜色、样式 | 传统 Android 资源体系 |
| SwiftUI | Assets.xcassets/ | 图片、颜色、图标 | Asset Catalog 体系 |
| ArkUI | resources/ | 图片、多媒体、字符串、布局 | 按 base/rawfile 分层 |
| Compose Multiplatform | composeResources/ | 图片、字符串、字体 | 跨平台共享资源 |
| React Native | src/assets/ | 图片、字体 | 通过 JS import 引用 |
| Flutter | assets/ | 图片、字体、JSON | 在 pubspec.yaml 声明 |
深刻感知:资源目录的差异,暴露了框架对“资源”的不同理解。Android 和鸿蒙把资源视为“需要编译和索引的体系”;SwiftUI 把资源视为“Asset Catalog 中的资产”;Flutter 把资源视为“需要声明的静态文件”;RN 把资源视为“可以被 JS 引用的模块”。资源的组织方式,直接影响资源引用的心智模型。
14.4 配置文件的命名,映射了构建工具血统
| 框架 | 配置文件 | 血统 |
|---|---|---|
| Jetpack Compose | build.gradle.kts | Gradle / JVM 生态 |
| SwiftUI | .xcodeproj + Info.plist | Xcode / LLVM 生态 |
| ArkUI | oh-package.json5 + hvigorfile.ts | Node.js / hvigor 生态 |
| Compose Multiplatform | build.gradle.kts + settings.gradle.kts | Gradle / KMP 生态 |
| React Native | package.json + metro.config.js | npm / Metro 生态 |
| Flutter | pubspec.yaml | Dart / Pub 生态 |
深刻感知:配置文件的名字,就是框架构建工具血统的身份证。.gradle.kts 属于 JVM 世界,package.json 属于 npm 世界,pubspec.yaml 属于 Dart 世界,oh-package.json5 是鸿蒙对 npm 的对标。理解配置文件的语言和约定,就理解了框架的构建哲学。
14.5 平台目录的“存在感”,决定开发者的关注重心
| 框架 | 平台目录 | 开发者是否需要关注 |
|---|---|---|
| Jetpack Compose | 无 | 不适用 |
| SwiftUI | 无 | 不适用 |
| ArkUI | 无 | 不适用 |
| Compose Multiplatform | androidMain/ 等 | 需要关注,但默认不碰 |
| React Native | android/、ios/ | 需要理解,可能修改 |
| Flutter | android/、ios/ 等 | 基本可以忽略 |
深刻感知:平台目录的“存在感”,就是框架对开发者的“关注引导”。RN 的 android/ 和 ios/ 是“主动的”——开发者需要理解它们来配置原生依赖、修改原生代码;Flutter 的平台壳是“被动的”——flutter create 生成后,开发者很少需要碰;Compose Multiplatform 的平台 source set 是“按需的”——只有需要平台特定实现时才碰。
14.6 声明式语法的三种风格,对应三种“UI 描述观”
| 风格 | 框架 | UI 描述观 |
|---|---|---|
| 函数式 DSL | Compose、SwiftUI、CMP | UI 是函数的组合,修饰符是函数的参数 |
| 装饰器式 | ArkUI | UI 是组件的结构,装饰器标记入口和状态 |
| Widget 树式 | Flutter、RN | UI 是树的嵌套,属性是节点的参数 |
深刻感知:语法风格反映了框架的“UI 描述观”。Compose 和 SwiftUI 认为“UI 是函数调用链”,ArkUI 认为“UI 是组件结构 + 装饰器”,Flutter 和 RN 认为“UI 是树”。这三种描述观没有优劣,但会影响开发者写代码时的“思维形状”——函数式 DSL 让开发者思考“组合”,Widget 树让开发者思考“嵌套”。
14.7 入口文件的差异,暴露了“启动模型”
| 框架 | 入口文件 | 启动模型 |
|---|---|---|
| Jetpack Compose | MainActivity.kt | Activity 生命周期启动 |
| SwiftUI | MyApp.swift | App 场景启动 |
| ArkUI | EntryAbility.ets | Ability 启动 |
| Compose Multiplatform | 各平台薄壳 | 平台入口调用共享 App() |
| React Native | index.js → App.tsx | JS 注册组件,原生壳加载 |
| Flutter | main.dart | main() → runApp() |
深刻感知:入口文件是框架“启动模型”的出口。Compose 从 Activity 启动,SwiftUI 从 App 场景启动,ArkUI 从 Ability 启动,CMP 从平台薄壳启动,RN 从 JS 注册启动,Flutter 从 main 启动。理解入口,就理解了框架如何看待“应用的生命周期”。
14.8 目录结构的“自由度”,反映约束强度
| 框架 | 目录自由度 | 约束来源 |
|---|---|---|
| Jetpack Compose | 中 | Gradle 约定 |
| SwiftUI | 高 | Xcode 工程约定 |
| ArkUI | 低 | AppScope/ 不可改名,模块强约定 |
| Compose Multiplatform | 中 | source set 约定 |
| React Native | 中 | 原生工程约定 |
| Flutter | 低 | lib/ + pubspec.yaml 强约定 |
深刻感知:目录自由度反映了框架的约束强度。ArkUI 和 Flutter 的约束最强——AppScope/ 不能改名,lib/ 是唯一源码根。SwiftUI 的自由度最高——Apple 只给了一个空白工程,目录怎么建全靠团队自律。约束强的好处是“不容易出错”,坏处是“灵活性受限”;自由度高的好处是“灵活”,坏处是“依赖团队规范”。
14.9 从目录结构看框架的“跨端代价”
| 框架 | 跨端代价在目录结构中的体现 |
|---|---|
| Compose Multiplatform | commonMain 必须保持 import 干净,否则 iOS 链接阶段才报错 |
| React Native | 需要维护 android/ 和 ios/ 两套原生壳,JS 和原生边界需清晰 |
| Flutter | 平台壳基本可忽略,但需要处理平台通道和原生插件 |
深刻感知:跨端的代价,最终都会“显影”在目录结构上。CMP 的代价是“import 干净性”——一个 android.* import 就能让 iOS 构建失败;RN 的代价是“双壳维护”——Android 和 iOS 的原生配置需要分别处理;Flutter 的代价是“平台通道”——虽然平台壳可忽略,但调用原生能力需要额外桥接。没有免费的跨端,代价只是换了一种形式。
14.10 从目录结构看“框架的设计空间”
| 设计维度 | 可选范围 | 六框架的位置 |
|---|---|---|
| 源码组织 | 单目录 / 按职责 / 按 source set | Compose 按包,SwiftUI 按职责,CMP 按 source set |
| 资源配置 | 编译索引 / Asset Catalog / 声明式 | Android/ArkUI 编译索引,SwiftUI Asset Catalog,Flutter 声明式 |
| 平台处理 | 无 / 双壳 / source set / 壳可忽略 | 原生无,RN 双壳,CMP source set,Flutter 壳可忽略 |
| 语法风格 | 函数式 DSL / 装饰器 / Widget 树 | Compose 函数式,ArkUI 装饰器,Flutter Widget 树 |
深刻感知:目录结构是设计空间的“空间投影”。源码组织、资源配置、平台处理、语法风格——这四个维度的选择,共同决定了框架的目录结构。通过目录结构,你已经在体验框架的设计空间。
14.11 十个感知总览
| 感知 | 核心观点 |
|---|---|
| 感知一 | 目录结构是设计哲学的空间投影 |
| 感知二 | 源码目录的数量,暴露了框架的共享粒度 |
| 感知三 | 资源目录的差异,反映了框架对“资源”的定义 |
| 感知四 | 配置文件的命名,映射了框架的构建工具血统 |
| 感知五 | 平台目录的“存在感”,决定了开发者的关注重心 |
| 感知六 | 声明式语法的三种风格,对应三种“UI 描述观” |
| 感知七 | 入口文件的差异,暴露了框架的“启动模型” |
| 感知八 | 目录结构的“自由度”,反映了框架的约束强度 |
| 感知九 | 从目录结构看框架的“跨端代价” |
| 感知十 | 从目录结构看“框架的设计空间” |
这十个感知,全部来自本课的实际比较内容。后续每一课都会回到这些感知上,用新的比较内容验证它们、丰富它们、深化它们。
下期预告
【六大移动UI框架比较学与练】第3课:文本、图片、布局核心组件实战对照
更多推荐
所有评论(0)