开篇:为什么选 Dart?
Dart 是 Google 于 2011 年发布的编程语言,最初定位为替代 JavaScript 的 Web 语言。虽然这一目标未能实现,但 Dart 凭借简洁的语法、高效的 JIT/AOT 编译能力以及出色的开发工具链,在 2017 年随着 Flutter 的推出迎来了新生。如今,Dart 不仅是 Flutter 框架的唯一开发语言,更通过其现代化的类型系统和异步模型,成为移动、Web、桌面跨平台开发的重要工具。
与其他语言相比,Dart 的设计取各家之长:语法接近 TypeScript 和 Java,空安全借鉴了 Kotlin 和 Swift,异步模型参考了 C# 的 async/await。对于前端开发者,Dart 的学习曲线相当平缓;对于原生 Android/iOS 开发者,Dart 的类型系统和面向对象特性也相当熟悉。
一、核心语法速览
1.1 变量声明
Dart 支持显式类型和类型推断两种方式:
// 显式类型声明
String name = 'Flutter';
int count = 42;
double price = 19.99;
bool isActive = true;
// 类型推断(使用 var)
var inferredName = 'Dart'; // 推断为 String
var inferredCount = 100; // 推断为 int
// final:运行时常量,只能赋值一次
final currentTime = DateTime.now();
// const:编译时常量,必须能在编译期确定值
const pi = 3.14159;
const appName = 'MyApp';
final 和 const 的区别至关重要:final 在运行时确定值,适合接收 API 返回的数据;const 在编译期确定值,适合定义配置项和 UI 常量。
// const List 是深度不可变的
const numbers = [1, 2, 3]; // List<int>
// numbers.add(4); // ❌ 编译错误
// final List 可以修改内容
final items = ['a', 'b'];
items.add('c'); // ✅ 可以
一句话总结:
var用于类型推断,final运行时常量,const编译时常量——三者覆盖了变量声明的全部场景。
1.2 空安全(Null Safety)
Dart 2.12 引入的空安全是其最重要的语言特性之一。核心原则是默认非空:
// 非空类型:默认不能为 null
String name = 'Alice';
// name = null; // ❌ 编译错误
// 可空类型:使用 ? 后缀标记
String? nullableName = 'Bob';
nullableName = null; // ✅ 可以
// 延迟初始化:使用 late
late String description;
void setup() {
description = 'Initialized later';
}
空安全引入了两个关键运算符:
// ?. 空感知属性访问
String? email;
int? length = email?.length; // email 为 null 时返回 null
// ?? 空合并运算符
String displayName = name ?? 'Anonymous';
// ??= 空感知赋值
String? cache;
cache ??= 'default value'; // 仅当 cache 为 null 时赋值
// ! 空断言运算符(慎用)
String confirmed = nullableName!; // 断言不为 null,运行时若为 null 则抛异常
一句话总结:空安全通过编译期检查消除了 NullPointerException,是 Dart 走向生产级语言的关键里程碑。
1.3 字符串与插值
Dart 的字符串支持单引号、双引号和多行字符串:
// 字符串插值
var user = 'Alice';
var greeting = 'Hello, $user!'; // Hello, Alice!
var calculation = '2 + 3 = ${2 + 3}'; // 2 + 3 = 5
// 多行字符串
var sqlQuery = '''
SELECT * FROM users
WHERE status = 'active'
ORDER BY created_at DESC
''';
// 原始字符串(不转义)
var path = r'C:\Users\name\file.txt';
一句话总结:Dart 字符串插值语法简洁直观,多行和原始字符串减少了转义字符的冗余。
二、函数与高阶函数
2.1 函数声明与参数
// 基本函数
int add(int a, int b) {
return a + b;
}
// 箭头函数(单行表达式)
int multiply(int a, int b) => a * b;
// 命名参数(推荐,增加可读性)
void createUser({
required String name,
required int age,
String? email,
bool isActive = true,
}) {
print('Creating user: $name, $age, $email, $isActive');
}
// 位置可选参数(用 [] 包裹)
void log(String message, [String level = 'INFO', DateTime? timestamp]) {
print('[${timestamp ?? DateTime.now()}] $level: $message');
}
// 调用
void main() {
createUser(name: 'Alice', age: 30);
createUser(name: 'Bob', age: 25, email: 'bob@example.com');
log('Start');
log('Error', 'ERROR');
}
Dart 推荐优先使用命名参数({}),尤其是对于参数较多的函数。required 关键字表示必须传入。
一句话总结:命名参数使函数调用像配置对象一样清晰,是 Dart 代码可读性的重要保障。
2.2 高阶函数与闭包
// 函数作为参数
void processItems<T>(List<T> items, void Function(T) action) {
for (var item in items) {
action(item);
}
}
// 函数作为返回值
Function(int) makeMultiplier(int factor) {
return (int value) => value * factor;
}
// 闭包捕获外部变量
void main() {
var counter = 0;
var increment = () => counter++;
increment();
increment();
print(counter); // 2
// 集合高阶函数
var numbers = [1, 2, 3, 4, 5];
var doubled = numbers.map((n) => n * 2).toList();
var evens = numbers.where((n) => n.isEven).toList();
var sum = numbers.reduce((a, b) => a + b);
print(doubled); // [2, 4, 6, 8, 10]
print(evens); // [2, 4]
print(sum); // 15
}
一句话总结:高阶函数让 Dart 具备了函数式编程的能力,集合操作如
map、where、reduce大幅提升了代码简洁度。
2.3 typedef 与函数类型
// 定义函数类型别名
typedef JsonMap = Map<String, dynamic>;
typedef Validator<T> = String? Function(T value);
// 使用
class FormField<T> {
final String label;
final T value;
final List<Validator<T>> validators;
FormField(this.label, this.value, this.validators);
List<String> validate() {
return validators
.map((v) => v(value))
.where((error) => error != null)
.cast<String>()
.toList();
}
}
// 实际验证函数
String? validateEmail(String? value) {
if (value == null || value.isEmpty) return '邮箱不能为空';
if (!value.contains('@')) return '邮箱格式不正确';
return null;
}
String? validateMinLength(String value, int min) {
if (value.length < min) return '至少需要 $min 个字符';
return null;
}
一句话总结:typedef 不仅为函数类型提供语义化命名,还可用于简化复杂类型的声明。
三、面向对象编程
3.1 类与构造函数
class User {
final String id;
final String name;
final String? email;
final DateTime createdAt;
// 主构造函数
User({
required this.id,
required this.name,
this.email,
DateTime? createdAt,
}) : createdAt = createdAt ?? DateTime.now();
// 命名构造函数
User.anonymous() : this(id: 'anon', name: '匿名用户');
User.fromJson(Map<String, dynamic> json)
: id = json['id'] as String,
name = json['name'] as String,
email = json['email'] as String?,
createdAt = DateTime.parse(json['createdAt'] as String);
// 工厂构造函数(可返回缓存实例或子类)
factory User.cached(String id) {
return _cache.putIfAbsent(id, () => User(id: id, name: 'User_$id'));
}
static final Map<String, User> _cache = {};
// 方法
String get displayName => name.isNotEmpty ? name : '未命名';
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'email': email,
'createdAt': createdAt.toIso8601String(),
};
// 重写操作符
@override
bool operator ==(Object other) =>
identical(this, other) || other is User && other.id == id;
@override
int get hashCode => id.hashCode;
@override
String toString() => 'User(id: $id, name: $name)';
}
构造函数初始化列表(: createdAt = ...)在构造函数体执行之前运行,适合初始化 final 字段和进行参数校验。
一句话总结:Dart 的构造函数系统丰富而灵活,主构造函数、命名构造函数、工厂构造函数覆盖了各种对象创建场景。
3.2 混入(Mixin)
Mixin 是 Dart 解决多重继承限制的优雅方案——它允许在不形成继承链的情况下复用代码:
// 定义 Mixin
mixin Loggable {
void log(String message) {
print('[${DateTime.now()}] $message');
}
void logError(String error) {
log('ERROR: $error');
}
}
mixin Serializable {
Map<String, dynamic> toJson();
String toJsonString() => jsonEncode(toJson());
}
// 使用 Mixin
class Product with Loggable, Serializable {
final String id;
final String name;
final double price;
Product({required this.id, required this.name, required this.price});
@override
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'price': price,
};
}
// 限制 Mixin 的使用范围(仅用于特定类或接口)
mixin Comparable<T> on Object {
bool compareTo(T other);
}
Mixin 与继承的区别:继承表达"是一个"的关系,Mixin 表达"能做某事"的能力。
一句话总结:Mixin 让 Dart 在单继承的限制下实现了代码的横向复用,是构建可组合行为的关键工具。
3.3 扩展方法(Extension)
Dart 2.7 引入的扩展方法允许为已有类型添加新功能:
// 为 String 添加扩展
extension StringExtensions on String {
String get reversed => split('').reversed.join('');
String truncate(int maxLength, {String suffix = '...'}) {
if (length <= maxLength) return this;
return substring(0, maxLength - suffix.length) + suffix;
}
bool get isValidEmail {
final regex = RegExp(r'^[\w.-]+@[\w.-]+\.\w+$');
return regex.hasMatch(this);
}
}
// 为 List 添加扩展
extension ListExtensions<T> on List<T> {
T? get firstOrNull => isEmpty ? null : first;
T? firstWhereOrNull(bool Function(T) test) {
for (var element in this) {
if (test(element)) return element;
}
return null;
}
List<T> distinct() => {...this}.toList();
}
// 为 nullable 类型添加扩展
extension NullableExtensions<T> on T? {
R? let<R>(R Function(T) transform) {
if (this == null) return null;
return transform(this!);
}
T orDefault(T defaultValue) => this ?? defaultValue;
}
// 使用
void main() {
print('Flutter'.reversed); // rettulF
print('Hello, World!'.truncate(8)); // Hello...
print('test@example.com'.isValidEmail); // true
var list = [1, 2, 2, 3, 3, 3];
print(list.distinct()); // [1, 2, 3]
String? maybeName;
print(maybeName.orDefault('默认名称')); // 默认名称
print(maybeName?.let((n) => n.toUpperCase())); // null
}
一句话总结:扩展方法让开发者能为标准库和第三方类型注入定制行为,极大提升了代码表达力和可读性。
3.4 抽象类与接口
Dart 中没有 interface 关键字——每个类都隐式定义了一个接口:
// 抽象类定义接口契约
abstract class Repository<T> {
Future<T?> getById(String id);
Future<List<T>> getAll();
Future<void> save(T item);
Future<void> delete(String id);
}
// 实现接口(可以同时实现多个接口)
class UserRepository implements Repository<User> {
final List<User> _users = [];
@override
Future<User?> getById(String id) async {
return _users.firstWhereOrNull((u) => u.id == id);
}
@override
Future<List<User>> getAll() async => List.unmodifiable(_users);
@override
Future<void> save(User item) async {
_users.removeWhere((u) => u.id == item.id);
_users.add(item);
}
@override
Future<void> delete(String id) async {
_users.removeWhere((u) => u.id == id);
}
}
// 枚举增强(Dart 2.17+)
enum TaskStatus {
pending('待处理', Icons.hourglass_empty),
inProgress('进行中', Icons.autorenew),
completed('已完成', Icons.check_circle),
cancelled('已取消', Icons.cancel);
final String label;
final IconData icon;
const TaskStatus(this.label, this.icon);
}
一句话总结:Dart 独特的"类即接口"设计让类型契约的表达既简洁又强大,枚举增强更是将 Dart 推向现代语言的行列。
四、集合与数据操作
4.1 三大核心集合
// List(有序,可重复)
var fruits = ['apple', 'banana', 'orange'];
fruits.add('grape');
fruits.insert(0, 'mango');
var first = fruits[0];
// Map(键值对)
var scores = {
'Alice': 95,
'Bob': 87,
'Charlie': 92,
};
scores['David'] = 88;
var aliceScore = scores['Alice']; // 95
// Set(无序,唯一)
var tags = {'flutter', 'dart', 'mobile'};
tags.add('flutter'); // 已存在,不重复
var allTags = tags.union({'web', 'desktop'});
4.2 集合运算符
// Spread 展开
var list1 = [1, 2, 3];
var list2 = [0, ...list1, 4, 5]; // [0, 1, 2, 3, 4, 5]
// Null-aware Spread
var nullable = null;
var list3 = [0, ...?nullable, 4]; // [0, 4]
// Collection If(条件集合)
var isAdmin = true;
var permissions = [
'read',
'write',
if (isAdmin) 'delete',
if (isAdmin) 'manage',
];
// Collection For(推导式)
var numbers = [1, 2, 3];
var doubled = [
for (var n in numbers) n * 2,
]; // [2, 4, 6]
// 模式匹配(Dart 3.0+)
var record = (1, 'one');
var (id, name) = record;
一句话总结:Dart 的集合运算符(展开、条件、推导)让集合构造从命令式变为声明式,代码更加紧凑易读。
五、异步编程
5.1 Future 与 async/await
// 基本异步操作
Future<String> fetchUserData(String userId) async {
await Future.delayed(Duration(seconds: 1)); // 模拟网络请求
return 'User data for $userId';
}
// 并行执行多个 Future
Future<List<String>> fetchMultipleUsers(List<String> ids) async {
var futures = ids.map((id) => fetchUserData(id));
return await Future.wait(futures);
}
// 错误处理
try {
var result = await fetchUserData('invalid-id');
} on NetworkException catch (e) {
print('网络错误: ${e.message}');
} on FormatException catch (e) {
print('格式错误: ${e.message}');
} catch (e) {
print('未知错误: $e');
} finally {
print('清理资源');
}
// 超时控制
var result = await fetchUserData('123').timeout(
Duration(seconds: 5),
onTimeout: () => '超时默认数据',
);
一句话总结:async/await 让异步代码像同步代码一样阅读和编写,错误处理也遵循熟悉的 try/catch 模式。
5.2 Stream 流式编程
import 'dart:async';
// 创建 Stream
Stream<int> countStream(int max) async* {
for (var i = 1; i <= max; i++) {
await Future.delayed(Duration(milliseconds: 100));
yield i; // 产生值
}
}
// 使用 Stream
void main() async {
await for (var count in countStream(5)) {
print(count); // 1, 2, 3, 4, 5(每 100ms)
}
// 流变换
var stream = countStream(10);
await stream
.where((n) => n.isEven)
.map((n) => n * 2)
.listen((data) => print(data)); // 4, 8, 12, 16, 20
// StreamController(手动控制)
var controller = StreamController<String>.broadcast();
controller.stream.listen(
(data) => print('收到: $data'),
onError: (e) => print('错误: $e'),
onDone: () => print('流已关闭'),
);
controller.add('消息 1');
controller.add('消息 2');
await controller.close();
}
Broadcast Stream 允许多个订阅者,适合事件总线场景:
class EventBus {
final _controller = StreamController<AppEvent>.broadcast();
Stream<AppEvent> get stream => _controller.stream;
void emit(AppEvent event) => _controller.add(event);
void dispose() => _controller.close();
}
sealed class AppEvent {}
class UserLoggedIn extends AppEvent {}
class SettingsChanged extends AppEvent {}
一句话总结:Stream 让 Dart 具备了响应式编程的能力,配合 async* / yield 可以优雅地处理异步数据序列。
六、异常处理
// 自定义异常
class ValidationException implements Exception {
final String field;
final String message;
ValidationException(this.field, this.message);
@override
String toString() => 'ValidationException($field): $message';
}
class NetworkException implements Exception {
final int statusCode;
final String? body;
NetworkException(this.statusCode, {this.body});
}
// 异常转换
try {
await http.get(url);
} on SocketException {
throw NetworkException(0); // 无网络
} on FormatException catch (e) {
throw ValidationException('response', '响应格式错误: ${e.message}');
} on HttpException catch (e) {
throw NetworkException(e.uri.hashCode);
}
// Result 类型(函数式错误处理)
sealed class Result<T, E> {}
class Success<T, E> extends Result<T, E> {
final T value;
Success(this.value);
}
class Failure<T, E> extends Result<T, E> {
final E error;
Failure(this.error);
}
Result<int, String> divide(int a, int b) {
if (b == 0) return Failure('不能除以零');
return Success(a ~/ b);
}
一句话总结:Dart 的异常处理继承 Java 风格,配合自定义 Result 类型可实现函数式错误处理。
七、元数据与代码生成
// 常用注解
@deprecated
void oldMethod() {}
@override
String toString() => '...';
// 自定义注解
class ApiEndpoint {
final String path;
final String method;
const ApiEndpoint(this.path, {this.method = 'GET'});
}
@ApiEndpoint('/users', method: 'GET')
Future<List<User>> getUsers() async {
// ...
}
// 代码生成器(如 freezed, json_serializable 使用 build_runner)
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart'; // 生成文件
@JsonSerializable()
class UserModel {
final String id;
final String name;
UserModel({required this.id, required this.name});
factory UserModel.fromJson(Map<String, dynamic> json) =>
_$UserModelFromJson(json);
Map<String, dynamic> toJson() => _$UserModelToJson(this);
}
一句话总结:元数据注解配合 build_runner 的代码生成,让 Dart 开发在保持类型安全的同时避免了大量样板代码。
八、Dart 与 JavaScript 互操作
// dart:js_interop(Dart 3.2+)
import 'dart:js_interop';
// 调用 JS 函数
@JS('console.log')
external void consoleLog(JSString message);
// 暴露 Dart 函数给 JS
@JSExport()
class FlutterBridge {
void sendMessage(String message) {
print('来自 JS: $message');
}
}
// 原有 js 包(pkg:js)
@JS('JSON.parse')
external dynamic jsonParse(String text);
一句话总结:js_interop 为 Flutter Web 和其他需要 JS 互操作的场景提供了类型安全的桥接方案。
FAQ
Q1: Dart 的空安全可以关闭吗?
不可以。从 Dart 3.0 开始,空安全是强制特性。所有新代码都必须在空安全环境下运行。迁移旧项目时,需要逐步将 ? 标记添加到可能为 null 的类型上。
Q2: final 和 const 到底用哪个?
- 值在编译期确定(如配置常量、颜色值、字号)→ 用
const - 值在运行时确定(如 API 返回、当前时间、用户输入)→ 用
final const对象可以共享实例,更省内存;final更灵活。
Q3: Dart 是单线程的,如何执行密集计算?
Dart 是事件循环单线程模型(类似 JavaScript),但可以通过 Isolate 实现真正的并行计算。对于耗时任务,使用 await compute(function, data) 最为方便,它自动在后台 Isolate 运行并在完成后返回结果。
Q4: 命名参数和位置参数怎么选?
参数 ≤3 个且无歧义 → 位置参数;参数 >3 个或有多个同类型参数 → 命名参数。Flutter Widget 的构造函数普遍使用命名参数,因为它让代码像标记语言一样清晰。
Q5: == 和 identical() 有什么区别?
== 是值相等(可通过 @override 自定义),identical() 是引用相等(内存地址相同)。对于构造函数中使用 const 创建的对象,Dart 会自动缓存实例,此时 identical(a, b) 和 a == b 都为 true。
Q6: Dart 的泛型和 Java 的有什么区别?
Dart 泛型在运行期会保留类型信息(reified generics),因此 List<int> is List<String> 在运行时会返回 false。而 Java 由于类型擦除,运行时无法区分 List<Integer> 和 List<String>。
Q7: 什么时候使用扩展方法,什么时候用 Mixin?
- 扩展现有类(如 String、List)的功能 → 扩展方法
- 复用一组方法的实现到多个类 → Mixin
- 需要访问
this并调用类自身方法 → Mixin
相关阅读
- https://plumephp.com/flutter-widgets-layout/ — Widget 体系与 Flutter 布局系统
- https://plumephp.com/flutter-state-management/ — Flutter 状态管理方案对比与实战
- https://plumephp.com/flutter-async-networking/ — Dart 在网络请求中的实际应用
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。