第 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 拦截示例。
← 上一章第 2 章 环境搭建(Windows + Android 方向)下一章 →第 4 章 核心概念:Widget 与声明式 UI
✉ 联系作者:linghurongjun@sina.com