← 返回首页

🎯 Dart 语言基础

一、变量与类型

核心概念

Dart 是强类型、可空安全的语言。理解其类型系统和变量声明语义,是写出安全、高效 Dart 代码的前提。

  • var 类型推断声明,编译器根据右值自动确定类型。一旦推断出类型,后续赋值必须保持相同类型(与 JavaScript 的 var 不同,Dart 的 var 是静态类型)。
  • final 运行时常量,只能赋值一次,但值本身在运行期才确定(如 DateTime.now())。优先使用 final 是 Dart 最佳实践,可提高代码可读性和不可变性保障。
  • const 编译时常量,值在编译期必须完全确定。const 对象在内存中共享同一实例(规范化),可提升性能。Flutter Widget 构造函数大量使用 const 来避免不必要的重建。
  • late 延迟初始化修饰符。声明非空类型变量但稍后才初始化,或声明懒加载的 final 变量(首次访问时才执行初始化函数)。若在初始化前访问会抛 LateInitializationError。
  • dynamic 动态类型,绕过静态类型检查。可赋值任意类型,方法调用在运行时解析。慎用:失去类型安全保障,编译器无法检测错误,会导致运行时 NoSuchMethodError。
  • 空安全(Null Safety) Dart 2.12 引入的核心特性(Sound Null Safety)。默认所有类型不可为 null,需要可空时用 Type? 声明。编译器通过流分析(Flow Analysis)在大多数情况下自动完成类型提升,无需手动类型转换。

1.1 变量声明

Dart 提供了多种变量声明方式,每种都有不同的用途和语义。理解它们的区别是写出高质量 Dart 代码的基础。

// var - 类型自动推断,可重新赋值
var name = 'Flutter';     // 推断为 String
name = 'Dart';              // ✅ 可以重新赋值
// name = 123;              // ❌ 不能改变类型

// final - 运行时常量,只能赋值一次
final createdAt = DateTime.now();  // 运行时确定值
final String title = '学习Dart';  // 可以显式指定类型

// const - 编译时常量,必须在编译期确定
const pi = 3.14159;          // 编译时就确定的值
const maxItems = 100;        // 整数常量
// const now = DateTime.now(); // ❌ 运行时值不能用 const

// late - 延迟初始化
late String description;     // 稍后初始化
late final String config = loadConfig();  // 懒加载,首次访问时才执行
提示:优先使用 final,只有需要重新赋值时才用 varconst 用于编译时就能确定的值,如配置常量。late 常用于需要延迟初始化或在构造函数体中初始化的场景。

1.2 基本数据类型

Dart 是强类型语言,所有变量都有类型。以下是最常用的内置类型:

// 数字类型
int age = 25;                // 整数
double price = 19.99;         // 浮点数
num value = 42;               // int 或 double 的父类型

// 字符串
String greeting = 'Hello Dart';
String multiLine = '''
这是一个
多行字符串
''';
String interpolation = '年龄是 $age, 明年 ${age + 1}';

// 布尔值
bool isActive = true;

// 动态类型 - 慎用!
dynamic anything = 'text';
anything = 42;               // ✅ dynamic 可以改变类型
anything.noSuchMethod();      // 编译通过,运行时报错

// Object - 所有类的基类
Object obj = 'text';
// obj.length;                // ❌ 编译错误,Object 没有 length

// 类型转换
int parsed = int.parse('42');
double d = 42.toDouble();
String s = 42.toString();

1.3 空安全(Null Safety)

Dart 的空安全是其类型系统的核心特性,帮助你在编译期捕获空引用错误。

// 默认不可为 null
String name = 'Dart';
// name = null;               // ❌ 编译错误

// ? 声明可空类型
String? nickname;             // 默认值为 null
print(nickname);              // 输出: null

// ! 断言非空(确定不为 null 时使用)
String sure = nickname!;      // 如果 nickname 为 null 会抛异常

// ?? 空合并运算符
String display = nickname ?? '匿名用户';  // nickname 为 null 时用默认值

// ??= 空值赋值
nickname ??= '默认昵称';     // 仅在 null 时赋值

// ?. 安全调用运算符
int? length = nickname?.length;  // nickname 为 null 时返回 null

// 实际应用示例
String getUserName(Map<String, dynamic> json) {
  return json['name']?.toString() ?? '未知用户';
}

// 类型提升(Flow Analysis)
void process(String? input) {
  if (input == null) return;
  // 此处 input 自动提升为 String(非空)
  print(input.length);        // ✅ 安全访问
}
最佳实践:尽量避免使用 ! 操作符,它会跳过空安全检查。优先使用 ?? 提供默认值,或使用 if 判断让编译器自动进行类型提升。
常见误区 — dynamic vs Object?:当你需要接受任意类型时,应优先使用 Object? 而非 dynamicObject? 仍然受类型检查保护,访问方法前必须做类型检查或转换;dynamic 则完全绕过类型系统,任何方法调用都能编译通过但可能在运行时崩溃。
常见误区 — late 的使用风险:不要把 late 作为"懒人写法"来延迟处理空安全问题。late 将编译期的空安全检查推迟到运行时,如果变量在使用前确实未初始化,会抛出 LateInitializationError,比编译错误更难调试。在 StatefulWidget 中,late 变量应在 initState() 中确保完成初始化。
本章小结:Dart 的类型系统围绕"默认非空、声明可空"设计。变量声明选择:能用 final 就不用 var,能用 const 就不用 final。空安全操作符 ???.??= 是日常开发高频工具,配合编译器的流分析(Flow Analysis)可以写出简洁且类型安全的代码。

