【六大移动UI框架比较学与练】第2课:工程目录结构与基础语法差异对比

合集名称:移动开发六大声明式UI框架比较学习合集

合集简介:

聚焦Jetpack Compose、SwiftUI、ArkUI、Compose Multiplatform、React Native、Flutter六大声明式UI框架,以经典示例代码对照讲解,拆解语法与底层渲染原理,区分原生、跨端特性。采用学与练:研习框架原理,每课配套15道多元化习题,理论结合实操,系统掌握现代化移动UI开发体系。

六大框架分类图

跨端框架

Compose Multiplatform
Kotlin / Skia

React Native
JS/TS / 原生组件映射

Flutter
Dart / Skia/Impeller

原生框架

Jetpack Compose
Kotlin / Android

SwiftUI
Swift / Apple

ArkUI
ArkTS / HarmonyOS

全套33课大纲

第一阶段:环境入门与基础认知(1–6)

  1. 第1课:搭建环境,创建第一个应用
  2. 第2课:工程目录结构与基础语法差异对比
  3. 第3课:文本、图片、布局核心组件实战对照
  4. 第4课:样式修饰、尺寸边距与界面样式开发
  5. 第5课:声明式UI核心状态机制入门详解
  6. 第6课:各框架UI渲染更新基础原理初识

第二阶段:状态机制与渲染核心(7–12)

  1. 第7课:状态提升与单向数据流标准实现对照
  2. 第8课:状态装饰器作用域与响应机制深度对比
  3. 第9课:重组刷新底层机制与渲染差异解析
  4. 第10课:不可变数据设计思想与框架实践规范
  5. 第11课:懒加载列表组件性能对比与最优写法
  6. 第12课:UI无效刷新成因与状态性能避坑指南

第三阶段:页面交互与适配(13–17)

  1. 第13课:页面路由跳转、传参与栈管理对比
  2. 第14课:点击、输入、手势交互事件体系对照
  3. 第15课:弹窗、浮层、抽屉通用组件实现对比
  4. 第16课:主题切换与深色模式适配方案
  5. 第17课:多设备屏幕适配与响应式布局开发

第四阶段:通用工程核心能力(18–23)

  1. 第18课:异步并发编程模型与写法全面对照
  2. 第19课:网络请求封装、拦截与异常统一处理
  3. 第20课:本地键值存储与数据库方案对比选型
  4. 第21课:MVVM架构分层规范与落地实践
  5. 第22课:Repository数据仓库模式统一实现
  6. 第23课:系统权限适配与原生能力调用对照

第五阶段:性能优化与工程发布(24–28)

  1. 第24课:UI渲染性能瓶颈与框架优化方案
  2. 第25课:官方调试工具使用与性能排查实战
  3. 第26课:图片加载、缓存策略与内存优化
  4. 第27课:项目模块化与多环境工程配置
  5. 第28课:项目打包、签名与应用上架流程对比

第六阶段:横向总结与技术选型(29–33)

  1. 第29课:六大框架全方位优劣与特性横向对比
  2. 第30课:原生开发与跨端开发核心差异深度总结
  3. 第31课:不同业务场景框架选型最优策略
  4. 第32课:声明式UI五层渲染架构模型深度剖析
  5. 第33课:全系列技术总结与进阶学习路线规划

本文目录


一、本课导学

1.1 从“能跑”到“看懂”

第1课让六个框架都跑起来了。但一个可运行的工程,不等于一个结构清晰的工程。跑通之后,真正的困惑才开始:

  • 六个框架的目录结构为什么差异这么大?哪些是必须的,哪些可以删?
  • 源码、资源、配置文件分别放在哪里?各自控制什么?
  • 为什么 Flutter 有 lib/,React Native 有 android/ 和 ios/,Compose Multiplatform 有 commonMain/?
  • 同一个“Hello World”,六个框架的代码风格为什么完全不同?

这些困惑指向一个核心认知:工程目录结构是框架设计哲学的空间投影。理解了目录结构,就理解了框架如何看待“代码、资源、配置、平台”之间的关系。

1.2 本课目标

