本节摘要:本节做两件事:切出写 Flutter 业务必需的 Dart 语言切片(空安全、final 与 const、类与工厂构造、Future 与 async-await),然后把工具链真正架起来——flutter doctor 体检、创建「轻记账」工程、跑通首次热重载。读完本章即可开工,后面所有章节的代码都以本节的工程为底座。
Dart 是一门"为界面而生的语言":它把 Java 式的类系统、JavaScript 式的异步、函数式语言的高阶函数揉在一起,而且从语言层面内置了空安全。对从其他语言转来的开发者,最省力的学法不是通读语言规范,而是按"Flutter 代码里高频出现的形态"反推语法。下面每一段都可以直接贴进 Dart Pad 或本地工程运行。
变量、类型与空安全。 Dart 类型可写可不写(编译器推断),但空安全不可绕过:类型默认不可为空,可空的要显式声明,并在使用前处理空值。
String name = '轻记账'; // 不可为空,永远有值 String? remark; // 可空类型,可能是 null int len = name.length; // 直接取,安全 int rLen = remark?.length ?? 0; // 可空链式调用,空则兜底 // remark!.length; // 非空断言:明知不为空才用,崩了自己负责
?. 与 ?? 这对符号在业务代码里出现的频率极高:前者"是 null 就短路",后者"是 null 就给默认值"。模型层与 UI 层之间传递的可选数据(比如备注、头像地址)全靠它们兜住。
final 与 const。 两者都表示"不可再赋值",区别在初始化时机:final 是运行时常量,const 是编译期常量。这个区别在 Flutter 里不只是语法洁癖——const 构造的 Widget 在框架内部会被复用,不随父级重建,是性能优化的一等公民(第 6 章会量化这件事)。
final now = DateTime.now(); // 运行时才知道,只能 final const pi = 3.14159; // 编译期已知,可以 const // const btn = Text('记一笔'); // 常量 Widget,父级重建时它原地不动
函数与命名参数。 Dart 函数是一等公民,Flutter 的界面代码几乎全部由"返回 Widget 的函数"和带命名参数的构造函数组成。
// 命名参数用大括号声明,调用处必须带参数名,可标 required double amountAfter({required double price, double discount = 1.0}) { return price * discount; } // 箭头函数:单表达式函数体,Widget 构造里随处可见 String label(double v) => '¥${v.toStringAsFixed(2)}'; void main() { print(amountAfter(price: 12.5, discount: 0.8)); // 10.0 print(label(10.0)); // ¥10.00 }
类与构造。 Flutter 的界面就是类的嵌套,Dart 的构造语法值得专门练一次。轻记账的支出模型后几章会反复用到,这里给出完整定义:
class Expense { final String title; final double amount; final String category; final DateTime createdAt; // 常量构造:字段全 final 时可用,配合 const 提升性能 const Expense({ required this.title, required this.amount, required this.category, required this.createdAt, }); // 命名构造 + 集合操作:从 Map 还原对象,序列化场景的标准写法 factory Expense.fromMap(Map<String, Object?> map) { return Expense( title: map['title']! as String, amount: (map['amount']! as num).toDouble(), category: map['category']! as String, createdAt: DateTime.parse(map['createdAt']! as String), ); } Map<String, Object?> toMap() => { 'title': title, 'amount': amount, 'category': category, 'createdAt': createdAt.toIso8601String(), }; @override String toString() => 'Expense($title, $amount, $category)'; }
异步:Future 与 async-await。 网络、磁盘、平台通道全是异步的,这段语法是第 4、5 章的地基。核心心法一句:async 函数里 await 之后的世界,都在 then 之后——写了 await,后面几行就自动等在结果到达之后执行,不需要回调嵌套。
Future<String> fetchCategory(double amount) async { await Future<void>.delayed(const Duration(milliseconds: 200)); // 模拟网络 return amount >= 1000 ? '大额支出' : '日常'; } Future<void> main() async { final tag = await fetchCategory(1500); print('归入:$tag'); // 200 毫秒后才执行 }
模式匹配与记录类型(Dart 3)。 新版 Dart 支持解构与 switch 表达式,统计页里按分类归组这类代码会明显变短:
String iconOf(String category) => switch (category) { '餐饮' => '🍜', '交通' => '🚌', '购物' => '🛍️', _ => '📦', // 兜底分支必须存在,编译器会检查穷举 };
这五个切片覆盖了本书代码里九成以上的 Dart 语法。遇到看不懂的写法,先怀疑是语法糖,查证的成本通常低于猜测。
Dart 切片在手,接下来把工位架起来。Flutter 的工具链设计成一个 CLI 为核心的闭环,flutter 命令行工具覆盖体检、建工程、跑应用、打包全流程。
体检:flutter doctor。 安装 SDK 并配置好环境变量后,先跑诊断。它会逐项检查 Flutter SDK、Android 工具链、Xcode(macOS 上)、Chrome、IDE 插件,并明确告诉你缺什么。第一次跑几乎必有红叉,按提示逐项补齐即可;只打算先做 Android 的,iOS 那几项可以放着不动。
$ flutter doctor -v [✓] Flutter (Channel stable, 3.x, on Microsoft Windows 11) [✓] Windows Version [✗] Android toolchain - develop for Android devices ✗ Unable to locate Android SDK. ← 按提示安装 Android Studio 或设置 sdk 路径 [!] Chrome - develop for the web ← 不做 Web 开发可忽略
建工程:flutter create。 一条命令生成可运行的完整工程,包含各平台的宿主壳(android、ios、web 等目录)、Dart 代码目录与依赖清单。轻记账工程建议在创建时指定组织名,Android 包名会用到:
$ flutter create --org com.example light_ledger $ cd light_ledger $ flutter run # 默认跑在第一个连接的设备上
工程里与日常开发直接相关的是三处:代码都写在 lib 目录,入口是其中的 main 函数所在文件;依赖声明在工程根的 pubspec 文件里(第 5 章详讲);android、ios 等平台目录是宿主壳,多数日子不用碰,第 7 章平台通道时才会打开。
首次热重载。 把默认计数器换成轻记账的占位首页,跑起来后体会一次热重载:
import 'package:flutter/material.dart'; void main() => runApp(const LightLedgerApp()); class LightLedgerApp extends StatelessWidget { const LightLedgerApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: '轻记账', theme: ThemeData(colorSchemeSeed: Colors.teal, useMaterial3: true), home: const HomePage(), ); } } class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('轻记账')), body: const Center(child: Text('今天还没有记账', style: TextStyle(fontSize: 18))), floatingActionButton: FloatingActionButton( onPressed: () {}, // 第 4 章接入状态后在这里记账 child: const Icon(Icons.add), ), ); } }
在运行中的终端按 r,终端输出热重载完成,模拟器里的文案与主题色立刻刷新。如果此刻顺手按了 R(热重启),会发现应用整个回到初始状态——这正是上一节"JIT 注入"与"全量重建"的体感差别。把同一个工程用 flutter run -d chrome 再跑一遍,浏览器里出现同一个界面:落地机制说的"换宿主不换界面",第一次亲手验证。
?. 与 ??、final 与 const 的时机差、命名参数、类与工厂构造、async-await;factory Expense.fromMap 是全书模型层的标准写法,序列化场景反复出现;勘察到此完毕:选型有依据、原理有地图、工具已就位。下一章拆开引擎看骨架——那份 Dart 代码进入运行时之后,是哪些部件接力把它画上屏幕的。