二、集合类型

核心概念

  • List<T> 有序可重复集合,支持随机访问(O(1))。底层实现为动态数组,在末尾追加元素为 O(1)摊销,中间插入为 O(n)。Flutter 的 children 属性接受 List<Widget>,是使用最频繁的集合类型。
  • Set<T> 无序不重复集合,基于哈希表实现,查找/插入/删除均为 O(1)。适合去重和集合运算(union/intersection/difference)。注意空字面量 {} 默认推断为 Map,需用 <String>{} 明确类型。
  • Map<K, V> 键值对集合,基于哈希表,键的查找为 O(1)。Dart 的 Map 保持插入顺序(LinkedHashMap)。JSON 解析的结果通常是 Map<String, dynamic>,是与后端数据交互的核心类型。
  • 展开运算符(...) 将一个集合的元素展开插入另一个集合。配合空感知展开 ...? 处理可空集合。在 Flutter 中常用于 Widget 列表的动态构建:[...items.map((e) => Text(e))]
  • 集合 if / 集合 for Dart 特有语法,允许在字面量集合中使用条件判断(if)和循环(for)。极大简化 Flutter Widget 树的动态构建,避免在 build() 方法外单独构建列表再传入。

2.1 List(列表)

List 是 Dart 中最常用的有序集合,类似于其他语言的数组。

// 创建列表
var fruits = ['苹果', '香蕉', '橙子'];  // 类型推断为 List<String>
List<int> numbers = [1, 2, 3, 4, 5];
var empty = <String>[];          // 空的类型列表

// 常用操作
fruits.add('葡萄');             // 添加元素
fruits.addAll(['芒果', '荔枝']);  // 添加多个
fruits.insert(0, '西瓜');      // 在指定位置插入
fruits.remove('香蕉');          // 删除元素
fruits.removeAt(0);             // 删除指定索引

// 函数式操作(非常重要!Flutter 中大量使用)
var lengths = fruits.map((f) => f.length).toList();
var longFruits = fruits.where((f) => f.length > 1).toList();
var hasApple = fruits.any((f) => f == '苹果');
var allLong = fruits.every((f) => f.length > 0);
var total = numbers.reduce((a, b) => a + b);  // 求和
var first = fruits.firstWhere((f) => f.startsWith('苹'));

// 不可变列表
final fixed = List.unmodifiable([1, 2, 3]);
const constList = [1, 2, 3];  // 编译时常量列表

2.2 Set(集合)与 Map(映射)

// Set - 无序不重复集合
var tags = {'Flutter', 'Dart', 'Mobile'};
tags.add('Flutter');    // 不会重复添加
tags.add('Web');

// Set 运算
var a = {1, 2, 3};
var b = {2, 3, 4};
print(a.union(b));          // {1, 2, 3, 4} 并集
print(a.intersection(b));   // {2, 3} 交集
print(a.difference(b));     // {1} 差集

// Map - 键值对
var user = {
  'name': '张三',
  'age': 25,
  'email': 'zhang@example.com',
};

// Map 操作
user['phone'] = '13800138000';      // 添加/修改
user.remove('email');                // 删除
var hasName = user.containsKey('name');

// 遍历 Map
user.forEach((key, value) {
  print('$key: $value');
});

// Map 转换
var upperKeys = user.map((k, v) =>
  MapEntry(k.toUpperCase(), v));

2.3 展开运算符与集合操作

// 展开运算符 ...
var base = [1, 2, 3];
var extended = [0, ...base, 4];  // [0, 1, 2, 3, 4]

// 空感知展开运算符 ...?
List<int>? maybeNull;
var safe = [0, ...?maybeNull];   // [0],不会报错

// 集合 if(在 Flutter UI 构建中非常实用)
var isLoggedIn = true;
var menu = [
  '首页',
  '发现',
  if (isLoggedIn) '个人中心',    // 条件添加
  if (!isLoggedIn) '登录',
];

// 集合 for
var items = [1, 2, 3];
var doubled = [
  for (var i in items) i * 2,   // [2, 4, 6]
];

// 组合使用 - Flutter 中的典型场景
var widgets = [
  'Header',
  for (var item in items)
    'Item: $item',
  if (items.length > 2) '查看更多...',
];
注意:集合 if 和集合 for 在 Flutter 的 Widget 树构建中大量使用,比如动态生成列表项或条件显示某些 Widget。务必掌握这些语法。
常见误区 — 修改 const 集合:const 列表/集合/映射是完全不可变的,调用 add()remove() 等方法会在运行时抛出 UnsupportedError。若需要可变集合,使用 final 代替 const,或在使用前调用 List.of(constList) 创建可变副本。
常见误区 — where().toList() 的惰性求值:list.where(condition) 返回的是 Iterable(懒求值),不是 List。每次遍历都会重新执行过滤条件。如果需要多次访问结果或修改结果集,必须调用 .toList() 将其转为 List。否则可能导致意外的性能问题。
本章小结:Dart 集合体系清晰:List(有序)、Set(去重)、Map(键值对)。函数式方法(map/where/reduce/any/every)配合集合 if/for 是 Flutter UI 构建的核心模式。展开运算符 ......? 让集合合并变得优雅。记住:where/map 返回 Iterable,需要 List 时调用 .toList()