学完本课,你应能:

  1. 说出六大框架的标准工程目录结构及关键文件职责。
  2. 对比六大框架的入口文件、源码目录、资源目录、配置文件的位置差异。
  3. 理解 KMP source set、Flutter lib/、RN android/ + ios/ 等典型结构的含义。
  4. 区分声明式 UI 语法的三种风格:函数式 DSL、装饰器式、Widget 树式。
  5. 完成15道习题并理解每道题的解析。
  6. 从目录结构差异中,读出框架的设计哲学与平台策略。

1.3 适用边界

本课适用于:

  1. 已完成第1课环境搭建的开发者。
  2. 需要为团队制定工程规范的架构人员。
  3. 需要从 A 框架迁移到 B 框架的开发者。

本课不适用于:

  1. 尚未跑通任何一个框架的初学者。
  2. 需要深入状态管理与性能优化的读者。这些内容将在第7–12课展开。

1.4 术语约定

术语定义
Source SetKotlin Multiplatform 中按平台组织的源码集合,如 commonMain、androidMain
Widget 树Flutter 中由 Widget 嵌套形成的 UI 描述树
声明式 DSL用函数调用链描述 UI 结构的编程风格,如 Compose、SwiftUI、ArkUI
组件树React Native 中由 React 组件嵌套形成的 UI 描述结构
入口文件应用启动时最先执行的文件,通常包含应用根组件或启动逻辑
结构范式框架组织源码、资源、配置、平台代码的总体模式

1.5 知识地图

本课导学

工程结构的三个根本问题

六大框架工程结构总览

各框架逐一深度剖析

目录结构深度对比

核心认知

15道习题

知识点总结

比较后的十个深刻感知


二、工程结构的三个根本问题

每个框架的工程结构,本质上都在回答三个问题。这三个问题构成工程结构的“铁三角”,缺一不可:

工程结构的三个根本问题

问题一:代码放哪里?
源码、资源、测试如何组织

问题二:配置怎么写?
依赖、构建、签名、权限如何声明

问题三:平台怎么分?
跨端框架如何处理平台特定代码

决定因素:框架的模块化理念

决定因素:构建工具的设计哲学

决定因素:跨端策略与 source set 设计

这三个问题的重要性在于:

  • 问题一决定心智负担。源码目录越分散,开发者越需要理解“什么代码该放哪里”。
  • 问题二决定工程门槛。配置文件越复杂,越需要理解构建工具的设计理念。
  • 问题三决定跨端代价。平台代码的隔离方式,直接影响代码共享的边界与风险。

本课的核心方法:用这三个问题作为统一的“坐标系”,逐一分析六大框架的工程结构。这样对比才有结构性,而不是零散的目录罗列。


三、六大框架工程结构总览

3.1 六大框架工程结构速查表

框架源码目录资源目录配置文件平台特定目录
Jetpack Composeapp/src/main/java/app/src/main/res/build.gradle.kts、AndroidManifest.xml无(Android 单平台)
SwiftUIMyApp/MyApp/Assets.xcassets/MyApp.xcodeproj、Info.plist无(Apple 单平台)
ArkUIentry/src/main/ets/entry/src/main/resources/app.json5、module.json5、oh-package.json5无(HarmonyOS 单平台)
Compose Multiplatformshared/src/commonMain/kotlin/shared/src/commonMain/composeResources/build.gradle.kts、settings.gradle.ktsandroidMain/、iosMain/、desktopMain/
React Nativesrc/src/assets/package.json、metro.config.jsandroid/、ios/
Flutterlib/assets/pubspec.yamlandroid/、ios/、web/ 等

3.2 两种结构范式

从这张表可以一眼看出:原生框架(Compose、SwiftUI、ArkUI)的工程结构相对统一,跨端框架(CMP、RN、Flutter)的工程结构差异巨大。

跨端框架

原生框架

Jetpack Compose
单模块 app/

SwiftUI
单目录 MyApp/

ArkUI
模块 entry/

Compose Multiplatform
commonMain + 平台 source set

React Native
src/ + android/ + ios/

Flutter
lib/ + 平台壳

单平台结构

按共享粒度分

按 JS/原生边界分

