大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
Dart 语言
这一课你将能做到
- 用 Dart 写变量、函数、类,理解
final/const/var/late的区别 - 熟练使用 List、Map、Set 以及函数式操作(map / where / fold)
- 写出正确的异步代码:Future、async/await、Stream,并理解空安全(
?/!/??)
为什么要学
Flutter 的 UI 全部由 Dart 写,Dart 不熟,写 Widget 就是抄。Dart 和 Java/Kotlin 很像,但有三个对 Flutter 至关重要的特性:空安全(编译期拦截 NPE)、async/await(IO 不卡 UI)、级联与函数式 API(写 UI 表达力强)。
人话:Dart 就是给 Flutter 量身定做的「安全版 JavaScript + 简化版 Java」。
先建立图像
强类型 var / final / const / late
面向对象 class / mixin / extension / interface
函数式 高阶函数 / map / where / 级联 ..
异步 Future + async/await + Stream
空安全 String vs String? 编译期强制区分
跟着做
不想装环境?打开 <https://dartpad.dev> 把每段代码粘进去点 Run。
步骤 1:变量与类型
void main() {
var name = 'Ada'; // 类型推断为 String
final now = DateTime.now(); // 运行期一次赋值,不可改
const pi = 3.14159; // 编译期常量
late String token; // 延迟初始化,使用前必须赋值
int age = 18;
double height = 1.72;
bool isAdmin = false;
print('$name $age $height $isAdmin $pi');
token = 'abc';
print(token);
}
要点:var 类型一旦确定不可变;final 是「运行期只读」;const 是「编译期常量」,能 const 就 const,Flutter 中性能更好。
步骤 2:函数
// 普通函数
int add(int a, int b) => a + b;
// 可选命名参数(Flutter 里到处是这种写法)
void greet({required String name, int age = 0}) {
print('hello $name, $age');
}
// 函数作为参数
void repeat(int times, void Function(int) action) {
for (var i = 0; i < times; i++) action(i);
}
void main() {
print(add(2, 3));
greet(name: 'Bob'); // age 用默认值
repeat(3, (i) => print('第 $i 次'));
}
步骤 3:集合与函数式
void main() {
var nums = [1, 2, 3, 4, 5];
var evens = nums.where((n) => n.isEven).map((n) => n * 10).toList();
print(evens); // [20, 40]
var sum = nums.fold(0, (a, b) => a + b);
print(sum); // 15
var user = {'name': 'Ada', 'age': 18};
print(user['name']);
var tags = <String>{'a', 'b', 'a'}; // Set 自动去重
print(tags); // {a, b}
// 集合里写控制流(Flutter 写 UI 巨好用)
var list = [
'start',
if (user['age'] == 18) '成年',
for (var i = 0; i < 3; i++) '项$i',
...nums.map((e) => 'n$e'),
];
print(list);
}
步骤 4:类、构造、命名构造、级联
class User {
final String name;
int age;
User(this.name, this.age);
// 命名构造
User.guest()
: name = 'guest',
age = 0;
void grow() => age++;
@override
String toString() => 'User($name, $age)';
}
class Point {
final int x, y;
const Point(this.x, this.y); // const 构造
}
void main() {
var u = User('Ada', 18)..grow()..grow(); // 级联
print(u); // User(Ada, 20)
print(User.guest());
const p1 = Point(1, 2);
const p2 = Point(1, 2);
print(identical(p1, p2)); // true,const 对象复用
}
步骤 5:空安全
void main() {
String a = 'ok';
// a = null; // 编译错:String 不能为 null
String? b = null; // 加 ? 才能为 null
print(b?.length); // 安全调用:null
print(b ?? 'default'); // 空则用默认
b = 'hi';
print(b.length); // 编译器已知非空,自动提升
String? c;
// print(c!.length); // 危险:null 时崩溃
if (c != null) print(c.length); // 推荐:判空
}
规则:默认非空,需要可空显式加 ?;尽量用 ?. ?? if (x != null),避免 !。
步骤 6:异步
Future<String> fetchUser() async {
await Future.delayed(const Duration(seconds: 1));
return 'Ada';
}
Stream<int> countdown(int from) async* {
for (var i = from; i > 0; i--) {
await Future.delayed(const Duration(milliseconds: 300));
yield i;
}
}
Future<void> main() async {
print('start');
var name = await fetchUser(); // 等待
print('got $name');
// 并发两个 Future
var both = await Future.wait([fetchUser(), fetchUser()]);
print(both);
// Stream:事件流
await for (var n in countdown(3)) {
print('倒计时 $n');
}
print('end');
}
讲清楚要点
finalvsconst:final 运行期一次赋值;const 编译期常量。Widget 加 const 能跳过 rebuild 优化性能。- 空安全在编译期救你:把 Java 的 NullPointerException 提前到编译错误,是 Dart 最大的安全红利。
late是延迟承诺:告诉编译器「我用之前一定赋值」。乱用会把空安全打破,能用构造初始化就别用 late。- Future 是「一次的结果」,Stream 是「多次的事件」:网络请求用 Future;WebSocket、键盘监听用 Stream。
- mixin 提供代码复用但不进入继承链:
class A extends B with C, D,常见如SingleTickerProviderStateMixin。
你可能会懵的地方
-
为什么 Flutter 代码到处是
const? const Widget 在 rebuild 时被复用,跳过 diff,性能更好。能用就加。 -
late和?有啥区别?late String s仍是非空,只是推迟赋值;String? s是真的可能为 null。 -
箭头函数
=>和{}区别?=> expr等价于{ return expr; },只能写一行表达式。 -
异步函数为啥必须
await? 不 await 就拿到 Future 本身而不是结果。Dart 是单线程事件循环,await 不阻塞线程,让出执行权。 -
为什么
const Point(1,2) == const Point(1,2)是同一对象? Dart 对 const 对象做规范化(canonicalization),相同字段的 const 实例共享内存。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
String s = null; | 编译错 | String? s = null; 或赋初值 |
网络请求 fetch() 不 await 直接用结果 | 拿到的是 Future | var r = await fetch(); |
滥用 ! 强制解包 | 运行期 crash | 用 ?. ?? 或判空 |
在构造函数外用 const 修饰可变字段 | 编译错 | const 类的所有字段必须 final |
for 循环里 await 多次请求 | 串行慢 | 用 Future.wait 并发 |
用 print 调试 Flutter | 日志被吞 | 用 debugPrint 或 log() |
卡点急救
The non-nullable variable 'x' must be initialized:字段没初始化。选late、构造函数必填、或加?。await用在非 async 函数:把函数改成Future<void> foo() async { ... }。type 'Null' is not a subtype of type 'String':某处把 null 当成 String 用。开 debugger 查堆栈,多半缺?.。- Stream 没数据:检查是否漏了
await for或listen;Stream 默认单订阅,二次监听要用.asBroadcastStream()。
面试常问
-
Dart 的 isolate 是什么? Dart 单线程 + 事件循环;isolate 是独立堆内存的并发单元,靠消息传递,不共享内存。CPU 密集任务用
compute()或Isolate.run。 -
Future 和 Stream 区别? Future 一次性返回单个结果或错误;Stream 是异步事件序列,可多次发射。
-
空安全怎么实现? 类型系统层把类型分为「非空 / 可空」两类,编译期做流程分析(definite assignment、promotion),禁止把 null 赋给非空。
-
mixin 和继承区别? mixin 不构造 is-a 关系,只是横向代码复用;可以避免多层继承混乱。
自测三问
final、const、late三者的差别和典型用法?- 如何把
[1,2,3,4,5]变成[4, 16](保留偶数并平方)?写一行代码。 - 为什么
String? name; print(name.length);编译不过?写两种修复方法。
小结一句话
Dart = 强类型 + 空安全 + 一等异步 + 函数式语法糖,掌握这些就掌握了写 Flutter 的全部语言基础。
延伸
- Dart 官方语言之旅:<https://dart.dev/language>
- 在 DartPad 上把本文每段代码跑一遍:<https://dartpad.dev>
- 下一课:Widget 体系,看 Dart 类如何变成屏幕上的 UI。