掌握 Dart 模式匹配: 2. 核心概念与基础模式

219 阅读4分钟

在上一章中,我们介绍了 Dart 模式匹配的宏观概念及其在简化代码中的重要性。本章将深入探讨模式匹配的基石——模式(Patterns) 。通过理解这些基础模式,你将能够灵活运用模式匹配进行数据解构和逻辑处理。本章提供简洁、实用的代码示例,帮助你快速上手。

2.1 模式(Patterns)的基本构成

在 Dart 中,模式是描述值“形状”的语法结构,用于匹配和解构数据。它可以是简单的字面量,也可以是复杂的嵌套结构。例如:

  • 42:一个常量模式
  • var x:一个变量模式
  • [a, b]:一个列表模式,包含两个变量模式。
  • {'name': n, 'age': a}:一个映射模式,包含两个变量模式。
  • Point(x: x, y: y):一个对象模式,解构对象的属性。

模式适用于以下场景:

  • 变量声明var (x, y) = point;
  • 赋值表达式(x, y) = (y, x);
  • switch 语句或表达式switch (value) { case int i: ... }
  • if-case 语句if (data case [int a, int b]) { ... }
  • for-in 循环for (var (key, value) in map.entries) { ... }

匹配与解构

  • 匹配:检查值是否符合模式的结构和内容。
  • 解构:匹配成功后,提取值并绑定到变量。

示例

Dart

void main() {
  // 匹配和解构坐标记录
  var point = (x: 10, y: 20);
  // 在 if-case 中引入新变量,需要使用 `var` 或明确类型
  if (point case (x: var x, y: var y)) {
    // x 和 y 会自动作为局部变量引入并推断类型(int)
    print('Point: ($x, $y)'); // 输出: Point: (10, 20)
  }

  // 尝试匹配类型不符的列表
  var data = [1, 'two'];
  // 使用 var 解构,变量 a, b 会推断为 dynamic,模式匹配成功
  if (data case [var a, var b]) {
    print('Two items: $a, $b');
  } else {
    print('Not two items.');
  }

  // 如果想严格匹配类型,则明确指定类型
  if (data case [int a, int b]) { // 明确指定 int 类型,此模式匹配失败
    print('Two integers: $a, $b');
  } else {
    print('Not two integers.'); // 输出: Not two integers.
  }
}

2.2 基础模式类型

Dart 提供四种基础模式,用于构建复杂模式匹配逻辑。

2.2.1 常量模式 (Constant Pattern)

作用:匹配值是否等于字面量或 const 常量。

语法:字面量(如 1, 'hello', true, null)或 const 变量。

示例

Dart

void checkStatusCode(int code) {
  switch (code) {
    case 200: // 常量模式
      print('Success');
    case 404: // 常量模式
      print('Not found');
    default:
      print('Unknown status: $code');
  }
}

void main() {
  checkStatusCode(200); // 输出: Success
  checkStatusCode(500); // 输出: Unknown status: 500
}

2.2.2 变量模式 (Variable Pattern)

作用:绑定匹配到的值到新变量,支持解构。

语法

  • var <variableName>
  • final <variableName>
  • <Type> <variableName>(类型可推断时通常省略,或利用简写模式在声明/赋值上下文)

示例

Dart

void main() {
  // 解构用户记录(使用简写模式,仅限于变量声明上下文)
  var user = (name: 'Alice', age: 25);
  // 当解构变量名与记录字段名一致时,使用简写模式 `(:name, :age)`
  // name 会被推断为 String,age 会被推断为 int
  var (:name, :age) = user;
  print('User: $name, Age: $age'); // 输出: User: Alice, Age: 25

  // 解构 API 响应列表
  var response = [200, 'OK'];
  // 列表解构时,变量名自动推断类型
  if (response case [var code, var message]) {
    print('Response: $code - $message'); // 输出: Response: 200 - OK
  }

  // switch 中绑定值(变量模式与类型测试模式的结合)
  void processInput(Object input) {
    switch (input) {
      case int value: // 类型测试模式:同时检查类型并绑定到变量 value (value 推断为 int)
        print('Number: $value');
      case String text: // 类型测试模式:同时检查类型并绑定到变量 text (text 推断为 String)
        print('Text: $text');
      default:
        print('Other: $input');
    }
  }
  processInput(42); // 输出: Number: 42
  processInput('Hello'); // 输出: Text: Hello
}

2.2.3 通配符模式 (Wildcard Pattern _)

作用:忽略不关心的值,保持模式结构完整。

语法_

示例

Dart

