我花了三个周末重复构建同一个应用。不是出于实验——我面临着一个真正的决定。我们在工作中的一个内部工具中添加一个移动伴侣,这是一个基于Spring Boot微服务运行的任务管理系统。我的团队之前接触过Dart;而我更习惯JavaScript。我们两个在过去两年里都没有发布过严肃的移动应用。选择技术栈的唯一诚实方式是在两个框架中构建一些真实的东西,并比较我所发现的情况。
我开发的这个应用不是玩具。它从REST API获取任务,使用SQLite本地缓存,在截止日期临近时触发计划推送通知,并在具有三个标签的底部导航布局上实现平滑的列表动画。简单到可以在周末完成。复杂到足以揭示这两个框架之间的真实差异。
这是我发现的内容——包括一个令我惊讶的结果。
应用程序
比较之前,我先具体说明我构建的内容。两个版本:
- 从分页的REST端点获取任务并带认证头
- 使用SQLite本地缓存响应
- 在标记任务完成时支持乐观更新
- 在每次任务截止时间前一小时安排本地推送通知
- 使用带三个标签的底部导航栏
这不是生产代码——这是一个受控的比较。两款应用都访问运行在我MacBook上的同一个模拟API。相同的UI设计,相同的功能集,不同的框架.
设置与开发者体验
Flutter的设置前期更复杂。你需要运行flutter doctor,追赶 Android SDK 版本,配置 Xcode 命令行工具。在一个干净的 Mac 上,我花了大约 45 分钟在两个模拟器上得到一个绿色的构建。错误消息通常足够具体来指导你,但它是一项机械性的工作.
使用 React Native 和 Expo 只花了 15 分钟。npx create-expo-app,选择一个模板,运行npx expo start,扫描二维码。完成。摩擦差是真实的,特别是如果你正在为新接触移动开发的团队进行入职培训.
难点在于:Expo的托管工作流程在需要Expo SDK中不包含的原生模块时工作得非常出色。然后你退出,你大致回到了与裸React Native设置相同的复杂程度。对于这个测试应用,我保持在托管Expo中并且没有遇到任何障碍.
编写UI代码
这是框架在日常使用中最能体现差异的地方。
Flutter 使用组件——所有东西都是组件,可组合且明确。这是任务列表项:
class TaskTile extends StatelessWidget {
final Task task;
final VoidCallback onComplete;
const TaskTile({super.key, required this.task, required this.onComplete});
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(
task.title,
style: task.completed
? const TextStyle(decoration: TextDecoration.lineThrough)
: null,
),
subtitle: Text(task.dueAt.toLocal().toString().substring(0, 16)),
trailing: task.completed
? const Icon(Icons.check_circle, color: Colors.green)
: IconButton(
icon: const Icon(Icons.radio_button_unchecked),
onPressed: onComplete,
),
);
}
}
React Native with TypeScript:
type TaskTileProps = {
task: Task;
onComplete: () => void;
};
export function TaskTile({ task, onComplete }: TaskTileProps) {
return (
<TouchableOpacity
style={styles.row}
onPress={task.completed ? undefined : onComplete}
>
<View style={styles.info}>
<Text style={[styles.title, task.completed && styles.completed]}>
{task.title}
</Text>
<Text style={styles.due}>{formatDate(task.dueAt)}</Text>
</View>
{task.completed ? (
<Ionicons name="checkmark-circle" size={22} color="#4CAF50" />
) : (
<Ionicons name="radio-button-off" size={22} color="#999" />
)}
</TouchableOpacity>
);
}
两者都易于阅读。Flutter版本更冗长但更有结构——你始终处于一个显式的、类型化的组件树中。React Native版本如果你为Web编写React,会感觉立刻熟悉,这取决于你团队的背景,是优势还是劣势。
我注意到一件事:在一天的完整开发过程中,Flutter的热重载比React Native更快、更可靠。不是戏剧性的——而是持续稳定的。
状态管理
我在Flutter中使用了Riverpod,在React Native中使用了Zustand。两者都很轻量级且明确。我故意避免了Provider或Redux,因为这个项目规模不大。
使用Riverpod的Flutter:
final tasksProvider = AsyncNotifierProvider<TasksNotifier, List<Task>>(
TasksNotifier.new,
);
class TasksNotifier extends AsyncNotifier<List<Task>> {
@override
Future<List<Task>> build() => _fetchTasks();
Future<void> completeTask(String id) async {
final previous = state;
state = AsyncData(
state.value!
.map((t) => t.id == id ? t.copyWith(completed: true) : t)
.toList(),
);
try {
await TasksApi.complete(id);
} catch (_) {
state = previous;
}
}
}
使用Zustand的React Native:
interface TaskStore {
tasks: Task[];
loading: boolean;
fetchTasks: () => Promise<void>;
completeTask: (id: string) => Promise<void>;
}
export const useTaskStore = create<TaskStore>((set, get) => ({
tasks: [],
loading: false,
fetchTasks: async () => {
set({ loading: true });
const tasks = await TasksApi.fetchAll();
set({ tasks, loading: false });
},
completeTask: async (id) => {
const previous = get().tasks;
set({
tasks: previous.map((t) => (t.id === id ? { ...t, completed: true } : t)),
});
try {
await TasksApi.complete(id);
} catch {
set({ tasks: previous });
}
},
}));
这些模式几乎一样——乐观更新,失败时回滚。操作体验差不多。如果你已经了解其中一个库,下午就能熟悉另一个。
性能
这就是我个人的叙事转折点。
Flutter 通过自己的引擎渲染。到 2026 年,Impeller 将成为 iOS 和 Android 的默认选项。它不使用原生 UI 组件 — 它自己绘制所有内容。这意味着跨平台完美的帧率一致性和不依赖于 JavaScript 线程的动画。
React Native 的新架构 — JSI 加 Fabric,自 RN 0.74 起默认启用,消除了旧的异步桥接。线程通信现在变为同步。这于 2024-2025 年关闭了显著的性能差距。
实际上,对于这个应用,我在现代设备上无法可靠地区分差异。在列表滚动时,两者都达到了60fps。差距出现在你用力推时——复杂的自定义动画、繁重的计算、非常长的列表。Flutter在那里仍然获胜,但你必须构建一些有要求的东西才会关心。
构建大小:Flutter发布APK是21MB。React Native裸是18MB。Flutter带着它的引擎到处走。
生态系统和第三方库
React Native受益于整个JavaScript生态系统。需要日期选择器、图表库、PDF生成器?npm上都有现成的包。问题在于它是否有真正的原生绑定,还是纯JavaScript的回退方案。
Flutter 的 pub.dev 更小但更经过精心挑选。包通常质量更高且维护得更好,因为社区更加专注。对于常见需求——HTTP 客户端、SQLite、推送通知、状态管理——Flutter 生态系统很稳固。
Flutter 仍然存在不足之处:一些 SDK 会先发布它们的 React Native 版本,然后才发布 Flutter 版本,有时会延迟数月。分析工具、一些支付网关、特定的第三方集成。如果你的应用依赖其中之一,请在确定之前检查 Flutter SDK 的可用性。
对于推送通知来说,两者flutter_local_notifications 和 Expo 通知在我的测试中运行顺畅。API质量没有明显差异。Flutter的设置需要直接触摸原生配置文件;Expo将这一点抽象化了.
我发布的
我发布了Flutter版本。这个决定是基于我特定情况下的因素.
界面一致性 — 我们的 App 拥有自定义列表动画和一种需要在 iOS 和 Android 上看起来完全一致的设计语言。Flutter 的渲染器保证了这一点,不会出现平台差异。
现有的 Dart 暴露 — 我的团队在 2023 年短暂使用过 Flutter。与切换到每个人都需要从零开始学习的移动特定 React/TypeScript 设置相比,启动成本较低。
没有异国情调的 SDK 要求 — 我们不依赖任何第三方SDK,这样可以避免我们面临“即将推出Flutter SDK”的问题。
桌面端已在路线图中 — Flutter在移动端、桌面端和Web端的单一代码库对我们很重要。我们已经运行着Spring Boot后端;为内部运营添加Flutter桌面客户端是直截了当的。
如果我们的团队使用 JavaScript 较多,或者如果我们需要与 Web 前端共享组件,那么 React Native 将是正确的选择.
真实总结
当 Flutter 更有意义时:
- 像素级自定义 UI — 你完全控制渲染器
- 性能密集型动画 — 没有 JS 线程瓶颈
- 超越移动端的定位 — 2026年Flutter桌面和网页版是真实的选择
- 团队愿意学习Dart — 投资看起来比实际小
当以下情况时React Native更合适:
- 以JS为主的团队 — 如果你的团队已经熟悉React,则无需磨合
- 与网页共享逻辑 — React Native Web 和共享业务逻辑已经成熟
- Expo 加速 MVP 开发 — 托管的 Expo 确实是开发可运行应用的最快路径
- 广泛的 SDK 可用性 — 一些第三方 SDK 仍然优先考虑 React Native 绑定
2026年,这两个框架都不是错误。2019-2022年的"Flutter与React Native"之争产生了许多时效性不佳的激烈观点。两者都能发布真实的生产应用。两者都有活跃的社区。两者已经足够趋同,以至于你的团队背景比框架的原始能力更重要。
我唯一要反对的一点是:不要仅仅因为团队里每个人都熟悉 JavaScript 就选择 React Native。移动开发已经有很多平台特有的怪癖——权限流程、后台任务、通知权限、密钥串存储——因此框架的选择不如理解 iOS 和 Android 实际工作原理来得重要。无论哪种方式,这种学习都是必需的。
2026年你在移动生产中运行的是什么?如果你在某个时候切换了框架——从Flutter切换到React Native或者反过来——是什么最终促使你下定决心?
最初发表于Medium。


