三、函数

核心概念

  • 函数是一等对象 Dart 中函数(Function)是一等公民:可以赋值给变量、作为参数传入其他函数、从函数中返回。这是 Flutter 回调机制(onPressed、onChanged)的基础。函数类型写作 ReturnType Function(ParamType)
  • 命名参数(Named Parameters) 用花括号 {} 包裹,调用时使用 name: value 语法。Flutter Widget 构造函数几乎全部使用命名参数,因为它让代码具有自文档性。required 关键字标记必填命名参数,配合空安全防止遗漏。
  • 箭头函数(Arrow Function) 当函数体只有一个表达式时,用 => 语法简化。等价于 { return expression; }。在 Flutter 中大量用于简化 build 方法中的 Widget 工厂函数和列表变换。
  • 闭包(Closure) 能捕获并访问其定义作用域中变量的函数。Flutter 中的事件回调(如按钮的 onPressed)常常是闭包,会捕获外部状态变量。注意:在循环中创建闭包时,每个闭包都捕获同一个循环变量引用,需要特别注意。
  • typedef 为函数类型定义别名,提高代码可读性。例如 typedef OnChanged<T> = void Function(T value)。在 Flutter 框架源码中,typedef 用于标准化回调类型命名。

3.1 函数定义与箭头函数

Dart 中函数是一等对象,可以赋值给变量、作为参数传递、从函数返回。

// 标准函数定义
int add(int a, int b) {
  return a + b;
}

// 箭头函数(函数体只有一个表达式时使用)
int multiply(int a, int b) => a * b;

// 函数作为变量
var greet = (String name) => '你好,$name!';
print(greet('Flutter'));   // 你好,Flutter!

// 函数作为参数
void execute(int Function(int, int) operation, int x, int y) {
  print('结果: ${operation(x, y)}');
}
execute(add, 3, 4);        // 结果: 7
execute(multiply, 3, 4);   // 结果: 12

3.2 参数类型

// 必需位置参数
String fullName(String first, String last) {
  return '$first $last';
}

// 可选位置参数 - 用 [] 包裹
String introduce(String name, [int? age, String city = '未知']) {
  var result = '我是$name';
  if (age != null) result += ',$age岁';
  result += ',来自$city';
  return result;
}
introduce('张三');                // 我是张三,来自未知
introduce('李四', 25, '北京');  // 我是李四,25岁,来自北京

// 命名参数 - 用 {} 包裹(Flutter 中最常用!)
void createUser({
  required String name,     // 必需命名参数
  int age = 0,               // 带默认值的命名参数
  String? email,             // 可选命名参数
}) {
  print('创建用户: $name, $age, $email');
}

// 调用时参数名清晰可读
createUser(name: '王五', age: 30, email: 'wang@test.com');
createUser(name: '赵六');   // age 默认 0, email 为 null

3.3 闭包与 typedef

// 闭包 - 函数可以捕获外部变量
Function makeCounter() {
  int count = 0;
  return () {
    count++;
    return count;
  };
}
var counter = makeCounter();
print(counter());  // 1
print(counter());  // 2
print(counter());  // 3

// 闭包在 Flutter 中的实际应用
List<Function()> buildCallbacks(List<String> items) {
  return items.map((item) {
    return () => print('点击了: $item');
  }).toList();
}

// typedef - 定义函数类型别名
typedef Comparator<T> = int Function(T a, T b);
typedef OnChanged<T> = void Function(T value);

// 使用 typedef
void sortList(List<int> list, Comparator<int> compare) {
  list.sort(compare);
}
sortList([3, 1, 2], (a, b) => a - b);  // 升序排列
Flutter 重点:Flutter 中的 Widget 构造函数大量使用命名参数。回调函数(如 onPressedonChanged)是闭包的典型应用。建议牢记命名参数的 required 语法。
常见陷阱 — 可变默认参数:不要使用可变对象(List、Map)作为命名参数的默认值,因为默认值在函数定义时创建一次,所有调用共享同一个对象。正确做法:用 List<String>? tags 配合 tags ?? [],或使用 const 常量作为默认值:this.tags = const []
常见陷阱 — 循环中的闭包:在 for 循环中为每个元素创建回调时,所有闭包捕获的是同一个循环变量。这在 Flutter 中常见于动态生成按钮列表。解决方法:用函数式方法 items.map((item) => Button(onTap: () => handle(item))) ,每次 map 调用的 item 是独立的绑定。
本章小结:Dart 函数系统的三个核心:命名参数(Flutter Widget 构建的基础)、箭头函数(简化单表达式函数)、闭包(事件回调的底层机制)。函数类型 T Function(P) 写法是 Dart 类型系统的重要部分,掌握它才能看懂 Flutter 源码中大量的回调类型定义。

四、控制流

4.1 条件与循环

// if-else
var score = 85;
if (score >= 90) {
  print('优秀');
} else if (score >= 60) {
  print('及格');
} else {
  print('不及格');
}

// 三元表达式
var status = score >= 60 ? '通过' : '未通过';

// switch 语句
var command = 'open';
switch (command) {
  case 'open':
    print('打开');
    break;
  case 'close':
    print('关闭');
    break;
  default:
    print('未知命令');
}

// for 循环
for (var i = 0; i < 5; i++) {
  print('索引: $i');
}

// for-in 遍历
var colors = ['红', '绿', '蓝'];
for (var color in colors) {
  print(color);
}

