Dart 速通:类型、空安全与异步

Dart 是 Flutter 唯一的前端语言,它比 JavaScript 严格、比 Java 简洁。会 JS 或 Java 的人半天就能写起来,真正需要刻意练的只有两块:空安全与异步。本章只讲写 Flutter 天天要用的语法。

变量与类型

void main() {
  var name = '瑞林';           // 类型推断为 String
  final now = DateTime.now();  // 运行期常量,只赋值一次
  const pi = 3.14159;          // 编译期常量,性能更好
  late String token;           // 延迟初始化,使用前必须赋值
  token = 'abc123';
  print('$name $now $pi $token'); // 插值用 $,复杂表达式用 ${}
}
关键字赋值时机是否可改典型用途
var声明时推断可改局部变量
final运行时一次不可改接口返回、Controller 引用
const编译期不可改Widget 构造、常量表
late稍后赋值可改initState 里初始化的字段

空安全

Dart 3 默认开启空安全:String 一定不是 null,可能为 null 的必须写成 String?,编译器强制你处理。

class User {
  final String name;
  final int? age; // 可能为 null
  const User({required this.name, this.age});
  String get label => age == null ? '$name 年龄未知' : '$name ${age!} 岁';
}

void demo(List<User> users) {
  final first = users.isEmpty ? null : users.first;
  print(first?.name ?? '没有用户'); // 安全访问 + 默认值
}
写法含义什么时候用
String?可空类型字段或返回值可能缺失
value!断言非空逻辑上一定非空,误判会抛异常
a?.ba 为 null 时整体为 null链式访问可空对象
a ?? ba 为 null 时取 b提供默认值
a ??= ba 为 null 时赋值懒初始化缓存
required具名参数必填构造函数不可省略的参数

类、mixin 与继承

extends 只能继承一个类,with 可混入多个 mixinimplements 只拿到接口约定、实现全部自己写。

abstract class Repository {            // 接口约定
  Future<List<String>> load();
}
mixin Loggable {
  void log(String msg) => print('[log] $msg');
}
class ApiRepository with Loggable implements Repository {
  @override
  Future<List<String>> load() async {
    log('开始请求');
    return const ['a', 'b'];
  }
}
class CachedRepository extends ApiRepository { // 继承实现,复用父类逻辑
  final List<String> _cache = [];
  @override
  Future<List<String>> load() async {
    if (_cache.isEmpty) _cache.addAll(await super.load());
    return _cache;
  }
}

集合与扩展

void collections() {
  final list = [1, 2, 3, 4, 5, 6];
  final doubled = list.map((e) => e * 2).toList();    // 映射
  final evens = list.where((e) => e.isEven).toList(); // 过滤
  final sum = list.fold<int>(0, (acc, e) => acc + e); // 聚合
  final tags = <String>{'dart', 'flutter'};           // Set 自动去重
  print('$doubled $evens $sum $tags');
}

extension NumFormat on double {
  String toMoney() => '¥${toStringAsFixed(2)}'; // 给已有类型加方法
}
typedef Json = Map<String, dynamic>;
typedef OnTapCallback = void Function(int index);

异步:Future 与 Stream

类型语义典型场景
Future<T>一次性的异步结果网络请求、读写文件
Stream<T>连续多次的事件流数据库监听、WebSocket
Future<String> fetchUserName(int id) async {
  await Future<void>.delayed(const Duration(milliseconds: 300));
  if (id <= 0) throw ArgumentError('id 必须为正数');
  return 'user_$id';
}

Future<void> run() async {
  try {
    print(await fetchUserName(1));
  } on ArgumentError catch (e) {
    print('参数错误:$e'); // 具名捕获,先具体后笼统
  } catch (e) {
    print('未知错误:$e');
  }
  print(await Future.wait([fetchUserName(1), fetchUserName(2)])); // 并行等待
}

Stream<int> ticker() async* {
  for (var i = 0; i < 3; i++) {
    await Future<void>.delayed(const Duration(seconds: 1));
    yield i; // yield 逐个产出事件
  }
}

与 JavaScript / Java 的差异对照

关注点DartJavaScriptJava
类型系统静态 + 推断动态(TS 才有静态类型)静态
空值String? + ?. + ?? 强制处理null 随处可见引用类型可为 null
常量const(编译期)/ final(运行期)仅块级 constfinal
异步async/await + Future/Streamasync/await + PromiseCompletableFuture
构造函数命名参数 + this.x 简写无类构造概念支持重载

小结:Dart 的重点只有三件事——用 final/const 表达不可变、用空安全把 null 消灭在编译期、用 async/await 处理一次性异步而用 Stream 处理连续事件;把 ! 当最后手段,能不用就不用。

笔记加载中…