Dart: Dart 3 新特性
Dart 3 的三大特性 — 让你的代码从"能跑"升级为"优雅"。
1. 你将学到
- Records:轻量级匿名数据聚合,多返回值
- Pattern Matching:destructuring / if-case / switch-pattern / exhaustive check
- Sealed Classes:受限继承体系 + exhaustive switch 保证
- 三者协作:Sealed + Pattern + Records 的组合拳
- Bob 场景:DataPipeline 用 Sealed class 定义数据源类型,Pattern matching 解析结果
2. 一个开发者的真实故事
(1) 痛点:类型层次过深与冗长的 if-else
Bob 的 DataPipeline 有 3 种数据源(API/File/Database),每种有不同的配置和连接方式。他用继承实现,但层次深、代码冗长。解析 API 响应时,大量 if-else 检查状态码和返回结构,5 种响应类型写了 200 行判断代码。
(2) Dart 3 新特性的解法
Sealed Classes 限制数据源类型,Pattern Matching 优雅处理各种响应,Records 轻量返回多个值。
// Sealed class: exhaustive data source types
sealed class DataSource {}
class ApiSource extends DataSource { final String endpoint; ApiSource(this.endpoint); }
class FileSource extends DataSource { final String path; FileSource(this.path); }
// Pattern matching on sealed class - compiler guarantees all cases handled
String describe(DataSource source) => switch (source) {
ApiSource(:final endpoint) => 'API: $endpoint',
FileSource(:final path) => 'File: $path',
};
// Records: return multiple values
(String, double) parseResponse(String raw) => ('ORD-001', 1500.0);
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
(3) 收益
- Sealed Classes 让类型分支有限可枚举,穷尽 switch 保证零遗漏
- Pattern Matching 让 if-else 链缩减为声明式的 switch 表达式
- Records 替代小型类,多返回值不再需要 Tuple 库或 Map
3. Records
(1) Records 基础
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:Records 创建与使用
void main() {
// Positional record
var order = ('ORD-001', 1500.0, 'completed');
print(order.$1); // ORD-001
print(order.$2); // 1500.0
print(order.$3); // completed
// Named record (preferred for readability)
var order2 = (id: 'ORD-002', amount: 3200.0, status: 'pending');
print(order2.id); // ORD-002
print(order2.amount); // 3200.0
print(order2.status); // pending
// Mixed positional and named
var mixed = ('ORD-003', 890.0, category: 'Electronics');
print(mixed.$1); // ORD-003
print(mixed.category); // Electronics
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:Records 作为函数返回值
// Multiple return values with records
({String id, double amount, double tax}) calculateOrder(double amount, double taxRate) {
return (
id: 'ORD-${DateTime.now().millisecondsSinceEpoch}',
amount: amount,
tax: amount * taxRate,
);
}
// Positional record for quick grouping
(String, double) parseAmount(String input) {
final parts = input.split(':');
return (parts[0], double.parse(parts[1]));
}
void main() {
final order = calculateOrder(1500.0, 0.08);
print('ID: ${order.id}, Amount: \$${order.amount}, Tax: \$${order.tax}');
final (label, value) = parseAmount('Revenue:52500.75');
print('$label: \$$value USD');
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
| Records 特性 | 语法 | 说明 |
|---|---|---|
| 位置字段 | .$1, .$2 |
按位置访问 |
| 命名字段 | .name |
按名称访问 |
| 类型标注 | (String, int) 或 ({String name, int count}) |
明确类型 |
| 相等性 | 值相等 | 字段值相同即相等 |
4. Pattern Matching
(1) 模式匹配类型
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:变量模式与解构
void main() {
// Variable pattern - extract values
var (id, amount, status) = ('ORD-001', 1500.0, 'completed');
print('ID: $id, Amount: $amount, Status: $status');
// Record destructuring with named fields
var (:id, :amount, :status) = (id: 'ORD-002', amount: 3200.0, status: 'pending');
print('ID: $id, Amount: $amount, Status: $status');
// List destructuring
var [first, second, ...rest] = [1, 2, 3, 4, 5];
print('First: $first, Second: $second, Rest: $rest');
// Map destructuring
var {'id': orderId, 'amount': orderAmount} = {'id': 'ORD-003', 'amount': 890.0};
print('Order: $orderId, Amount: $orderAmount');
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:if-case 模式
void main() {
Object value = '1,500.00 USD';
// if-case pattern matching
if (value case String s when s.contains('USD')) {
print('USD value: $s');
}
// Pattern matching on record
var response = (statusCode: 200, body: '{"orders": 1500}');
if (response case (statusCode: 200, :var body)) {
print('Success response: $body');
}
// Pattern matching on list
var orders = ['ORD-001', 'ORD-002', 'ORD-003'];
if (orders case [var first, var second, ...]) {
print('First two: $first, $second');
}
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:switch 模式
String classifyOrder(Object value) => switch (value) {
// Type pattern
int i when i > 1000 => 'High value integer: $i',
int i => 'Low value integer: $i',
double d when d >= 1000 => 'Premium: \$${d.toStringAsFixed(2)}',
double d => 'Standard: \$${d.toStringAsFixed(2)}',
String s => 'String: $s',
List l when l.length > 100 => 'Large batch: ${l.length} items',
List l => 'Small batch: ${l.length} items',
_ => 'Unknown type',
};
// Pattern matching with records
String describeResponse((int, String) response) => switch (response) {
(200, var body) => 'OK: $body',
(404, _) => 'Not Found',
(500, var msg) => 'Server Error: $msg',
(>= 400, var msg) => 'Client Error: $msg',
_ => 'Unknown response',
};
void main() {
print(classifyOrder(1500)); // High value integer: 1500
print(classifyOrder(890.0)); // Standard: $890.00
print(classifyOrder([1, 2, 3])); // Small batch: 3 items
print(describeResponse((200, 'OK'))); // OK: OK
print(describeResponse((404, 'Missing'))); // Not Found
print(describeResponse((500, 'Crash'))); // Server Error: Crash
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
| 模式类型 | 语法 | 用途 |
|---|---|---|
| 变量模式 | var x |
提取值 |
- 类型模式 |
Type x| 类型检查+提取 | | 常量模式 |42,'hello'| 精确匹配 | | 关系模式 |>= 1000| 范围判断 | | 逻辑模式 |a \|\| b,a && b| 组合条件 | | 守卫 |when condition| 额外条件 | | 通配符 |_| 忽略值 |
5. Sealed Classes
(1) Sealed Class 继承体系
classDiagram
class DataSource {
<<sealed>>
}
class ApiSource {
+String endpoint
+Map headers
}
class FileSource {
+String path
+Encoding encoding
}
class DatabaseSource {
+String connectionString
}
DataSource <|-- ApiSource
DataSource <|-- FileSource
DataSource <|-- DatabaseSource
note for DataSource "exhaustive switch guaranteed"
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:Sealed Class 定义与穷尽匹配
sealed class DataSource {
const DataSource();
}
class ApiSource extends DataSource {
final String endpoint;
final Map<String, String> headers;
const ApiSource(this.endpoint, {this.headers = const {}});
}
class FileSource extends DataSource {
final String path;
const FileSource(this.path);
}
class DatabaseSource extends DataSource {
final String connectionString;
const DatabaseSource(this.connectionString);
}
// Exhaustive switch - compiler checks all subtypes
String describeSource(DataSource source) => switch (source) {
ApiSource(:final endpoint, :final headers) =>
'API: $endpoint (${headers.length} headers)',
FileSource(:final path) =>
'File: $path',
DatabaseSource(:final connectionString) =>
'Database: $connectionString',
};
// If you add a new subtype and forget to update switch,
// the compiler will report an error!
void main() {
final sources = ``<DataSource>``[
ApiSource('https://api.example.com/orders', headers: {'Authorization': 'Bearer token'}),
FileSource('/data/orders.csv'),
DatabaseSource('postgresql://localhost:5432/orders'),
];
for (final source in sources) {
print(describeSource(source));
}
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
| Sealed Class 特性 | 说明 |
|---|---|
| 子类限制 | 必须在同一库内定义 |
| 穷尽检查 | switch 必须覆盖所有子类 |
| 编译时保证 | 新增子类时,遗漏的 switch 会编译报错 |
| 不能被实例化 | 本身是抽象的 |
6. 三者协作
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:Sealed + Pattern + Records 组合
// Sealed class for API results
sealed class ApiResult``<T>`` {
const ApiResult();
}
class Success``<T>`` extends ApiResult``<T>`` {
final T data;
final (int, String) metadata; // Record: (statusCode, message)
const Success(this.data, this.metadata);
}
class ApiError``<T>`` extends ApiResult``<T>`` {
final String message;
final int? statusCode;
const ApiError(this.message, {this.statusCode});
}
class NetworkError``<T>`` extends ApiResult``<T>`` {
final String reason;
const NetworkError(this.reason);
}
// Pattern matching with destructuring
String handleResult(ApiResult<List``<String>``> result) => switch (result) {
Success(:final data, metadata: (200, final msg)) =>
'OK ($msg): ${data.length} items loaded',
Success(:final data, metadata: (final code, _)) =>
'Loaded with status $code: ${data.length} items',
ApiError(:final message, statusCode: final code?) =>
'API Error [$code]: $message',
ApiError(:final message) =>
'API Error: $message',
NetworkError(:final reason) =>
'Network Error: $reason',
};
void main() {
final results = <ApiResult<List``<String>``>>[
Success(['ORD-001', 'ORD-002'], (200, 'OK')),
Success(['ORD-003'], (206, 'Partial Content')),
ApiError('Rate limit exceeded', statusCode: 429),
ApiError('Unknown error'),
NetworkError('Connection timeout'),
];
for (final result in results) {
print(handleResult(result));
}
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
7. Bob 场景:DataPipeline 数据源与结果解析
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:完整的数据源抽象
sealed class DataSource {
const DataSource();
String get displayName;
}
class ApiSource extends DataSource {
final String endpoint;
final Duration timeout;
const ApiSource(this.endpoint, {this.timeout = const Duration(seconds: 10)});
@override
String get displayName => 'API ($endpoint)';
}
class FileSource extends DataSource {
final String path;
final String format;
const FileSource(this.path, {this.format = 'csv'});
@override
String get displayName => 'File ($path, $format)';
}
class DatabaseSource extends DataSource {
final String connectionString;
final String query;
const DatabaseSource(this.connectionString, {this.query = 'SELECT * FROM orders'});
@override
String get displayName => 'Database ($connectionString)';
}
// Processing result with Records
typedef ProcessingResult = ({int processed, int skipped, double revenue, Duration time});
ProcessingResult processDataSource(DataSource source) => switch (source) {
ApiSource(:final endpoint, :final timeout) => (
processed: 50000,
skipped: 120,
revenue: 525000.0,
time: timeout,
),
FileSource(:final path, :final format) => (
processed: 100000,
skipped: 350,
revenue: 1200000.0,
time: Duration(seconds: 5),
),
DatabaseSource(:final connectionString, :final query) => (
processed: 1200000,
skipped: 800,
revenue: 15000000.0,
time: Duration(seconds: 15),
),
};
void main() {
final sources = ``<DataSource>``[
ApiSource('https://api.example.com/orders'),
FileSource('/data/orders.csv'),
DatabaseSource('postgresql://localhost:5432/orders'),
];
print('=== DataPipeline Source Analysis ===');
for (final source in sources) {
final (:processed, :skipped, :revenue, :time) = processDataSource(source);
print('\n${source.displayName}:');
print(' Processed: ${processed} orders');
print(' Skipped: $skipped records');
print(' Revenue: \$${revenue.toStringAsFixed(2)} USD');
print(' Time: ${time.inSeconds}s');
}
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
8. 完整示例:DataPipeline 响应处理系统
// ============================================
// DataPipeline Response Processing System
// Sealed classes + Pattern matching + Records
// ============================================
// Sealed class for different response types
sealed class ApiResponse {
const ApiResponse();
}
class SuccessResponse extends ApiResponse {
final int statusCode;
final Map<String, dynamic> data;
const SuccessResponse(this.statusCode, this.data);
}
class ErrorResponse extends ApiResponse {
final int statusCode;
final String message;
final String? details;
const ErrorResponse(this.statusCode, this.message, {this.details});
}
class TimeoutResponse extends ApiResponse {
final Duration timeout;
final String endpoint;
const TimeoutResponse(this.timeout, this.endpoint);
}
class RedirectResponse extends ApiResponse {
final String newEndpoint;
final int statusCode;
const RedirectResponse(this.newEndpoint, this.statusCode);
}
// Processing outcome as a Record
typedef Outcome = ({bool success, String message, double? revenue});
// Pattern matching on all response types - exhaustive!
Outcome handleResponse(ApiResponse response) => switch (response) {
SuccessResponse(statusCode: 200, :final data) when data.containsKey('orders') => (
success: true,
message: 'Loaded ${data['orders']} orders',
revenue: (data['revenue'] as num?)?.toDouble(),
),
SuccessResponse(statusCode: 200, :final data) => (
success: true,
message: 'OK but no orders field: ${data.keys}',
revenue: null,
),
SuccessResponse(statusCode: 206, :final data) => (
success: true,
message: 'Partial data: ${data.length} fields',
revenue: null,
),
SuccessResponse(statusCode: final code) => (
success: true,
message: 'Unexpected success code: $code',
revenue: null,
),
ErrorResponse(statusCode: 429, :final message) => (
success: false,
message: 'Rate limited: $message',
revenue: null,
),
ErrorResponse(statusCode: final code, :final message, :final details?) => (
success: false,
message: 'Error [$code]: $message - $details',
revenue: null,
),
ErrorResponse(statusCode: final code, :final message) => (
success: false,
message: 'Error [$code]: $message',
revenue: null,
),
TimeoutResponse(:final timeout, :final endpoint) => (
success: false,
message: 'Timeout after ${timeout.inSeconds}s on $endpoint',
revenue: null,
),
RedirectResponse(:final newEndpoint, :final statusCode) => (
success: false,
message: 'Redirect ($statusCode) to $newEndpoint',
revenue: null,
),
};
void main() {
final responses = ``<ApiResponse>``[
SuccessResponse(200, {'orders': 50000, 'revenue': 525000.0}),
SuccessResponse(200, {'products': 3000}),
SuccessResponse(206, {'partial': true}),
ErrorResponse(429, 'Rate limit exceeded'),
ErrorResponse(500, 'Internal server error', details: 'Database connection lost'),
ErrorResponse(404, 'Not found'),
TimeoutResponse(const Duration(seconds: 30), '/api/v1/orders'),
RedirectResponse('/api/v2/orders', 301),
];
print('=== DataPipeline Response Handler ===\n');
for (final response in responses) {
final (:success, :message, :revenue) = handleResponse(response);
final status = success ? 'OK' : 'FAIL';
final rev = revenue != null ? ' (\$${revenue.toStringAsFixed(2)} USD)' : '';
print('[$status] $message$rev');
}
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
输出:
=== DataPipeline Response Handler ===
[OK] Loaded 50000 orders ($525000.00 USD)
[OK] OK but no orders field: (products)
[OK] Partial data: 1 fields
[FAIL] Rate limited: Rate limit exceeded
[FAIL] Error [500]: Internal server error - Database connection lost
[FAIL] Error [404]: Not found
[FAIL] Timeout after 30s on /api/v1/orders
[FAIL] Redirect (301) to /api/v2/orders
❓ 常见问题
Q:Records 和 Class 有什么区别? A:Records 是匿名的、基于值的轻量数据结构;Class 有名字、基于引用、可以有方法。Records 适合简单数据传递,Class 适合复杂业务逻辑。
Q:Sealed class 和 abstract class 有什么区别? A:Sealed class 限制子类在同一库内,编译器可穷尽检查;abstract class 的子类可在任何地方定义,无法穷尽。需要穷尽保证时用 sealed。
Q:Pattern matching 可以用在哪些地方? A:switch 表达式、switch 语句、if-case、变量声明、for-in 循环。Dart 3 处处皆模式。
Q:Records 的性能如何? A:Records 编译为普通对象,性能与小型类相当。没有额外开销,但也不比类更快。选择基于可读性。
Q:sealed class 可以有构造函数吗? A:可以有工厂构造函数(返回子类),但自身不能被实例化(隐式抽象)。构造函数主要用于子类共享初始化逻辑。
Q:switch 表达式中
_和default有区别吗? A:功能相同,都是兜底匹配。Dart 3 推荐用_(通配符模式),更简洁。default是旧语法保留。
Q:Records 可以作为 Map 的 key 吗? A:可以。Records 基于值相等,自动实现了 == 和 hashCode。
(1, 2) == (1, 2)为 true。
📖 小节
- Records 轻量级匿名数据聚合,支持位置和命名字段,值语义相等
- Pattern Matching 支持 if-case、switch-pattern、变量解构、守卫条件
- Sealed Classes 限制子类在同一库,编译器保证穷尽 switch
- 三者组合:Sealed 定义有限类型 → Pattern 匹配与解构 → Records 轻量返回值
- 这是 Dart 3 最核心的升级,让代码更安全、更简洁、更表达力强
📝 作业
- 基础题(难度⭐):定义两个 Records:一个位置记录
(String, double)表示订单 ID 和金额,一个命名记录({String id, double amount, String status}),创建实例并解构打印。 - 进阶题(难度⭐⭐):用 Sealed Class 定义 3 种支付方式(CreditCard/PayPal/BankTransfer),每种有不同字段。用 switch 表达式实现
describePayment()方法,确保穷尽性。 - 挑战题(难度⭐⭐⭐):设计一个完整的 API 响应处理系统:Sealed Class 定义响应类型(Success/ValidationError/ServerError/Timeout),每种携带不同的 Record 数据。用 Pattern Matching 实现响应处理器,包含关系模式和守卫条件。