// while 和 do-while
var n = 5;
while (n > 0) {
  print(n--);
}

4.2 Dart 3 模式匹配(Pattern Matching)

Dart 3 引入了强大的模式匹配功能,让代码更简洁、更安全。

// switch 表达式(Dart 3 新语法)
String describe(int score) => switch (score) {
  >= 90 => '优秀',
  >= 80 => '良好',
  >= 60 => '及格',
  _    => '不及格',
};

// 解构赋值
var point = (3, 4);           // Record
var (x, y) = point;            // 解构: x=3, y=4

// 命名字段解构
var user = (name: '张三', age: 25);
var (:name, :age) = user;      // name='张三', age=25

// List 解构
var [first, second, ...rest] = [1, 2, 3, 4, 5];
// first=1, second=2, rest=[3,4,5]

// Map 解构
var {'name': userName, 'age': userAge} = {'name': '李四', 'age': 30};

// if-case 模式匹配
var data = {'type': 'success', 'value': 42};
if (data case {'type': 'success', 'value': int v}) {
  print('成功,值为: $v');     // v 自动绑定且类型为 int
}

// switch 匹配复杂类型
void handleResponse(Object response) {
  switch (response) {
    case String s:
      print('字符串: $s');
    case int n when n > 0:
      print('正整数: $n');
    case [int x, int y]:
      print('坐标: ($x, $y)');
    case {'error': String msg}:
      print('错误: $msg');
    default:
      print('未知类型');
  }
}
Dart 3 提示:模式匹配是 Dart 3 最重要的新特性之一。switch 表达式(用 =>)比传统 switch 语句更简洁,且编译器会检查是否覆盖了所有情况。
常见误区 — switch 语句的穿透(Fall-through):Dart 的 switch 语句不支持隐式穿透(fall-through),每个 case 必须有 breakreturncontinue。但 Dart 3 的新 switch 表达式(=> 语法)语义上是独立的,不需要 break。迁移旧代码时需区分两种写法。
本章小结:Dart 控制流的亮点是 Dart 3 引入的模式匹配。switch 表达式替代传统 switch-case,代码更简洁且编译器强制穷尽检查。解构赋值(var (x, y) = point)和 if-case 让复杂数据解包变得优雅。配合 sealed 类使用时,穷尽匹配是消除 runtime 错误的有力工具。

五、面向对象

核心概念

  • class(类) Dart 中所有类都隐式继承自 Object。Dart 使用单继承,但通过 mixin 实现代码的多维复用。类可以有多个命名构造函数(Named Constructors),这是 Dart 区别于 Java 的重要设计。
  • 构造函数语法糖 Dart 的 this.fieldName 参数语法可在构造函数参数列表中直接给字段赋值,无需在函数体写 this.name = name。初始化列表(: field = expr)在构造函数体执行前运行,适合字段的计算初始化。
  • 工厂构造函数(factory) 可以返回缓存实例、子类实例或通过其他逻辑创建对象,而非每次都创建新对象。常用于实现单例模式(如 Logger)和 JSON 工厂方法(UserModel.fromJson)。
  • Mixin Dart 特有的代码复用机制。用 mixin 关键字定义,用 with 混入类中。Mixin 不能实例化,可以定义方法和字段。Flutter 框架中大量使用,如 TickerProviderStateMixinAutomaticKeepAliveClientMixin
  • 扩展方法(extension) 无需修改原类即可为其添加新方法。语法:extension Name on ExistingType { ... }。常用于给 String、List 等内置类型添加工具方法,让调用更自然('email@test.com'.isEmail)。

5.1 类与构造函数

Dart 是纯面向对象语言,所有值都是对象。类是组织代码的核心方式。

class User {
  // 实例属性
  final String name;
  int age;
  String? _email;          // _ 开头表示私有

  // 默认构造函数 - 使用语法糖简化赋值
  User(this.name, this.age, [this._email]);

  // 命名构造函数
  User.guest() : name = '访客', age = 0;

  User.fromJson(Map<String, dynamic> json)
    : name = json['name'] as String,
      age = json['age'] as int;

  // getter 和 setter
  String? get email => _email;
  set email(String? value) {
    if (value != null && value.contains('@')) {
      _email = value;
    }
  }

  // 计算属性
  bool get isAdult => age >= 18;

  // 方法
  String greet() => '大家好,我是$name';

  // 重写 toString
  @override
  String toString() => 'User($name, $age)';
}

// 使用
var user = User('张三', 25, 'zhang@test.com');
var guest = User.guest();
var jsonUser = User.fromJson({'name': '李四', 'age': 30});

5.2 工厂构造函数与 const 构造函数

// 工厂构造函数 - 可以返回缓存实例或子类
class Logger {
  static final Map<String, Logger> _cache = {};
  final String name;

  // 私有构造函数
  Logger._internal(this.name);

  // 工厂构造函数 - 实现单例模式
  factory Logger(String name) {
    return _cache.putIfAbsent(
      name,
      () => Logger._internal(name),
    );
  }

  void log(String msg) => print('[$name] $msg');
}

// 两个变量指向同一个实例
var log1 = Logger('APP');
var log2 = Logger('APP');
print(identical(log1, log2));  // true

// const 构造函数 - 编译时常量对象
class Point {
  final double x;
  final double y;

  const Point(this.x, this.y);

