第 3 章 Dart 语言基础
会任何一门编程语言即可快速上手 Dart。本节只讲写 Flutter 必须掌握的部分,代码都可粘贴到 DartPad 运行。
3.1 变量与空安全
void main() {
var name = '小明'; // 类型自动推断为 String
int age = 18; // 显式声明
final now = DateTime.now(); // final:运行期常量(运行时才确定)
const pi = 3.14159; // const:编译期常量(编译时就必须确定)
print('${now.year} 年,圆周率约 $pi');
// 可空类型:值可能不存在。这里用随机条件造出 null/非 null 两种情况
String? nickname = DateTime.now().second.isEven ? '小明' : null;
print(nickname?.length); // 安全调用:为 null 时整体返回 null,不崩溃
print(nickname ?? '无名'); // ??:左侧为 null 时取右侧
print('昵称长度:${nickname?.length ?? 0}'); // 两者组合最常用
// 只有 100% 确定非空时才用 !,判断错会直接抛异常(Null check operator)
if (nickname != null) {
print('首字:${nickname.substring(0, 1)}'); // 判空后自动提升为非空类型
}
print('$name 今年 $age 岁'); // 字符串插值,$变量 / ${表达式}
}
3.2 集合:List / Map / Set
void main() {
// List ≈ 原生数组
var fruits = ['apple', 'banana'];
fruits.add('cherry');
fruits.where((f) => f.startsWith('a')); // 过滤:[apple]
// Map ≈ 原生 Map
var user = {'name': '小明', 'age': 18};
print(user['name']); // 小明
// 展开(...)与集合 if —— Flutter 构建列表时天天用!
var extra = ['durian'];
var all = [...fruits, ...extra];
print(all); // [apple, banana, cherry, durian]
var withHeader = [
'header',
if (user['age']! as int >= 18) 'adult-banner', // 条件加入元素
for (var f in fruits) f.toUpperCase(), // 循环生成元素
];
print(withHeader);
}
3.3 函数:命名参数与箭头函数
// 命名参数(Flutter 组件参数全靠它):{} 内为可选命名参数,required 必填
String greet(String name, {String title = '同学', required bool formal}) {
return formal ? '$title $name 您好' : '嗨,$name';
}
void main() {
print(greet('小明', formal: true)); // 同学 小明 您好
print(greet('小明', title: '先生', formal: false)); // 嗨,小明
// 箭头函数 = 单表达式函数体
int square(int n) => n * n;
print('5 的平方:${square(5)}');
[1, 2, 3].map((n) => n * n).forEach(print);
}
3.4 类:构造语法糖
class User {
final String name;
final int age;
// this.name / this.age:参数直接赋给字段(Flutter 源码中最常见的写法)
User({required this.name, this.age = 0});
// 复制并修改部分字段 —— 状态管理中高频出现
User copyWith({String? name, int? age}) =>
User(name: name ?? this.name, age: age ?? this.age);
@override
String toString() => 'User($name, $age)';
}
void main() {
var u = User(name: '小明', age: 18);
print(u.copyWith(age: 19)); // User(小明, 19)
}
3.5 异步:Future / async / await
// 模拟网络请求:Future 表示「未来才有结果」
Future<String> fetchUserName() async {
await Future.delayed(const Duration(seconds: 1)); // 模拟耗时
return '小明';
}
void main() async {
print('开始请求...');
final name = await fetchUserName(); // await 等待结果,不阻塞 UI 线程
print('收到:$name');
// 错误处理
try {
await Future.delayed(const Duration(milliseconds: 100));
} catch (e) {
print('出错了: $e');
}
}
💡 Stream 一句话
Future 是「一个未来的值」,Stream 是「一系列持续到来的值」(如倒计时、传感器数据)。第 7.5 章 Riverpod 的 StreamProvider 就是消费它的。
3.6 判断与分支:if / 三元 / switch
String judge(int score) {
// 1. if / else if / else —— 与 Java/JS 完全一致,条件必须是 bool(没有「真值」隐式转换)
if (score >= 90) {
return '优秀';
} else if (score >= 60) {
return '及格';
} else {
return '不及格';
}
}
void main() {
print(judge(85)); // 及格
// 2. 三元表达式 —— Flutter 里给 Widget 按 conditions 换样式最常用
int age = 20;
String stage = age >= 18 ? '成年' : '未成年';
print(stage);
// 3. ??= :为 null 才赋值
String? nickname;
nickname ??= '无名氏';
print(nickname); // 无名氏
// 4. switch 语句(Dart 3 起无需 break,不会贯穿;default 兜底)
String status = 'WAITING';
switch (status) {
case 'PENDING_PAY':
print('待支付,15 分钟内完成');
case 'WAITING':
print('待医生接诊');
case 'IN_PROGRESS' || 'FINISHED': // 多值匹配用 ||
print('问诊相关状态');
default:
print('未知状态');
}
// 5. switch 表达式(Dart 3 新增)—— 可以直接「求值」,配合枚举极好用
var type = 2; // 1 找医生 / 2 极速问诊 / 3 开药门诊
String typeName = switch (type) {
1 => '找医生',
2 => '极速问诊',
3 => '开药门诊',
_ => '未知类型', // _ 相当于 default
};
print(typeName);
// 6. switch + 解构(模式匹配):把值「拆开」用
var point = (3, 5); // Record 记录类型
String area = switch (point) {
(0, 0) => '原点',
(var x, 0) => 'x 轴上,x=$x',
(0, var y) => 'y 轴上,y=$y',
(var x, var y) when x == y => '对角线上', // when 守卫条件
(var x, var y) => '普通点 ($x, $y)',
};
print(area); // 普通点 (3, 5)
}
💡 什么时候用哪种
两三个分支用
if;只是二选一取值用三元;枚举/固定值的多路分发优先用 switch 表达式——漏写分支时编译器会报错(对枚举 exhaustive check),比 if 更安全。
3.7 循环:for / for-in / while / continue / break
void main() {
// 1. 经典 for
for (var i = 0; i < 3; i++) {
print('第 $i 次循环');
}
// 2. for-in 遍历集合 —— 日常最多用这种
var doctors = ['李慧敏', '王振华', '张丽'];
for (var name in doctors) {
print('医生:$name');
}
// 3. 带下标遍历:asMap().entries 或简单用索引
for (var i = 0; i < doctors.length; i++) {
print('${i + 1}. ${doctors[i]}');
}
// 4. while / do-while
int stock = 3;
while (stock > 0) {
print('卖出 1 盒,剩 $stock');
stock--;
}
do {
print('至少执行一次,stock=$stock');
} while (false);
// 5. break / continue
for (var i = 0; i < 10; i++) {
if (i == 2) continue; // 跳过本次
if (i == 5) break; // 整个循环结束
print(i); // 0 1 3 4
}
// 6. 标签:跳出多层循环(嵌套循环时偶尔用到)
outer:
for (var x = 0; x < 3; x++) {
for (var y = 0; y < 3; y++) {
if (x * y == 2) break outer; // 直接跳出两层
print('($x, $y)');
}
}
// 7. 集合函数往往能替代手写循环(第 3.2 章的 where/map/reduce)
var nums = [1, 2, 3, 4, 5];
print(nums.where((n) => n.isEven).map((n) => n * n).toList()); // [4, 16]
}
💡 Flutter 里的循环就是列表渲染
实战中「循环」大多写成
children: [for (var d in doctors) DoctorTile(d)](3.2 章的集合 for)或 ListView.builder 的 itemBuilder,手写 for 打印的场景很少。
3.8 异常处理:throw / try-catch / on-finally
// 自定义业务异常 —— 项目里用 ApiException 承载 HTTP 状态码 + 提示文案
class ApiException implements Exception {
final int code;
final String message;
ApiException(this.code, this.message);
@override
String toString() => 'ApiException($code, $message)';
}
// 用 throw 抛出(任何对象都可以 throw,但规范上应抛 Exception/Error 子类)
void login(String password) {
if (password.length < 8) {
throw ApiException(401, '用户名或密码错误');
}
}
void main() {
// 1. catch 全部:拿到异常对象与堆栈
try {
login('123');
} catch (e, stackTrace) {
print('捕获:$e');
print('堆栈前 1 行:${stackTrace.toString().split('\n').first}');
}
// 2. on 精确捕获:只拦截某一类异常,还能「解构」出异常变量
try {
login('123');
} on ApiException catch (e) {
print('业务异常 [${e.code}]:${e.message}');
} on FormatException {
print('数据格式坏了');
}
// 3. finally:无论成败都执行(关 loading、关数据库连接)
bool loading = true;
try {
login('123');
} catch (e) {
print(e);
} finally {
loading = false;
print('loading=$loading'); // loading=false
}
// 4. 重新抛出:记完日志继续往上抛,交给更上层统一处理
try {
login('123');
} on ApiException catch (e) {
print('记录日志:$e');
rethrow; // 只能用在 catch 块里
}
// 5. assert:只在 debug 模式生效,release 打包自动移除(内部契约检查用)
int age = -1;
assert(age >= 0, '年龄不能为负'); // debug 下抛 AssertionError
print('done');
}
⚠️ 异常不是「错误码返回值」
Dart 的习惯是:非法入参用
throw ArgumentError、业务失败抛自定义异常,让调用方 try-catch;不要返回 null 或负数代表失败——空安全已经把 null 语义管起来了,混用反而更乱。第 8 章 dio 封装里就有完整的 on DioException 拦截示例。