Branches(分支)
分支让程序根据条件选择不同的执行路径。Dart 主要使用 if 处理布尔条件,使用 if-case 同时检查并解构数据,使用 switch 在多个候选分支中进行选择。
学习分支时,需要重点掌握以下问题:
- 条件是否必须是
bool? - 什么时候使用
if,什么时候使用switch? switch语句和switch表达式有什么区别?- 模式、守卫和穷尽性如何影响分支选择?
使用 if 和 else 判断条件
if 在条件为 true 时执行对应代码块。条件为 false 时,可以交给 else if 检查下一个条件,最后由 else 处理其余情况:
String describeScore(int score) {
if (score >= 90) {
return 'excellent';
} else if (score >= 60) {
return 'passed';
} else {
return 'failed';
}
}
void main() {
print(describeScore(82)); // passed
}
条件按照书写顺序从上到下检查,命中一个分支后不会继续检查后面的分支。因此,范围更小或要求更严格的条件通常应该放在前面。
Dart 不会把数字、字符串或对象自动转换为布尔值,条件必须是 bool:
void main() {
const items = ['variables', 'types'];
// if (items.length) {} // 编译错误:int 不是 bool
if (items.isNotEmpty) {
print('There are ${items.length} items.');
}
}
即使分支中只有一条语句,也建议保留花括号。这样后续添加语句时,不容易因为缩进看似属于分支、实际却在分支外执行而引入错误。
利用分支获得类型提升
分支条件可以帮助分析器确认变量的更具体类型。下面的 value 原本是 Object?,排除 null 并确认它是 String 后,真分支中的类型会被提升为 String:
String normalize(Object? value) {
if (value is String && value.isNotEmpty) {
return value.trim();
}
return 'unknown';
}
void main() {
print(normalize(' Dart ')); // Dart
}
类型提升依赖控制流分析。变量在检查后又被赋值、被无法安全分析的代码修改,或者程序离开当前有效路径时,提升可能不再成立。
使用 if-case 检查数据形状
从 Dart 3.0 开始,if-case 可以用模式检查值,并在匹配成功时解构出变量:
String readUser(Object data) {
if (data case {'name': String name, 'age': int age}) {
return '$name is $age years old';
}
return 'invalid user';
}
void main() {
print(readUser({'name': 'Mia', 'age': 20}));
}
只有 Map 同时包含符合类型要求的 name 和 age 时,模式才会匹配。变量 name 和 age 只在匹配成功的分支内可用。
if-case 适合只关心“匹配”与“不匹配”两条路径的场景。需要处理多个数据形状时,通常使用 switch 更清晰。模式的完整语法参见模式概览与用法。
使用 switch 语句执行多分支逻辑
switch 语句按顺序尝试 case,执行第一个匹配分支中的语句:
enum OrderStatus { pending, paid, shipped, cancelled }
void printNextAction(OrderStatus status) {
switch (status) {
case OrderStatus.pending:
print('wait for payment');
case OrderStatus.paid:
print('prepare shipment');
case OrderStatus.shipped:
print('track delivery');
case OrderStatus.cancelled:
print('close order');
}
}
void main() {
printNextAction(OrderStatus.paid); // prepare shipment
}
Dart 的非空 case 不会自动继续执行下一个 case,因此上例不需要在每个分支末尾写 break。通常让每个分支自然结束,或者用 return、throw 等语句明确离开当前流程。
多个值执行相同逻辑时,可以使用逻辑或模式 ||:
String dayType(String day) {
switch (day) {
case 'Saturday' || 'Sunday':
return 'weekend';
case 'Monday' || 'Tuesday' || 'Wednesday' || 'Thursday' || 'Friday':
return 'weekday';
default:
return 'invalid day';
}
}
void main() {
print(dayType('Sunday')); // weekend
}
default 匹配所有尚未处理的值。处理开放范围的类型(例如任意字符串)时,通常需要它来提供兜底路径。
使用 switch 表达式直接产生值
从 Dart 3.0 开始,switch 也可以作为表达式产生一个值。每个分支使用 => 连接模式和结果表达式,分支之间用逗号分隔:
enum TrafficLight { red, yellow, green }
String instruction(TrafficLight light) => switch (light) {
TrafficLight.red => 'stop',
TrafficLight.yellow => 'slow down',
TrafficLight.green => 'go',
};
void main() {
print(instruction(TrafficLight.green)); // go
}
当每个分支只是为了计算一个值时,switch 表达式通常比先声明变量、再在多个 case 中赋值更直接。分支需要执行多条语句时,则使用 switch 语句。
使用 when 添加守卫条件
模式匹配成功后,还可以使用 when 守卫检查额外的布尔条件。只有模式和守卫都成立,当前分支才会执行:
String classify(Object value) => switch (value) {
int score when score >= 90 => 'excellent',
int score when score >= 60 => 'passed',
int _ => 'failed',
_ => 'not a score',
};
void main() {
print(classify(95)); // excellent
print(classify('95')); // not a score
}
如果模式匹配但守卫为 false,Dart 会继续尝试后面的 case。守卫必须是 bool,适合表达无法只靠模式清楚描述的附加条件。
保证分支穷尽
穷尽(exhaustive)表示所有可能值都有对应分支。switch 表达式必须是穷尽的,否则代码无法保证产生结果:
enum ConnectionState { disconnected, connecting, connected }
String label(ConnectionState state) => switch (state) {
ConnectionState.disconnected => 'offline',
ConnectionState.connecting => 'connecting',
ConnectionState.connected => 'online',
};
对于 enum、sealed 类层次等边界明确的类型,分析器可以检查分支是否完整。逐项列出所有已知情况还有一个好处:以后新增枚举值或密封子类型时,未覆盖的位置会产生静态提示。
switch 语句不一定要求穷尽。如果遗漏情况会让程序处于不明确的状态,应补齐分支或添加 default。对于希望分析器持续检查完整性的封闭类型,优先显式列出每种情况,而不是过早使用 default 吞掉未来新增的值。
简单二选一可以使用条件表达式
只需要根据一个条件选择两个值时,可以使用 condition ? value1 : value2:
String accessLabel(bool isAdmin) => isAdmin ? 'administrator' : 'member';
void main() {
print(accessLabel(false)); // member
}
条件表达式适合简短的值选择。任一分支包含复杂计算、多个副作用或嵌套条件时,使用 if 会更容易阅读。
TypeScript 对比
Dart 和 TypeScript 都提供 if、else、switch 和条件表达式,但关键行为并不完全相同:
| 对比项 | Dart | TypeScript |
|---|---|---|
if 条件 | 必须是 bool | 会按 JavaScript 规则判断真值和假值 |
case 结束 | 非空分支不会落入下一个 case | 通常要用 break 或 return 防止落入下一分支 |
switch 表达式 | Dart 3.0 起原生支持 | 没有直接对应语法 |
| 模式与守卫 | 支持模式和 when 守卫 | 普通 switch 没有直接对应能力 |
function describeStatus(status: string): string {
switch (status) {
case 'ready':
return 'start';
case 'busy':
return 'wait';
default:
return 'unknown';
}
}
两种语言的基本分支思路相似。最需要记忆的是:Dart 条件严格要求 bool,并且 Dart 3 的 switch 不只是传统语句,还能结合模式、守卫和表达式完成更强的分支选择。
选择分支形式的简单规则
| 需求 | 推荐写法 |
|---|---|
| 判断一个或少量布尔条件 | if / else if / else |
| 检查一种数据形状并解构变量 | if-case |
| 在多个候选分支中执行语句 | switch 语句 |
| 在多个候选分支中计算一个值 | switch 表达式 |
| 简短地二选一产生值 | 条件表达式 ?: |
| 模式匹配后还需检查附加条件 | when 守卫 |
优先选择能直接表达意图的形式。无论使用哪一种,都应先处理更具体的条件,再处理宽泛的兜底条件,并确认所有必须处理的输入都有明确路径。