  // const 构造函数中所有字段必须是 final
  double get distance => (x * x + y * y);
}

// const 对象在编译时创建,相同值共享实例
const p1 = Point(1, 2);
const p2 = Point(1, 2);
print(identical(p1, p2));    // true,共享同一实例

5.3 继承与抽象类

// 抽象类 - 不能直接实例化
abstract class Shape {
  // 抽象方法 - 子类必须实现
  double area();
  double perimeter();

  // 普通方法 - 子类可以继承
  String describe() => '面积: ${area().toStringAsFixed(2)}';
}

class Circle extends Shape {
  final double radius;
  Circle(this.radius);

  @override
  double area() => 3.14159 * radius * radius;

  @override
  double perimeter() => 2 * 3.14159 * radius;
}

class Rectangle extends Shape {
  final double width, height;
  Rectangle(this.width, this.height);

  @override
  double area() => width * height;

  @override
  double perimeter() => 2 * (width + height);
}

5.4 Mixin 与扩展方法

// Mixin - 代码复用机制(不同于继承)
mixin Printable {
  void printInfo() => print(toString());
}

mixin Serializable {
  Map<String, dynamic> toJson();

  String serialize() => toJson().toString();
}

// 使用 with 关键字混入
class Product with Printable, Serializable {
  final String name;
  final double price;

  Product(this.name, this.price);

  @override
  Map<String, dynamic> toJson() => {
    'name': name,
    'price': price,
  };

  @override
  String toString() => '$name (¥$price)';
}

// 扩展方法 - 给现有类添加功能
extension StringX on String {
  // 检查是否为有效邮箱
  bool get isEmail =>
    RegExp(r'^[\w-\.]+@[\w-]+\.\w+$').hasMatch(this);

  // 首字母大写
  String get capitalize =>
    isEmpty ? this : '${this[0].toUpperCase()}${substring(1)}';
}

// 使用扩展方法
print('test@mail.com'.isEmail);   // true
print('hello'.capitalize);        // Hello

// 增强枚举(Dart 2.17+)
enum Priority {
  low(1, '低'),
  medium(2, '中'),
  high(3, '高'),
  urgent(4, '紧急');

  final int level;
  final String label;
  const Priority(this.level, this.label);

  // 枚举可以有方法和 getter
  bool get isUrgent => level >= 3;

  @override
  String toString() => 'Priority.$name($label)';
}

var p = Priority.high;
print(p.label);        // 高
print(p.isUrgent);     // true
注意:Mixin 是 Dart 中非常灵活的代码复用方式,Flutter 框架中大量使用(如 TickerProviderStateMixin)。扩展方法可以让你给任何类添加方法,非常适合编写工具方法。增强枚举让枚举类型更加强大实用。
常见误区 — Dart 没有真正的私有成员:Dart 的私有修饰符是 _ 前缀,其作用域是库(library)级别,而非类级别。这意味着同一个文件(库)内的其他类可以访问 _field,但不同文件不能。与 Java 的 private 关键字(类级私有)不同,不能通过继承或同包访问。
常见误区 — mixin 的使用限制:如果 mixin 需要依赖特定的超类接口,应使用 mixin MyMixin on SuperClass 声明约束。不加约束的 mixin 可以被任何类混入,但如果 mixin 内部调用了 super 方法而混入的类没有该方法,会编译报错。理解 mixin 的线性化顺序(C3 linearization)有助于排查方法重写问题。
本章小结:Dart OOP 的设计哲学强调不可变性(final 字段)和工厂模式(JSON 反序列化)。Mixin 是 Flutter 框架扩展 Widget 功能的主要机制(动画、生命周期等)。掌握 copyWith 模式是与不可变状态管理(Riverpod/Bloc)配合使用的关键。extension 方法让代码更具表现力而无需修改原始类。

六、泛型

6.1 泛型类与泛型函数

泛型允许你编写类型安全且可复用的代码,避免重复编写类似逻辑。

// 泛型类 - 通用的结果包装器
class Result<T> {
  final T? data;
  final String? error;
  final bool isSuccess;

  Result.success(this.data)
    : error = null, isSuccess = true;

  Result.failure(this.error)
    : data = null, isSuccess = false;

  // 泛型方法 - 转换包装的数据类型
  Result<R> map<R>(R Function(T) transform) {
    if (isSuccess && data != null) {
      return Result.success(transform(data as T));
    }
    return Result.failure(error);
  }
}

// 使用泛型类
var result = Result<int>.success(42);
var strResult = result.map((n) => '数字是 $n');  // Result<String>

var error = Result<String>.failure('网络错误');

// 泛型函数
T firstOrDefault<T>(List<T> list, T defaultValue) {
  return list.isNotEmpty ? list.first : defaultValue;
}

var num1 = firstOrDefault([1, 2, 3], 0);      // int 类型
var str1 = firstOrDefault(<String>[], '空');   // String 类型

6.2 类型约束

// 使用 extends 约束泛型类型
class NumberList<T extends num> {
  final List<T> _items = [];

  void add(T value) => _items.add(value);

  // 因为 T 约束为 num,可以安全调用 num 的方法
  T get max => _items.reduce((a, b) => (a > b ? a : b) as T);

  double get average {
    if (_items.isEmpty) return 0;
    var sum = _items.fold<num>(0, (prev, e) => prev + e);
    return sum / _items.length;
  }
}

var intList = NumberList<int>();
intList.add(10);
intList.add(20);
print(intList.average);       // 15.0