void main() {
  // 忽略记录中的字段(使用简写模式和通配符,仅限于变量声明上下文)
  var user = (name: 'Bob', id: 123, role: 'user');
  // 解构 name 字段,并忽略 id 和 role 字段
  var (:name, id: _, role: _) = user;
  print('User: $name'); // 输出: User: Bob

  // switch 中忽略子模式(列表模式结合通配符)
  void processRequest(List<String> request) {
    switch (request) {
      case ['GET', var path]: // 解构第一个元素为 'GET',第二个元素绑定到 path
        print('Fetching: $path');
      case ['POST', var path, _]: // 解构第一个为 'POST',第二个绑定到 path,忽略第三个
        print('Posting to: $path');
      case _: // 匹配任何其他情况
        print('Invalid request.');
    }
  }
  processRequest(['GET', '/api/users']); // 输出: Fetching: /api/users
  processRequest(['POST', '/api/data', 'payload']); // 输出: Posting to: /api/data
}

2.2.4 类型测试模式 (Type-Test Pattern)

作用:检查值类型并进行智能类型提升。

语法<Type> <variableName>

示例

Dart

void main() {
  void processShape(Object shape) {
    switch (shape) {
      case int radius: // 类型测试模式:检查 shape 是否为 int,并将智能提升后的值绑定到 radius
        print('Circle, radius: $radius');
      case List<double> rect: // 类型测试模式:检查 shape 是否为 List<double>,并绑定到 rect
        print('Rectangle, dimensions: $rect');
      default:
        print('Unknown shape.');
    }
  }
  processShape(5); // 输出: Circle, radius: 5
  processShape([3.0, 4.0]); // 输出: Rectangle, dimensions: [3.0, 4.0]
}

2.2.5 空检查模式 (?)

作用:匹配非空值,确保空安全处理。

语法<pattern>?

示例

Dart

void main() {
  // 处理可空用户数据(if-case 中需要明确声明变量)
  (String?, int?)? user = ('Alice', null);

  // (name: var name, age: var age):仅当 user 非空,且其 `name` 字段非空时才匹配。
  // `name` 会被推断为 String (非空),`age` 会被推断为 `int?`。
  if (user case (String name, int? age)) {
    print('Name: $name, Age: ${age ?? "unknown"}'); // 输出: Name: Alice, Age: unknown
  }

  // 处理可空坐标(在 switch 表达式中使用空检查模式)
  String processNullableCoord((int?, int?)? coords) {
    return switch (coords) {
      // 记录本身非空且两个字段都非空时匹配 (x, y 会被推断为 int)
      (int x, int y)? => 'Point: ($x, $y)',
      // 记录非空,第一个字段非空,第二个字段为 null
      (int x, null)? => 'Missing y coordinate ($x).',
      // 记录非空,第一个字段为 null,第二个字段非空
      (null, int y)? => 'Missing x coordinate (y=$y).',
      // 记录非空,两个字段都为 null
      (null, null)? => 'Both coordinates missing.',
      // 记录本身为 null
      null => 'No coordinates provided.',
      // 对于 (int?, int?)? 类型,如果所有上述具象模式都被覆盖,Dart 编译器可能认为它是穷尽的,
      // 不需要 _ 默认分支。如果未来添加更多复杂类型,可能需要 _。
    };
  }

  print(processNullableCoord((10, 20))); // 输出: Point: (10, 20)
  print(processNullableCoord(null)); // 输出: No coordinates provided.
  print(processNullableCoord((10, null))); // 输出: Missing y coordinate (10).
  print(processNullableCoord((null, 20))); // 输出: Missing x coordinate (y=20).
  print(processNullableCoord((null, null))); // 输出: Both coordinates missing.
}

章节总结

本章深入讲解了 Dart 模式匹配的四种基础模式:

  • 常量模式:用于匹配字面量或 const 常量。
  • 变量模式:用于绑定值以便解构,并强调了利用 varfinal 进行类型推断。同时明确了简写模式 (:name) 主要用于变量声明赋值上下文。
  • 通配符模式:使用 _ 明确表示忽略不关心的值,提升代码可读性。
  • 类型测试模式:直接在 case 后指定类型即可进行类型检查和智能类型提升,这是 Dart 3 中推荐的简洁用法。
  • 空检查模式:通过 ? 后缀确保模式只匹配非空值,提升空安全处理的优雅性。

这些优化后的示例展示了如何在实际场景(如 API 响应、几何形状处理)中使用模式匹配,代码简洁且空安全。

展望下一章

理解了基础模式之后,是时候解锁模式匹配更强大的功能了。下一章将深入探讨解构模式,包括列表模式映射模式记录模式对象模式,帮助你优雅地处理各种复杂数据结构,让你的 Dart 代码更加简洁和富有表现力。