Dart: Dart 3 新特性

Dart 3 的三大特性 — 让你的代码从"能跑"升级为"优雅"。

1. 你将学到


2. 一个开发者的真实故事

(1) 痛点:类型层次过深与冗长的 if-else

Bob 的 DataPipeline 有 3 种数据源(API/File/Database),每种有不同的配置和连接方式。他用继承实现,但层次深、代码冗长。解析 API 响应时,大量 if-else 检查状态码和返回结构,5 种响应类型写了 200 行判断代码。

(2) Dart 3 新特性的解法

Sealed Classes 限制数据源类型,Pattern Matching 优雅处理各种响应,Records 轻量返回多个值。

DART
// 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);
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

(3) 收益


3. Records

(1) Records 基础

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:Records 创建与使用

DART
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
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:Records 作为函数返回值

DART
// 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');
}
TEXT 📖 仅展示
> **输出:** 在本地 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) 模式匹配类型

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:变量模式与解构

DART
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');
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:if-case 模式

DART
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');
  }
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:switch 模式

DART
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
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
模式类型 语法 用途
变量模式 var x 提取值

5. Sealed Classes

(1) Sealed Class 继承体系

100%
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"
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:Sealed Class 定义与穷尽匹配

DART
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));
  }
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
Sealed Class 特性 说明
子类限制 必须在同一库内定义
穷尽检查 switch 必须覆盖所有子类
编译时保证 新增子类时,遗漏的 switch 会编译报错
不能被实例化 本身是抽象的

6. 三者协作

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:Sealed + Pattern + Records 组合

DART
// 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));
  }
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

7. Bob 场景:DataPipeline 数据源与结果解析

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:完整的数据源抽象

DART
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');
  }
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

8. 完整示例:DataPipeline 响应处理系统

DART
// ============================================
// 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');
  }
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

输出:

TEXT 📖 仅展示
=== 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。


📖 小节


📝 作业

  1. 基础题(难度⭐):定义两个 Records:一个位置记录 (String, double) 表示订单 ID 和金额,一个命名记录 ({String id, double amount, String status}),创建实例并解构打印。
  2. 进阶题(难度⭐⭐):用 Sealed Class 定义 3 种支付方式(CreditCard/PayPal/BankTransfer),每种有不同字段。用 switch 表达式实现 describePayment() 方法,确保穷尽性。
  3. 挑战题(难度⭐⭐⭐):设计一个完整的 API 响应处理系统:Sealed Class 定义响应类型(Success/ValidationError/ServerError/Timeout),每种携带不同的 Record 数据。用 Pattern Matching 实现响应处理器,包含关系模式和守卫条件。

← 上一课 | 下一课 →

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