// 泛型实际应用 - 仓库模式
abstract class Repository<T> {
  Future<List<T>> getAll();
  Future<T?> getById(String id);
  Future<void> save(T entity);
  Future<void> delete(String id);
}

// 具体实现
class UserRepository implements Repository<User> {
  @override
  Future<List<User>> getAll() async {
    // 从数据库或 API 获取用户列表
    return [];
  }
  // ... 其他方法
  @override
  Future<User?> getById(String id) async => null;
  @override
  Future<void> save(User entity) async {}
  @override
  Future<void> delete(String id) async {}
}
提示:泛型在 Flutter 中无处不在。ListView.builderFutureBuilder<T>ValueNotifier<T> 等都依赖泛型。掌握泛型是理解 Flutter 源码的前提。

七、异步编程

7.1 Future 与 async/await

异步编程是 Dart 的核心特性之一。网络请求、文件读写、数据库操作等都是异步的。

// 返回 Future 的函数
Future<String> fetchUserName(int userId) async {
  // 模拟网络请求延迟
  await Future.delayed(Duration(seconds: 2));
  return '用户_$userId';
}

// 使用 async/await
Future<void> loadData() async {
  try {
    var name = await fetchUserName(1);
    print('获取到: $name');
  } catch (e) {
    print('出错了: $e');
  } finally {
    print('加载完成');
  }
}

// 并行执行多个异步任务
Future<void> loadAll() async {
  // 同时发起多个请求,等待全部完成
  var results = await Future.wait([
    fetchUserName(1),
    fetchUserName(2),
    fetchUserName(3),
  ]);
  print(results);  // [用户_1, 用户_2, 用户_3]
}

// Future 链式调用
fetchUserName(1)
  .then((name) => print('用户: $name'))
  .catchError((e) => print('错误: $e'))
  .whenComplete(() => print('完成'));

// 带超时的 Future
var name = await fetchUserName(1)
  .timeout(Duration(seconds: 5),
    onTimeout: () => '超时默认值');

7.2 Stream(数据流)

// 创建 Stream
Stream<int> countStream(int max) async* {
  for (var i = 1; i <= max; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i;  // 逐个产出值
  }
}

// 监听 Stream
Future<void> listenToCount() async {
  // 方式1: await for
  await for (var count in countStream(5)) {
    print('计数: $count');
  }

  // 方式2: listen
  countStream(3).listen(
    (data) => print('数据: $data'),
    onError: (e) => print('错误: $e'),
    onDone: () => print('完成'),
  );
}

// StreamController - 手动控制 Stream
import 'dart:async';

class EventBus {
  final _controller = StreamController<String>.broadcast();

  Stream<String> get events => _controller.stream;

  void emit(String event) => _controller.add(event);

  void dispose() => _controller.close();
}

// Stream 转换操作
countStream(10)
  .where((n) => n.isEven)        // 过滤偶数
  .map((n) => '偶数: $n')        // 转换
  .take(3)                       // 只取前3个
  .listen(print);

7.3 Isolate 与 compute()

import 'dart:isolate';
import 'package:flutter/foundation.dart';

// compute() - Flutter 提供的简便方式在隔离线程中执行计算
// 适用于耗时的纯计算任务,避免阻塞 UI 线程

// 必须是顶层函数或静态方法
int heavyComputation(int n) {
  // 模拟耗时计算(如大数据解析、图片处理)
  var sum = 0;
  for (var i = 0; i < n; i++) {
    sum += i;
  }
  return sum;
}

// 使用 compute
Future<void> runHeavyTask() async {
  // 在独立 Isolate 中执行,不会卡 UI
  var result = await compute(heavyComputation, 1000000);
  print('计算结果: $result');
}

// 直接使用 Isolate(更灵活但更复杂)
Future<void> useIsolate() async {
  // Dart 2.19+ Isolate.run 简化写法
  var result = await Isolate.run(() {
    // 这段代码在独立 Isolate 中执行
    return heavyComputation(1000000);
  });
  print('Isolate 结果: $result');
}

// JSON 解析实际示例
List<Map<String, dynamic>> parseJsonList(String jsonStr) {
  // 假设这是大量 JSON 数据的解析
  import 'dart:convert';
  return (jsonDecode(jsonStr) as List).cast();
}

// 在 Isolate 中解析大 JSON
Future<List<Map<String, dynamic>>> fetchAndParse() async {
  var jsonStr = await fetchFromNetwork();  // 网络请求
  return await compute(parseJsonList, jsonStr);  // 隔离线程解析
}
重要:在 Flutter 中,UI 运行在主 Isolate 上。耗时超过 16ms 的计算(如大 JSON 解析、图片处理)应该放在 compute() 或单独的 Isolate 中执行,否则会导致界面卡顿。async/await 不会创建新线程,它只是异步等待。
常见误区 — async 函数的错误处理:在 Flutter 中,如果 async 函数抛出异常但没有 try-catch 捕获,异常不会自动显示在界面上,而是进入 Flutter 的未处理异常处理器(FlutterError.onError)。在 initState 中调用 async 函数时尤其注意:不能直接 await,必须用 void Function() 包裹并在内部处理错误。
常见误区 — StreamController 忘记 close:使用 StreamController 时必须在 Widget 销毁时调用 controller.close(),否则会造成内存泄漏。在 StatefulWidget 中,应在 dispose() 方法中关闭。对于广播 Stream(.broadcast()),忘记取消监听(subscription.cancel())同样会导致泄漏。
本章小结:Dart 异步体系以 Future(单次结果)和 Stream(数据流)为核心。async/await 是语法糖,底层是事件循环(Event Loop)和微任务队列(Microtask Queue)。关键原则:async/await 不创建新线程,CPU 密集型任务必须用 Isolate.run() 或 compute()。FutureBuilder 和 StreamBuilder 是 Flutter 将异步数据绑定到 UI 的标准方式。