按 Dart/平台边界分

为什么原生框架结构统一? 因为它们只服务一个平台,不存在“平台怎么分”的问题。为什么跨端框架差异巨大? 因为它们的跨端策略完全不同——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 启动 ComposeSwiftUI 的 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")
            }
        }
    }
}

四个关键特征:

  1. 一切 UI 都是 @Composable 函数。
  2. 布局通过嵌套函数调用来表达。
  3. 修饰符用 Modifier 链式调用。
  4. 没有 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()
    }
}

五个关键特征:

  1. View 协议 + body 属性描述界面。
  2. @State、@Binding、@Observable 管理状态。
  3. 修饰符通过 . 链式调用。
  4. 没有 XML,UI 即 Swift 代码。
  5. 目录按职责划分(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)
  }
}

五个关键特征:

  1. @Entry 标记页面入口,@Component 标记自定义组件。
  2. build() 方法返回 UI 结构。
  3. 状态用 @State、@Prop、@Link 等装饰器。
  4. 属性通过 . 链式调用。
  5. 目录按模块(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
androidMainActivity、Android Context 相关实现iOS/Desktop 专属 API
iosMainComposeUIViewController 工厂、iOS 平台实现android.*
desktopMainDesktop 窗口入口、Swing 互操作移动端专属 API

7.3 语法特征:与 Jetpack Compose 高度一致

// commonMain
@Composable
fun App() {
    MaterialTheme {
        Column(
            modifier = Modifier.fillMaxSize(),
            verticalArrangement = Arrangement.Center
        ) {
            Text("Hello, Compose Multiplatform")
        }
    }
}

四个关键特征:

  1. 共享 UI 写在 commonMain,语法与 Jetpack Compose 几乎相同。
  2. expect/actual 机制处理平台差异。
  3. 平台入口是“薄壳”,只负责调用共享 App()。
  4. 资源放在 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.jsonJS 依赖、脚本、React Native 版本
metro.config.jsMetro 打包器配置

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' },
});

五个关键特征:

  1. JSX 描述 UI,React Hooks 管理状态。
  2. View、Text 等组件映射到平台原生组件。
  3. 样式用 StyleSheet.create,类似 CSS 但非 CSS。
  4. android/ 和 ios/ 是原生壳,JS 代码在 src/。
  5. 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'),
            ),
          ],
        ),
      ),
    );
  }
}

五个关键特征:

  1. 一切皆 Widget,UI 由 Widget 树嵌套描述。
  2. 状态用 setState(简单场景)或状态库(复杂场景)。
  3. 样式通过 Widget 的命名参数传入。
  4. lib/ 是唯一的源码根目录。
  5. 平台壳(android/、ios/ 等)由 flutter create 生成,通常不手动修改。

9.4 结构范式:自绘渲染的底气

Flutter 的目录结构是 “单 lib/ + 多平台壳” 的极简结构。lib/ 是纯粹的 Dart 世界,平台壳是“被生成的”,开发者很少需要碰。

这种结构的底气来自 Flutter 的自绘渲染——既然 UI 不依赖平台控件,平台壳就只需要提供一个“画布”。这与 RN 的“双壳架构”形成鲜明对比:RN 的壳是“主动的”,Flutter 的壳是“被动的”。这个差异不是工程约定,而是渲染路线差异在目录结构上的直接投影。


十、六大框架目录结构深度对比

10.1 全维度对照表

对比项Jetpack ComposeSwiftUIArkUICompose MultiplatformReact NativeFlutter
源码根目录app/src/main/java/MyApp/entry/src/main/ets/shared/src/commonMain/kotlin/src/ + App.tsxlib/
资源目录res/Assets.xcassets/resources/composeResources/src/assets/assets/
依赖配置build.gradle.ktsXcode 工程设置oh-package.json5build.gradle.ktspackage.jsonpubspec.yaml
构建配置build.gradle.kts.xcodeprojbuild-profile.json5 + hvigorfile.tsbuild.gradle.kts + settings.gradle.ktsmetro.config.js + babel.config.jspubspec.yaml
入口文件MainActivity.ktMyApp.swiftEntryAbility.ets各平台薄壳index.js → App.tsxmain.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 结构差异的设计哲学对照

