前两篇我们走完了 React Native + Expo 的完整路径——从核心组件到嵌套导航再到持久化状态管理。React Native 的思路是"把 JavaScript 指令翻译成原生组件",就像一位翻译官站在 JS 和原生平台中间。Flutter 走了完全不同的路:它不借原生组件,而是自带画布,用自己的渲染引擎在画布上绘制每一个像素。这意味着 Flutter 在 Android 上画的按钮和在 iOS 上画的按钮,用的是同一套绘制代码,而非各自平台的原生控件。
这种"自带画布"的架构带来一个直接后果:Flutter 的编程语言不是 JavaScript,而是 Google 专为它打造的 Dart。所以本篇需要同时掌握两件事——一门新语言和一个全新的 UI 声明体系。别担心,你已有的 TypeScript 和 React 知识会让这个过渡比想象中轻松。
Flutter 最初叫"Sky",是 Google 在 2015 年的一个实验项目,目标只有一个:在移动设备上实现每秒 60 帧的流畅渲染。2017 年正式发布 1.0 后,Flutter 逐步扩展到 Web、桌面端和嵌入式设备。截至本文写作时,最新稳定版是 Flutter 3.47.2(2026 年 8 月 27 日发布),搭载 Dart 3.13.2。
Flutter 与 React Native 最根本的区别在渲染策略。React Native 把 JS 组件映射到 iOS 的 UIView 或 Android 的 View——两个平台上的"按钮"长得不一样,因为它们就是不同的原生控件。Flutter 不借用任何原生 UI 组件,它用自带的渲染引擎(Impeller)直接在 Skia/Impeller 画布上绘制一切:按钮的文字、圆角、阴影、手势区域,全都是画出来的。这就是为什么 Flutter 应用在 Android 和 iOS 上看起来像素级一致。
Flutter 的代码栈分为三层,每层职责清晰:
| 层级 | 语言 | 职责 |
|---|---|---|
| Framework | Dart | Widget 树、Material/Cupertino 组件、动画、手势、渲染层抽象 |
| Engine | C++ | Impeller 渲染引擎、Dart 运行时、文本排版、Platform Channel 通信 |
| Embedder | 平台语言 | 将 Flutter 内容嵌入原生窗口(Android Activity / iOS UIKit / Windows HWND) |
你在写 Flutter 应用时,90% 时间都在 Framework 层用 Dart 写 Widget。Engine 层你几乎不会直接接触,但它决定了你的应用性能——3.47 版本最大的变化是 Impeller 渲染引擎在桌面端(macOS/Windows/Linux)默认启用,之前它只在移动端是默认的。
Flutter 的渲染管线有三棵树,这是理解 Flutter 性能和调试的基础:
对比 React Native:React Native 的渲染管线是 JS 组件 → Bridge → 原生组件,中间的 Bridge 序列化/反序列化开销是旧版 RN 的性能瓶颈。Flutter 的三棵树全在同一个 Dart 进程内,没有跨语言通信开销。这也是为什么 Flutter 的动画性能通常优于 React Native。
确认 Flutter SDK 已安装后,执行以下命令创建项目:
flutter create 会生成一个计数器模板项目。在 macOS 上默认编译为 macOS 应用,用 flutter run -d chrome 可以切换到 Web,-d ios 切换到 iOS 模拟器。
你已经熟悉 TypeScript,Dart 的学习曲线会非常短。两者都是强类型、支持类型推断、有空安全机制的语言。但 Dart 有自己的设计取舍——比如它没有 union type,却有了更完善的面向对象体系。
四个关键区别于 TypeScript 的地方:final 只能赋值一次但运行时确定值,const 编译时就确定值——TS 没有这个区分。? 后缀表示可空,和 TS 的 string | null 类似但语法更简洁。late 表示"我保证用之前会赋值",跳过空安全检查但保留类型约束。
Dart 3.13 正式稳定了 Primary Constructors——这是从 Dart 3.12 实验特性转正的语法糖,让你一行代码定义带字段的类。先看传统写法:
用 Primary Constructors 后,声明字段和构造函数压缩到类头一行:
参数 final double x 直接在类头声明,自动生成对应字段和构造函数。这里还用了 extension 为类追加方法——Dart 的扩展方法语法和 Swift 很像,不需要像 TypeScript 那样用 declaration merging。
Dart 从 2.12 开始强制空安全(sound null safety),语法和 TS 类似但有差异:
| 概念 | TypeScript | Dart |
|---|---|---|
| 可空类型 | string | null |
String? |
| 空值断言 | value! |
value! |
| 空值合并 | a ?? b |
a ?? b |
| 可选链 | a?.b |
a?.b |
| 联合类型 | string | number |
不支持,用 sealed class |
sealed class Result 后,Dart 编译器会在 switch 中强制穷尽检查所有子类型,类似 Rust 的 enum。Dart 的异步模型和 JS 几乎一样:Future 对应 Promise,async/await 语法完全相同。Stream 则对应 RxJS 的 Observable——用于多次发射事件的场景。
注意 async* 后的星号——这是 Dart 的异步生成器语法,函数返回 Stream 而非 Future。yield 逐次发射值,消费者用 await for 或 .listen() 接收。在 Flutter 中,Stream 广泛用于定时器、传感器数据、WebSocket 等持续事件流的处理。
你已掌握 React 的组件思想——JSX 描述 UI,state 变化触发重渲染。Flutter 的 Widget 体系几乎是同一套思路,只不过用 Dart 替代了 JSX,用 Widget 类替代了 React.Component。
Flutter 的设计哲学是"一切皆 Widget"——对齐方式是 Widget(Center),边距是 Widget(Padding),手势检测是 Widget(GestureDetector),甚至主题色也是 Widget(Theme)。这和 React 中"一切皆组件"的理念完全一致,但 Flutter 走得更彻底——没有 CSS,没有单独的样式系统,所有视觉表现都通过组合 Widget 实现。
一个 Widget 的基本定义:
几个关键点对应到 React 概念:build() 方法等于 React 的 render() 或函数组件本身。required 关键字等于 TS 中必填 props。super.key 是 Flutter 的标识系统,用于 Element 树的 diff 算法——作用类似 React 的 key prop。$name 是 Dart 的字符串插值,等价于 TS 的 ${name}。
Flutter 的 Widget 分两类,选择哪一类取决于"UI 是否需要随时间变化":
| 对比项 | StatelessWidget | StatefulWidget |
|---|---|---|
| 对应 React | 函数组件(无 state) | useState / class 组件 |
| 数据来源 | 仅构造函数参数 | 构造函数 + 内部 State |
| 触发重建 | 父 Widget 传入新参数 | setState() 调用 |
| 适用场景 | 静态文本、纯展示组件 | 表单、动画、计数器、交互 |
flutter create 生成的默认模板就是一个计数器。拆解它的结构:
注意几个关键点:
_CounterAppState 是 Dart 的私有标记(文件级可见)
setState() 通知框架"状态变了,请重新 build"。和 React 的 setState 概念完全一致——不调用它,UI 不会更新
<Scaffold><Center><Text/></Center></Scaffold>,Dart 中用命名参数 child: 嵌套
const Text('计数器') 告诉编译器这个 Widget 永远不变,可以复用同一个实例。Flutter 会在 Element 树 diff 时跳过 const Widget 的对比,提升性能
{required this.name}。调用时必须写参数名:Greeting(name: '张三')。这比 JS 的位置参数更安全,重构时改参数顺序不会引入 bug。StatefulWidget 的 State 对象有一套生命周期回调,和 React 的 useEffect/useMount 类似但更细粒度:
initState 对应 React 的 useEffect(() => {}, [])(挂载时执行一次),dispose 对应 useEffect return 的清理函数(卸载时执行)。Flutter 还有 didChangeDependencies(依赖变化时)和 didUpdateWidget(父 Widget 更新时),但日常开发最常用的就是 initState 和 dispose 这对。
上一章的计数器代码中出现了 Scaffold、AppBar、FloatingActionButton——这些都属于 Material 组件库。Flutter 内置了两套完整的 UI 组件系统:Material Design(Google 风格)和 Cupertino(iOS 风格)。在 3.47 版本中,这两套库迎来了重大变化。
Material 组件库遵循 Google 的 Material Design 3 设计规范。使用 Material 组件需要先在根节点包裹 MaterialApp,它会注入主题、路由、本地化等全局配置。核心组件一览:
如果你需要让 Flutter 应用看起来像原生 iOS 应用,使用 Cupertino 组件库。需要在根节点包裹 CupertinoApp 而非 MaterialApp。关键差异:
| 功能 | Material | Cupertino |
|---|---|---|
| 页面骨架 | Scaffold | CupertinoPageScaffold |
| 顶栏 | AppBar | CupertinoNavigationBar |
| 按钮 | ElevatedButton | CupertinoButton |
| 列表 | ListView + ListTile | CupertinoListSection |
Flutter 3.47 将 Material 和 Cupertino 组件从核心 SDK 中解耦为独立的 pub 包——material_ui 和 cupertino_ui。3.47 版本中核心 SDK 仍包含这些库(向后兼容),但你可以选择 opt-in 使用独立包,获得更快的 bug 修复和组件更新——独立包按自己的周节奏发版,不必等 Flutter 核心版本。
大多数 Flutter 应用不需要"纯 Material"或"纯 Cupertino"——而是根据运行平台自适应。Flutter 提供了 Platform.isIOS 判断当前平台:
注意 import 'dart:io' 引入了 Platform 类——但 Web 端不支持 dart:io,如果你的 Flutter 项目同时编译 Web,需要用条件导入或 defaultTargetPlatform 替代。
Flutter 没有 CSS,所有布局都靠 Widget 组合实现。如果你熟悉 CSS Flexbox,Flutter 的布局体系会让你感到亲切——Row 就是 flex-direction:row,Column 就是 flex-direction:column。但 Flutter 把间距、对齐、弹性都拆成了独立的 Widget,而不是 CSS 属性。
Container 是 Flutter 中最常用的布局 Widget——它像 CSS 中的 div,可以同时设置 padding、margin、decoration(背景色/圆角/边框)、约束(宽高范围):
EdgeInsets 是 Flutter 替代 CSS margin/padding 值的工具:.all(16) 四方向均等,.symmetric(vertical: 8, horizontal: 12) 分纵横,.only(left: 4, top: 8) 指定方向。
Row 和 Column 是 Flutter 的 Flexbox 等价物。关键参数 mainAxisAlignment(主轴对齐)和 crossAxisAlignment(交叉轴对齐),对应 CSS 的 justify-content 和 align-items:
| CSS Flexbox | Flutter | 说明 |
|---|---|---|
justify-content: center |
MainAxisAlignment.center |
主轴居中 |
justify-content: space-between |
MainAxisAlignment.spaceBetween |
两端对齐 |
align-items: center |
CrossAxisAlignment.center |
交叉轴居中 |
flex: 1 |
Expanded(flex: 1) |
弹性占据剩余空间 |
Expanded 包裹的子 Widget 会占据主轴剩余空间——上面的例子中,"用户名"文本会填满图标和编辑按钮之间的所有空间。多个 Expanded 可以用 flex: 参数按比例分配空间。
CSS 的 position: relative/absolute 在 Flutter 中是 Stack + Positioned:
Stack 的第一个子元素是底层,后面的子元素依次叠在上面。Positioned 用 top/bottom/left/right 定位——和 CSS absolute 的偏移属性完全一致。Stack 适合做头像角标、图片标注、浮层引导等需要层叠的场景。
Expanded 包裹。setState() 就行,Flutter 的概念类似但调用方式不同。初学者常犯的错误是直接修改 _count 字段却忘记包裹 setState()。setState(() { ... }) 回调中。框架在回调结束后标记 Element 为 dirty,下一帧自动重建。await 之后使用 context 是 Flutter 最常见的运行时崩溃之一。Widget 可能在 await 期间已经被卸载,此时访问 context 会抛出 BuildContext is not mounted。if (!context.mounted) return; 守卫。这是 Dart 3 引入的标准模式,比旧版 mounted 检查更简洁。RenderFlex overflowed by N pixels。这是 Flutter 约束模型的直接体现——父 Widget 给了无限宽度,子 Widget 不知道自己该缩小还是滚动。Expanded 或 Flexible;对可能超出屏幕的内容用 SingleChildScrollView 包裹。build() 方法会在每一帧被调用,如果在里面发网络请求、写文件、甚至 print 调试,会导致严重的性能问题和不可预测的行为。initState();监听外部数据源放 didChangeDependencies();清理资源放 dispose()。build() 只负责返回 Widget 树,不做任何其他事。Record 类型表示用户信息(name, age, isActive),并用 Primary Constructor 定义一个 User 类sealed class 定义一个 Result 联合类型(Success/Failure),写一个 switch 表达式处理两种分支async/await 写一个函数,模拟 2 秒延迟后返回一个随机整数StatelessWidget 搭建静态名片布局(头像 + 姓名 + 职业标签)Stack + Positioned 在右下角添加在线状态圆点Row + Expanded 排列"关注 | 粉丝 | 文章"三列数据StatefulWidget 的"点赞"按钮,点击后数字 +1,用 setState() 刷新Platform.isIOS 或 defaultTargetPlatform 判断当前平台CupertinoPageScaffold + CupertinoNavigationBarScaffold + AppBar