八、高级特性(Dart 3)

8.1 Records(记录)

Records 是 Dart 3 引入的轻量级不可变数据结构,适合返回多个值或临时数据组合。

// 位置 Record
(int, String) getUserInfo() {
  return (25, '张三');
}
var info = getUserInfo();
print(info.$1);  // 25 (位置从 $1 开始)
print(info.$2);  // 张三

// 命名 Record
({String name, int age, String email}) getUser() {
  return (name: '李四', age: 30, email: 'li@test.com');
}
var user = getUser();
print(user.name);   // 李四
print(user.age);    // 30

// 混合 Record(位置 + 命名)
(int, int, {String label}) point = (10, 20, label: '原点');

// Record 解构
var (age, name) = getUserInfo();  // age=25, name='张三'

// Record 的相等性比较(按值比较)
var r1 = (1, 'a');
var r2 = (1, 'a');
print(r1 == r2);    // true(值相等即相等)

// 实际应用 - 替代简单的数据类
Future<({List<String> items, bool hasMore})> fetchPage(int page) async {
  // 一次返回多个相关的值
  return (items: ['项目1', '项目2'], hasMore: true);
}

8.2 Sealed 类与穷尽匹配

// sealed 类 - 限制子类范围,支持穷尽匹配
sealed class AppState {}

class Loading extends AppState {}

class Loaded extends AppState {
  final List<String> data;
  Loaded(this.data);
}

class Error extends AppState {
  final String message;
  Error(this.message);
}

// 穷尽匹配 - 编译器确保处理所有情况
String renderState(AppState state) => switch (state) {
  Loading()       => '加载中...',
  Loaded(data: var items) => '共 ${items.length} 条数据',
  Error(message: var msg) => '错误: $msg',
  // 不需要 default!编译器知道已覆盖所有情况
};

// 状态管理实际应用
sealed class NetworkResult<T> {}

class Success<T> extends NetworkResult<T> {
  final T data;
  Success(this.data);
}

class Failure<T> extends NetworkResult<T> {
  final String error;
  final int? statusCode;
  Failure(this.error, [this.statusCode]);
}

// 在 Widget 中使用
Widget buildContent(NetworkResult<User> result) => switch (result) {
  Success(data: var user) => Text('欢迎, ${user.name}'),
  Failure(error: var msg, statusCode: 404) => Text('未找到: $msg'),
  Failure(error: var msg) => Text('错误: $msg'),
};

8.3 更多模式匹配技巧

// 对象模式 - 匹配对象的属性
class Rect {
  final double width, height;
  Rect(this.width, this.height);
  double get area => width * height;
}

String classifyRect(Rect r) => switch (r) {
  Rect(width: var w, height: var h) when w == h => '正方形 ($w x $h)',
  Rect(area: > 100)   => '大矩形',
  Rect(area: > 50)    => '中矩形',
  _                    => '小矩形',
};

// guard 子句 when
String checkAge(Object value) => switch (value) {
  int n when n < 0   => '无效年龄',
  int n when n < 18  => '未成年',
  int n when n < 60  => '成年人',
  int _              => '老年人',
  _                  => '不是数字',
};

// 逻辑模式组合
String describeNumber(int n) => switch (n) {
  0                 => '零',
  1 || 2 || 3       => '小数字',       // 或模式
  >= 4 && <= 10     => '中等数字',     // 且模式
  > 10              => '大数字',
  _                 => '负数',
};
进阶提示:Sealed 类 + 模式匹配是 Dart 3 中管理应用状态的最佳实践。编译器会强制你处理所有可能的状态,大大减少遗漏导致的 bug。这在 Flutter 状态管理中特别有用。
Dart 3 版本要求:Records、Patterns、Sealed 类等特性需要 Dart 3.0+(对应 Flutter 3.10+)。如果项目中的 pubspec.yaml 中 sdk 约束是 >=2.x.x,需要升级到 >=3.0.0 才能使用这些特性。升级时还需注意 Dart 3 对一些旧语法的重大变更(如 mixin 声明方式)。
本章小结(Dart 3 新特性):Records 解决了"函数返回多个值"的痛点,轻量且类型安全,比 Map 更好。Patterns 和 sealed 类的组合是 Dart 3 最强大的特性:用 sealed 类定义封闭的状态集合,用 switch 表达式穷尽处理每种状态,编译器保证不会遗漏。这个模式已成为 Flutter Bloc/Riverpod 等状态管理框架的推荐实践。

九、实践练习

练习一:数据模型与 JSON 序列化

实现一个完整的用户数据模型,包含 JSON 序列化、数据验证和工厂构造函数。

// 练习:补全以下代码并运行测试
class UserModel {
  final String id;
  final String name;
  final String email;
  final int age;
  final List<String> tags;

  const UserModel({
    required this.id,
    required this.name,
    required this.email,
    required this.age,
    this.tags = const [],
  });