跨端框架结构

原生框架结构

Jetpack Compose
标准 Android 模块

SwiftUI
职责分层

ArkUI
模块 + Ability

Compose Multiplatform
source set 分层

React Native
双平台壳 + JS

Flutter
单 lib + 平台壳

平台内组织

按共享粒度组织

按 JS/原生边界组织

按 Dart/平台边界组织

10.3 三个根本问题的回答对比

回到第二章提出的三个根本问题,六个框架的回答如下:

框架代码放哪里?配置怎么写?平台怎么分?
Jetpack ComposeAndroid 模块内按包组织Gradle DSL不适用(单平台)
SwiftUI按职责分层,自由度高Xcode 工程设置不适用(单平台)
ArkUI模块 + Ability,强约定oh-package.json5 + hvigor不适用(单平台)
Compose Multiplatform按 source set 分层Gradle + KMP 插件commonMain + 平台 source set
React Nativesrc/ + 双原生壳package.json + Metroandroid/ + ios/ 双目录
Flutter单 lib/ 目录pubspec.yamllib/ + 平台壳

这张表就是本课的核心结论:六个框架对三个根本问题的回答,决定了它们的工程结构范式。理解了这个对应关系,就理解了“为什么目录长这样”。


十一、本课核心认知

11.1 核心认知

  1. 工程目录结构是框架设计哲学的空间投影。
  2. 原生框架结构相对统一,跨端框架结构差异巨大。
  3. Compose Multiplatform 的 source set 是“共享优先”的体现。
  4. React Native 的 android/ + ios/ 是“JS 驱动原生”的体现。
  5. Flutter 的 lib/ + 平台壳是“自绘渲染”的体现。
  6. 声明式语法有三大风格:函数式 DSL(Compose、SwiftUI)、装饰器式(ArkUI)、Widget 树式(Flutter、React Native)。

11.2 限制条件

  1. 本课只覆盖标准工程结构,不涉及模块化拆分与多模块工程。
  2. 不同框架版本可能调整默认结构,请以官方模板为准。
  3. 本课不展开状态管理、路由、网络等具体实现。
  4. 目录结构的最佳实践因团队规模、项目复杂度而异。

十二、习题

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 ComposeSwiftUIArkUICompose MultiplatformReact NativeFlutter
源码目录app/src/main/java/MyApp/entry/src/main/ets/shared/src/commonMain/kotlin/src/ + App.tsxlib/
资源目录res/Assets.xcassets/resources/composeResources/src/assets/assets/
配置文件build.gradle.ktsXcode 工程oh-package.json5build.gradle.ktspackage.jsonpubspec.yaml
平台特定目录无无无androidMain/ 等android/、ios/android/、ios/ 等
结构范式Android 模块 + Compose 包职责分层模块 + AbilitySource set 分层双平台壳 + JS 核心单 lib/ + 平台壳

解读:原生框架的结构范式是“平台内组织”,跨端框架的结构范式是“按共享粒度或 JS/原生边界组织”。理解结构范式,比记住具体目录更重要——因为目录会随版本变化,范式相对稳定。


十三、知识点总结

13.1 工程目录结构知识点

  1. Jetpack Compose:标准 Android 模块结构,app/src/main/java/ 放源码,res/ 放资源,build.gradle.kts 配置构建。
  2. SwiftUI:按职责分层,MyApp.swift 是入口,Views/、Models/、ViewModels/ 组织代码,Assets.xcassets/ 放资源。
  3. ArkUI:AppScope/ 不可改名,entry/src/main/ets/ 放 ArkTS 源码,resources/ 放资源,oh-package.json5 管理依赖。
  4. Compose Multiplatform:commonMain 放共享 UI,androidMain/iosMain/desktopMain 放平台代码,composeResources/ 放资源。
  5. React Native:src/ 放 JS/TS 源码,android/ 和 ios/ 是原生壳,package.json 管理依赖,Metro 打包。
  6. Flutter:lib/ 放 Dart 源码,assets/ 放资源,pubspec.yaml 管理依赖,平台壳由 flutter create 生成。

