2、Dart语言基础:变量与数据类型、函数与闭包、面向对象编程、异步编程

好,咱们直接进入正题。Dart 是 Flutter 的基石,你如果 Dart 写得不顺手,Flutter 开发起来会非常痛苦。我个人带团队时,发现很多新人上来就怼 Widget,结果遇到状态管理、异步请求时一脸懵。说白了,地基没打牢。

这一章我会把 Dart 最核心的四个部分串起来讲。你跟着我过一遍,基本就能上手写业务了。

2.1 变量与数据类型

Dart 是强类型语言,但它也给了你偷懒的空间。我个人习惯:能用 var 的地方就用 var,但前提是你一眼能看出类型。如果类型不明确,我会显式声明。

var name = '张三'; // 编译器自动推断为 String
int age = 28;      // 显式声明,更清晰
dynamic anything = '可以是任何类型'; // 慎用,我一般只在 JSON 解析时用
注意: 我曾经在项目里滥用 dynamic,结果运行时类型错误导致线上崩溃。从那以后,我给自己定了个规矩:dynamic 只在和原生平台交互时使用。

Dart 的数据类型分为几类:

  • 基本类型: intdoubleStringbool
  • 集合类型: ListSetMap
  • 特殊类型: nullSymbolRunes

这里有个坑,我提一下。Dart 里 intdouble 都是 num 的子类。你写 API 返回时,如果后端返回了 3.0,Dart 会把它解析成 double。你如果强转成 int,会报错。嗯,我踩过这个坑。

2.2 函数与闭包

Dart 里函数是一等公民。什么意思?就是函数可以赋值给变量,可以作为参数传递,也可以作为返回值。这在 Flutter 里太常见了,比如你写 onPressed 回调,本质上就是在传函数。

// 普通函数
int add(int a, int b) {
  return a + b;
}

// 箭头函数(单表达式)
int multiply(int a, int b) => a * b;

// 函数作为参数
void execute(int a, int b, int Function(int, int) operation) {
  print(operation(a, b));
}

闭包这个东西,很多新手觉得玄乎。说白了,闭包就是一个函数,它记住了自己创建时的环境变量。我举个例子:

Function makeCounter() {
  int count = 0;
  return () {
    count++;
    return count;
  };
}

void main() {
  var counter = makeCounter();
  print(counter()); // 1
  print(counter()); // 2
}

你看,count 变量明明在 makeCounter 执行完后就应该被销毁了,但闭包把它「包」住了。我在项目中用闭包做过防抖函数,效果很好。

小技巧: 闭包在 Flutter 里常用于保持状态,比如在 StatefulWidget 里创建回调函数时,可以用闭包捕获当前的状态值,避免异步回调时读到过期的数据。

2.3 面向对象编程

Dart 是纯面向对象的语言,连 intnull 都是对象。这一点和 Java 不同,Java 有基本类型和包装类型的区分,Dart 没有。

类的基本写法:

class User {
  final String name;
  int age;

  // 构造函数
  User(this.name, this.age);

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

  void sayHello() {
    print('你好,我是 $name');
  }
}

我个人比较喜欢用 final 来声明不可变字段。为什么?因为不可变对象在多线程环境下更安全,而且代码意图更清晰。你想想看,一个字段如果到处都能改,调试起来多头疼。

继承和混入(Mixin)是 Dart 里两个重要的概念。继承是「is-a」关系,混入是「has-a」关系。我建议你多用混入,少用多层继承。多层继承容易导致代码僵化,混入则灵活得多。

mixin Flyable {
  void fly() => print('飞行中...');
}

class Bird with Flyable {}

void main() {
  Bird().fly(); // 飞行中...
}
核心观点: 面向对象不是让你堆砌类,而是让你用封装、继承、多态来管理复杂度。我在重构一个老项目时,把 2000 行的 God Class 拆成了 5 个职责单一的小类,Bug 率直接降了 60%。

2.4 异步编程

Flutter 是单线程模型,但异步编程无处不在。网络请求、文件读写、数据库操作,全是异步的。Dart 用 FutureStream 来处理异步。

先看 Future

Future<String> fetchData() async {
  await Future.delayed(Duration(seconds: 2));
  return '数据加载完成';
}

void main() async {
  print('开始加载...');
  var data = await fetchData();
  print(data);
}

这里有个关键点:await 只能在 async 函数里使用。如果你在普通函数里用了 await,编译器会直接报错。嗯,这个错误我见过无数次了。

再说 Stream。Stream 和 Future 的区别是什么?Future 只返回一个值,Stream 可以返回多个值。比如你监听用户输入、监听文件变化,用 Stream 就很合适。

Stream<int> countStream() async* {
  for (int i = 1; i <= 5; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i;
  }
}

void main() async {
  await for (var value in countStream()) {
    print(value);
  }
}
避坑指南: 我曾经在项目里忘记取消 Stream 订阅,导致页面销毁后还在接收数据,引发了内存泄漏。记住:在 StatefulWidgetdispose 方法里一定要取消订阅。

异步编程的另一个常见场景是并发。Dart 提供了 Future.wait 来并行执行多个异步任务:

Future<void> main() async {
  var results = await Future.wait([
    fetchUser(),
    fetchPosts(),
    fetchComments(),
  ]);
  // 三个请求并行执行,全部完成后才继续
}

这个模式在首页数据加载时特别有用。你想想看,如果串行请求三个接口,用户得等三倍的时间。并行的话,只等最慢的那个。

个人经验: 我一般会在项目里封装一个 AsyncHelper 工具类,统一处理超时、重试、错误转换。这样业务代码里就不用重复写 try-catch 了,代码干净很多。

好了,Dart 基础的四个核心部分就讲到这里。变量与数据类型是基本功,函数与闭包是灵活性的体现,面向对象是组织代码的骨架,异步编程是 Flutter 的灵魂。把这四个吃透了,你写 Flutter 代码会顺手很多。

下一章我们开始搭建项目目录结构,到时候我会分享我踩过的坑和总结的最佳实践。