  // 工厂构造:从 JSON 创建
  factory UserModel.fromJson(Map<String, dynamic> json) {
    return UserModel(
      id: json['id'] as String,
      name: json['name'] as String,
      email: json['email'] as String,
      age: json['age'] as int,
      tags: (json['tags'] as List?)
        ?.map((e) => e as String).toList() ?? [],
    );
  }

  // 转换为 JSON
  Map<String, dynamic> toJson() => {
    'id': id,
    'name': name,
    'email': email,
    'age': age,
    'tags': tags,
  };

  // copyWith - 不可变对象的更新模式
  UserModel copyWith({
    String? name,
    String? email,
    int? age,
    List<String>? tags,
  }) {
    return UserModel(
      id: id,
      name: name ?? this.name,
      email: email ?? this.email,
      age: age ?? this.age,
      tags: tags ?? this.tags,
    );
  }

  // 数据验证
  bool get isValid =>
    name.isNotEmpty &&
    email.contains('@') &&
    age > 0 && age < 150;

  @override
  String toString() => 'UserModel($name, $email)';
}

// 测试代码
void main() {
  var user = UserModel.fromJson({
    'id': '001',
    'name': '张三',
    'email': 'zhang@test.com',
    'age': 25,
    'tags': ['Flutter', 'Dart'],
  });

  print(user.isValid);                     // true
  print(user.toJson());                     // JSON Map
  print(user.copyWith(name: '李四').name);  // 李四
}

练习二:异步任务管理器

实现一个简单的异步任务管理器,支持并发控制和结果收集。

// 练习:实现一个带并发控制的任务执行器
class TaskRunner<T> {
  final int maxConcurrent;
  final List<Future<T> Function()> _tasks = [];
  final List<T> _results = [];
  int _running = 0;

  TaskRunner({this.maxConcurrent = 3});

  // 添加任务
  void addTask(Future<T> Function() task) {
    _tasks.add(task);
  }

  // 执行所有任务(带并发控制)
  Future<List<T>> runAll() async {
    final completer = Completer<List<T>>();
    var index = 0;

    void runNext() async {
      if (index >= _tasks.length) {
        if (_running == 0) {
          completer.complete(_results);
        }
        return;
      }

      final currentIndex = index++;
      _running++;

      try {
        var result = await _tasks[currentIndex]();
        _results.add(result);
      } catch (e) {
        print('任务 $currentIndex 失败: $e');
      } finally {
        _running--;
        runNext();  // 完成一个就启动下一个
      }
    }

    // 启动初始批次
    var initial = _tasks.length < maxConcurrent
        ? _tasks.length
        : maxConcurrent;
    for (var i = 0; i < initial; i++) {
      runNext();
    }

    if (_tasks.isEmpty) return [];
    return completer.future;
  }
}

// 使用示例
void main() async {
  var runner = TaskRunner<String>(maxConcurrent: 2);

  for (var i = 0; i < 5; i++) {
    runner.addTask(() async {
      await Future.delayed(Duration(seconds: 1));
      return '任务$i完成';
    });
  }

  var results = await runner.runAll();
  print(results);  // [任务0完成, 任务1完成, ...]
}

练习三:Sealed 类实现状态机

使用 Dart 3 的 Sealed 类实现一个购物车状态管理。

// 练习:使用 sealed 类管理购物车状态
sealed class CartState {}

class CartEmpty extends CartState {}

class CartLoading extends CartState {}

class CartLoaded extends CartState {
  final List<CartItem> items;
  CartLoaded(this.items);

  double get totalPrice =>
    items.fold(0, (sum, item) => sum + item.price * item.quantity);

  int get itemCount =>
    items.fold(0, (sum, item) => sum + item.quantity);
}

class CartError extends CartState {
  final String message;
  CartError(this.message);
}

// 购物车项目
class CartItem {
  final String name;
  final double price;
  final int quantity;

  const CartItem({
    required this.name,
    required this.price,
    this.quantity = 1,
  });

  CartItem copyWith({int? quantity}) =>
    CartItem(name: name, price: price, quantity: quantity ?? this.quantity);
}

// 渲染购物车状态(穷尽匹配)
String renderCart(CartState state) => switch (state) {
  CartEmpty()     => '🛒 购物车为空,去逛逛吧!',
  CartLoading()   => '⏳ 加载中...',
  CartLoaded(items: []) => '🛒 购物车为空',
  CartLoaded(items: var items, totalPrice: var total)
    => '🛍 ${items.length} 种商品,合计: ¥${total.toStringAsFixed(2)}',
  CartError(message: var msg) => '❌ $msg',
};

// 测试
void main() {
  var states = [
    CartEmpty(),
    CartLoading(),
    CartLoaded([
      CartItem(name: 'Flutter 实战', price: 79.0, quantity: 2),
      CartItem(name: 'Dart 编程', price: 59.0),
    ]),
    CartError('网络连接失败'),
  ];

  for (var state in states) {
    print(renderCart(state));
  }
  // 输出:
  // 🛒 购物车为空,去逛逛吧!
  // ⏳ 加载中...
  // 🛍 2 种商品,合计: ¥217.00
  // ❌ 网络连接失败
}
练习建议:建议在 DartPad 上运行这些代码。尝试修改和扩展功能,比如给 UserModel 添加密码验证、给 TaskRunner 添加重试机制、给购物车添加优惠券功能等。

目录