13.2 语法风格知识点

声明式语法的三种风格

函数式 DSL
Jetpack Compose / SwiftUI / CMP

装饰器式
ArkUI

Widget 树式
Flutter / React Native

UI 是函数调用链
修饰符是函数参数

UI 是组件结构
装饰器标记入口与状态

UI 是树嵌套
属性是节点参数

  1. 函数式 DSL:Jetpack Compose、SwiftUI、Compose Multiplatform。UI 是函数调用链或结构体组合,修饰符用 . 链式调用。
  2. 装饰器式:ArkUI。@Entry、@Component、@State 装饰器 + build() 方法。
  3. Widget 树式:Flutter。一切皆 Widget,嵌套 Widget 描述 UI,命名参数传样式。
  4. JSX + Hooks:React Native。JSX 描述 UI,React Hooks 管理状态。

13.3 设计哲学知识点

  1. 原生框架结构统一,跨端框架结构差异巨大。
  2. Compose Multiplatform 的 source set 是“共享优先”的体现。
  3. React Native 的 android/ + ios/ 是“JS 驱动原生”的体现。
  4. Flutter 的 lib/ + 平台壳是“自绘渲染”的体现。
  5. ArkUI 的 AppScope/ 不可改名,体现鸿蒙生态的强约定。

13.4 学习路线知识点

  1. 先理解“源码、资源、配置、平台”四个维度的组织方式。
  2. 再理解每个框架的“结构范式”。
  3. 最后理解结构范式与设计哲学的对应关系。

十四、本课比较后的十个深刻感知

本课完成了六大框架工程目录结构与基础语法的横向对比。在这个具体比较过程中,浮现出十个值得深思的感知。这些感知全部来自本课的实际比较内容,后续每一课都会回到这些感知上,验证、丰富、深化它们。

14.1 目录结构是设计哲学的空间投影

框架目录结构特征设计哲学
Jetpack Compose标准 Android 模块 + Compose 包渐进增强,不另起炉灶
SwiftUI职责分层,自由度最高信任开发者自律
ArkUI模块 + Ability,AppScope/ 不可改名强约定保证多设备兼容
Compose Multiplatformsource set 分层共享优先,平台为例外
React Native双平台壳 + JS 核心不消灭原生,JS 作为上层
Flutter单 lib/ + 平台壳自绘渲染,平台壳只是画布

深刻感知:你看到的目录树,就是框架设计者眼中的世界地图。Compose 把 UI 挂在 Android 模块里,说明它把自己定位为“Android 生态的一部分”;Flutter 把 Dart 代码全放 lib/,说明它认为“UI 应该独立于平台”;RN 保留 android/ 和 ios/,说明它承认“原生工程是不可或缺的基础”。

14.2 源码目录的数量,暴露了共享粒度

框架源码根目录数量共享粒度
Jetpack Compose1 个单平台,无需共享
SwiftUI1 个单平台,无需共享
ArkUI1 个单平台,无需共享
Compose Multiplatform4 个以上按 source set 共享
React Native3 个按 JS/原生边界共享
Flutter1 个核心 + 多个平台壳按 Dart/平台边界共享

深刻感知:源码目录的数量,就是框架“共享粒度”的度量。Compose Multiplatform 有最多的源码目录,因为它需要精细区分“哪些代码能共享、哪些不能”;Flutter 只有一个 lib/,因为它把所有 UI 代码都视为可共享;RN 有三个源码目录,因为它需要区分 JS 代码和两套原生壳。

14.3 资源目录的差异,反映了对“资源”的定义

框架资源目录资源类型特点
Jetpack Composeres/图片、字符串、颜色、样式传统 Android 资源体系
SwiftUIAssets.xcassets/图片、颜色、图标Asset Catalog 体系
ArkUIresources/图片、多媒体、字符串、布局按 base/rawfile 分层
Compose MultiplatformcomposeResources/图片、字符串、字体跨平台共享资源
React Nativesrc/assets/图片、字体通过 JS import 引用
Flutterassets/图片、字体、JSON在 pubspec.yaml 声明

深刻感知:资源目录的差异,暴露了框架对“资源”的不同理解。Android 和鸿蒙把资源视为“需要编译和索引的体系”;SwiftUI 把资源视为“Asset Catalog 中的资产”;Flutter 把资源视为“需要声明的静态文件”;RN 把资源视为“可以被 JS 引用的模块”。资源的组织方式,直接影响资源引用的心智模型。

14.4 配置文件的命名,映射了构建工具血统

框架配置文件血统
Jetpack Composebuild.gradle.ktsGradle / JVM 生态
SwiftUI.xcodeproj + Info.plistXcode / LLVM 生态
ArkUIoh-package.json5 + hvigorfile.tsNode.js / hvigor 生态
Compose Multiplatformbuild.gradle.kts + settings.gradle.ktsGradle / KMP 生态
React Nativepackage.json + metro.config.jsnpm / Metro 生态
Flutterpubspec.yamlDart / Pub 生态

深刻感知:配置文件的名字,就是框架构建工具血统的身份证。.gradle.kts 属于 JVM 世界,package.json 属于 npm 世界,pubspec.yaml 属于 Dart 世界,oh-package.json5 是鸿蒙对 npm 的对标。理解配置文件的语言和约定,就理解了框架的构建哲学。

14.5 平台目录的“存在感”,决定开发者的关注重心

框架平台目录开发者是否需要关注
Jetpack Compose无不适用
SwiftUI无不适用
ArkUI无不适用
Compose MultiplatformandroidMain/ 等需要关注,但默认不碰
React Nativeandroid/、ios/需要理解,可能修改
Flutterandroid/、ios/ 等基本可以忽略

深刻感知:平台目录的“存在感”,就是框架对开发者的“关注引导”。RN 的 android/ 和 ios/ 是“主动的”——开发者需要理解它们来配置原生依赖、修改原生代码;Flutter 的平台壳是“被动的”——flutter create 生成后,开发者很少需要碰;Compose Multiplatform 的平台 source set 是“按需的”——只有需要平台特定实现时才碰。

14.6 声明式语法的三种风格,对应三种“UI 描述观”

风格框架UI 描述观
函数式 DSLCompose、SwiftUI、CMPUI 是函数的组合,修饰符是函数的参数
装饰器式ArkUIUI 是组件的结构,装饰器标记入口和状态
Widget 树式Flutter、RNUI 是树的嵌套,属性是节点的参数

深刻感知:语法风格反映了框架的“UI 描述观”。Compose 和 SwiftUI 认为“UI 是函数调用链”,ArkUI 认为“UI 是组件结构 + 装饰器”,Flutter 和 RN 认为“UI 是树”。这三种描述观没有优劣,但会影响开发者写代码时的“思维形状”——函数式 DSL 让开发者思考“组合”,Widget 树让开发者思考“嵌套”。

14.7 入口文件的差异,暴露了“启动模型”

框架入口文件启动模型
Jetpack ComposeMainActivity.ktActivity 生命周期启动
SwiftUIMyApp.swiftApp 场景启动
ArkUIEntryAbility.etsAbility 启动
Compose Multiplatform各平台薄壳平台入口调用共享 App()
React Nativeindex.js → App.tsxJS 注册组件,原生壳加载
Fluttermain.dartmain() → 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 MultiplatformcommonMain 必须保持 import 干净,否则 iOS 链接阶段才报错
React Native需要维护 android/ 和 ios/ 两套原生壳,JS 和原生边界需清晰
Flutter平台壳基本可忽略,但需要处理平台通道和原生插件

深刻感知:跨端的代价,最终都会“显影”在目录结构上。CMP 的代价是“import 干净性”——一个 android.* import 就能让 iOS 构建失败;RN 的代价是“双壳维护”——Android 和 iOS 的原生配置需要分别处理;Flutter 的代价是“平台通道”——虽然平台壳可忽略,但调用原生能力需要额外桥接。没有免费的跨端,代价只是换了一种形式。

14.10 从目录结构看“框架的设计空间”

设计维度可选范围六框架的位置
源码组织单目录 / 按职责 / 按 source setCompose 按包,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课:文本、图片、布局核心组件实战对照


Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