Dart 与 Flutter 快速入门指南

Dart 与 Flutter 快速入门指南

面向有编程经验的后端开发者(PHP / Go),以对比视角快速掌握 Dart 与 Flutter。


目录

  • [第一部分:Dart 快速入门](#第一部分:Dart 快速入门 "#%E7%AC%AC%E4%B8%80%E9%83%A8%E5%88%86dart-%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8")
    • [1. Dart 简介](#1. Dart 简介 "#1-dart-%E7%AE%80%E4%BB%8B")
    • [2. 基础语法](#2. 基础语法 "#2-%E5%9F%BA%E7%A1%80%E8%AF%AD%E6%B3%95")
    • [3. 控制流](#3. 控制流 "#3-%E6%8E%A7%E5%88%B6%E6%B5%81")
    • [4. 函数](#4. 函数 "#4-%E5%87%BD%E6%95%B0")
    • [5. 集合](#5. 集合 "#5-%E9%9B%86%E5%90%88")
    • [6. 面向对象](#6. 面向对象 "#6-%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1")
    • [7. 空安全](#7. 空安全 "#7-%E7%A9%BA%E5%AE%89%E5%85%A8")
    • [8. 异步编程](#8. 异步编程 "#8-%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B")
    • [9. 错误处理](#9. 错误处理 "#9-%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86")
    • [10. 常用库与工具](#10. 常用库与工具 "#10-%E5%B8%B8%E7%94%A8%E5%BA%93%E4%B8%8E%E5%B7%A5%E5%85%B7")
  • [第二部分:Flutter 快速入门](#第二部分:Flutter 快速入门 "#%E7%AC%AC%E4%BA%8C%E9%83%A8%E5%88%86flutter-%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8")
    • [1. Flutter 简介与架构](#1. Flutter 简介与架构 "#1-flutter-%E7%AE%80%E4%BB%8B%E4%B8%8E%E6%9E%B6%E6%9E%84")
    • [2. 项目结构与入口](#2. 项目结构与入口 "#2-%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E5%85%A5%E5%8F%A3")
    • [3. 基础 Widget](#3. 基础 Widget "#3-%E5%9F%BA%E7%A1%80-widget")
    • [4. 布局 Widget](#4. 布局 Widget "#4-%E5%B8%83%E5%B1%80-widget")
    • [5. 列表与滚动](#5. 列表与滚动 "#5-%E5%88%97%E8%A1%A8%E4%B8%8E%E6%BB%9A%E5%8A%A8")
    • [6. 导航与路由](#6. 导航与路由 "#6-%E5%AF%BC%E8%88%AA%E4%B8%8E%E8%B7%AF%E7%94%B1")
    • [7. 状态管理](#7. 状态管理 "#7-%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86")
    • [8. 网络请求](#8. 网络请求 "#8-%E7%BD%91%E7%BB%9C%E8%AF%B7%E6%B1%82")
    • [9. 表单与输入](#9. 表单与输入 "#9-%E8%A1%A8%E5%8D%95%E4%B8%8E%E8%BE%93%E5%85%A5")
    • [10. 常用组件速查表](#10. 常用组件速查表 "#10-%E5%B8%B8%E7%94%A8%E7%BB%84%E4%BB%B6%E9%80%9F%E6%9F%A5%E8%A1%A8")

第一部分:Dart 快速入门

1. Dart 简介

特性 说明
类型系统 强类型,支持类型推断
运行方式 JIT(开发热重载) + AOT(生产编译)
内存管理 GC 自动回收
并发模型 单线程 Event Loop + Isolate
包管理 pub.dev,类似 npm / composer

与 Go / PHP 的关键差异:

概念 Go PHP Dart
入口函数 func main() index.php 直接执行 void main()
并发 goroutine + channel 多进程 / Swoole async/await + Isolate
类型 静态强类型 动态弱类型 静态强类型(可推断)
OOP struct + interface class class + mixin
空安全 无(用零值) 编译时空安全

2. 基础语法

2.1 入口文件与打印
dart 复制代码
void main() {
  print('Hello, Dart!');
}
2.2 变量声明
dart 复制代码
// 类型推断(最常用)
var name = 'Kris';         // String
var age = 28;              // int
var score = 98.5;          // double
var isAdmin = true;        // bool

// 显式类型(需要明确类型时使用)
String city = 'Hangzhou';
int count = 100;
double price = 19.99;
bool isActive = false;

// dynamic:动态类型,类似 PHP 的变量(谨慎使用)
dynamic anything = 'hello';
anything = 123;   // 合法,但失去类型安全

// final / const:不可变变量
final now = DateTime.now();   // 运行时确定
const pi = 3.14159;            // 编译时常量
2.3 内置类型
dart 复制代码
// 数字
int a = 42;
double b = 3.14;
num c = 10;       // num 是 int/double 的父类
c = 10.5;

// 字符串
String s1 = '单引号';
String s2 = "双引号";
String s3 = '''多行
字符串''';
String s4 = 'Hello, $name';           // 字符串插值
String s5 = '1 + 1 = ${1 + 1}';       // 表达式插值
String s6 = 'Dart' ' ' 'is' ' fun';   // 相邻字符串自动拼接

// 布尔
bool flag = true;

// Runes(Unicode 码点)
var heart = '\u2665';   // ♥
var emoji = '\u{1f600}'; // 😀

// Symbol(反射用,不常用)
var sym = #mySymbol;
2.4 运算符
dart 复制代码
// 算术
int sum = 1 + 2;
int diff = 5 - 3;
int product = 4 * 2;
double quotient = 7 / 2;    // 3.5(始终返回 double)
int intQuotient = 7 ~/ 2;   // 3(整除)
int remainder = 7 % 2;      // 1

// 类型判断
if (obj is String) { /* ... */ }
if (obj is! int)  { /* ... */ }

// 类型转换
var numStr = '123';
int parsed = int.parse(numStr);
String back = parsed.toString();

// 空相关运算符(见空安全章节)
String? maybeNull;
String value = maybeNull ?? 'default';        // 空值合并
maybeNull ??= 'assign if null';               // 空值赋值
int? length = maybeNull?.length;              // 空安全访问

// 级联操作符 .. (链式调用)
var list = []
  ..add(1)
  ..add(2)
  ..add(3);

// 展开运算符
var list1 = [1, 2, 3];
var list2 = [0, ...list1, 4]; // [0, 1, 2, 3, 4]

3. 控制流

3.1 条件语句
dart 复制代码
// if-else(与 Go/PHP 相同)
if (score >= 90) {
  print('优秀');
} else if (score >= 60) {
  print('及格');
} else {
  print('不及格');
}

// switch
switch (status) {
  case 'success':
    print('成功');
    break;
  case 'pending':
  case 'processing':       // 多个 case 合并
    print('处理中');
    break;
  default:
    print('未知');
}
3.2 循环
dart 复制代码
// for(经典 C 风格)
for (var i = 0; i < 5; i++) {
  print(i);
}

// for-in(遍历可迭代对象)
var items = ['a', 'b', 'c'];
for (var item in items) {
  print(item);
}

// while / do-while
while (condition) { /* ... */ }
do { /* ... */ } while (condition);

4. 函数

4.1 函数定义
dart 复制代码
// 基本定义
int add(int a, int b) {
  return a + b;
}

// 箭头函数(单表达式)
int multiply(int a, int b) => a * b;

// 可选位置参数
String greet(String name, [String? title]) {
  if (title != null) return '$title $name';
  return name;
}

// 命名参数(最常用)
String buildUrl(String host, {int? port, String? path}) {
  var url = 'https://$host';
  if (port != null) url += ':$port';
  if (path != null) url += '/$path';
  return url;
}

// 调用
buildUrl('api.example.com', port: 443, path: 'v1/users');
buildUrl('api.example.com', path: 'v1/users');  // port 可省略

// 命名参数默认值
String connect({String host = 'localhost', int port = 8080}) {
  return '$host:$port';
}

// 必需命名参数(required)
void createUser({required String name, required String email}) {
  // ...
}
4.2 函数是一等公民
dart 复制代码
// 函数赋值给变量
var fn = (int x) => x * 2;
int result = fn(5); // 10

// 函数作为参数
void processItems(List<int> items, bool Function(int) filter) {
  for (var item in items) {
    if (filter(item)) print(item);
  }
}

processItems([1, 2, 3, 4], (n) => n > 2);

// 闭包
Function makeCounter() {
  var count = 0;
  return () => ++count;
}

var counter = makeCounter();
print(counter()); // 1
print(counter()); // 2

5. 集合

5.1 List(列表,类似数组 / slice)
dart 复制代码
// 创建
var list = [1, 2, 3];
var typed = <String>['a', 'b', 'c'];
var fixed = List<int>.filled(3, 0);  // [0, 0, 0] 固定长度

// 常用操作
list.add(4);                // 添加
list.addAll([5, 6]);       // 批量添加
list.insert(0, 0);         // 指定位置插入
list.remove(3);            // 删除元素
list.removeAt(0);          // 删除索引
list.contains(2);          // 是否包含
list.indexOf(2);           // 索引位置
list.length;               // 长度
list.isEmpty;              // 是否为空
list.first;                // 首个元素
list.last;                 // 末个元素

// 函数式操作(类似 PHP 的 array_map / array_filter)
var doubled = list.map((e) => e * 2).toList();
var evens = list.where((e) => e.isEven).toList();
var any = list.any((e) => e > 5);
var every = list.every((e) => e > 0);
var sum = list.fold(0, (prev, e) => prev + e);
var reduced = list.reduce((a, b) => a + b);

// 排序
list.sort();
list.sort((a, b) => b.compareTo(a));  // 降序

// 解构(Dart 3+)
var [first, second, ...rest] = [1, 2, 3, 4, 5];
5.2 Set(集合,无重复)
dart 复制代码
var set = <String>{'apple', 'banana', 'orange'};
// 或 var set = {'apple', 'banana'};  // 注意:{} 默认是 Map

set.add('grape');
set.add('apple');    // 重复,不会添加
set.contains('apple');
set.remove('banana');

// 集合运算
var a = {1, 2, 3};
var b = {3, 4, 5};
a.union(b);          // {1, 2, 3, 4, 5}
a.intersection(b);   // {3}
a.difference(b);     // {1, 2}
5.3 Map(字典,类似 PHP 关联数组 / Go map)
dart 复制代码
// 创建
var map = {'name': 'Kris', 'age': 28};
var typed = <String, dynamic>{'id': 1, 'data': 'hello'};

// 常用操作
map['email'] = 'kris@example.com';   // 添加/修改
map['name'];                          // 取值
map.containsKey('name');
map.containsValue(28);
map.remove('age');
map.length;
map.keys;          // Iterable
map.values;        // Iterable

// 遍历
map.forEach((key, value) => print('$key: $value'));

for (var entry in map.entries) {
  print('${entry.key} = ${entry.value}');
}

6. 面向对象

6.1 类的基本定义
dart 复制代码
class User {
  // 实例字段
  String name;
  int age;

  // 构造函数
  User(this.name, this.age);

  // 命名构造函数
  User.guest()
      : name = 'Guest',
        age = 0;

  User.fromJson(Map<String, dynamic> json)
      : name = json['name'] as String,
        age = json['age'] as int;

  // 方法
  void greet() {
    print('Hi, I am $name, $age years old.');
  }

  // Getter
  bool get isAdult => age >= 18;

  // Setter
  set birthday(int age) {
    this.age = age;
  }

  // 覆写 toString
  @override
  String toString() => 'User(name: $name, age: $age)';
}

// 使用
var user = User('Kris', 28);
var guest = User.guest();
var fromApi = User.fromJson({'name': 'Tom', 'age': 25});
6.2 继承
dart 复制代码
class Animal {
  final String name;
  Animal(this.name);

  void speak() => print('...');
}

class Dog extends Animal {
  Dog(String name) : super(name);

  @override
  void speak() => print('$name: Woof!');
}

// 抽象类(不能实例化)
abstract class Shape {
  double area();                       // 抽象方法

  void describe() => print('A shape'); // 普通方法
}

class Circle extends Shape {
  final double radius;
  Circle(this.radius);

  @override
  double area() => 3.14159 * radius * radius;
}
6.3 Mixin(混入,Dart 特色)

类似 PHP 的 Trait,一种代码复用机制。

dart 复制代码
mixin Logger {
  void log(String msg) => print('[LOG] $msg');
}

mixin Validator {
  bool isValidEmail(String email) => email.contains('@');
}

// 使用 with 关键字混入
class UserService with Logger, Validator {
  void createUser(String email) {
    if (!isValidEmail(email)) {
      log('Invalid email: $email');
      return;
    }
    log('User created: $email');
  }
}
6.4 extension(扩展方法)
dart 复制代码
// 为现有类型添加方法
extension StringExtension on String {
  String get capitalized =>
      isEmpty ? this : '${this[0].toUpperCase()}${substring(1)}';

  bool get isValidEmail => contains('@') && contains('.');
}

// 使用
print('hello'.capitalized);          // Hello
print('a@b.com'.isValidEmail);       // true
6.5 枚举
dart 复制代码
enum Status { pending, processing, done, failed }

void handleStatus(Status s) {
  switch (s) {
    case Status.pending:
      break;
    case Status.processing:
      break;
    case Status.done:
      break;
    case Status.failed:
      break;
  }
}

7. 空安全(Null Safety)

Dart 的空安全在 编译时 防止空引用错误,类似 Kotlin / Swift。

dart 复制代码
// 非空类型(默认)
String name = 'Kris';     // 不能为 null
// name = null;           // 编译错误!

// 可空类型
String? nullableName;     // 可以为 null
nullableName = null;      // OK
nullableName = 'Kris';    // OK

// 空值检查与智能转换
int getLength(String? str) {
  if (str == null) return 0;
  return str.length;      // 自动提升为非空类型
}

// 安全访问 ?.
String? maybeName;
int? len = maybeName?.length;   // 如果 maybeName 为 null,返回 null

// 空值合并 ??
String displayName = maybeName ?? 'Anonymous';

// 空值断言 ! (确定不为 null 时使用,否则抛异常)
String definitely = maybeName!;

// late 关键字(延迟初始化)
late String lazyInit;

void setup() {
  lazyInit = 'initialized';
}

void use() {
  print(lazyInit);   // 如果在 setup() 之前访问会报错
}

// late final(延迟初始化 + 不可变)
late final Database db;

8. 异步编程

Dart 是单线程 Event Loop 模型,异步通过 Future / Stream 实现,语法上使用 async / await

8.1 Future
dart 复制代码
// 创建 Future
Future<String> fetchData() {
  return Future.delayed(Duration(seconds: 1), () => 'Data loaded');
}

// async / await(推荐)
Future<void> loadData() async {
  print('Loading...');
  String data = await fetchData();
  print(data);  // 1秒后输出: Data loaded
}

// .then() 链式调用
fetchData().then((data) {
  print(data);
}).catchError((err) {
  print('Error: $err');
});

// 并行执行多个 Future
Future<void> loadAll() async {
  var results = await Future.wait([
    fetchData(),
    fetchData(),
    fetchData(),
  ]);
  print(results); // [Data loaded, Data loaded, Data loaded]
}
8.2 Stream

类似于 Go 的 channel 或 PHP 的 Generator,用于处理连续数据流。

dart 复制代码
// 创建 Stream
Stream<int> countStream(int max) async* {
  for (int i = 1; i <= max; i++) {
    await Future.delayed(Duration(milliseconds: 500));
    yield i;
  }
}

// 监听 Stream
void main() async {
  await for (var value in countStream(5)) {
    print(value); // 每 500ms 输出: 1, 2, 3, 4, 5
  }
}

// Stream 常用方法
var stream = countStream(10);
stream
    .where((n) => n.isEven)
    .map((n) => n * 2)
    .listen((data) => print(data));
8.3 StreamController
dart 复制代码
import 'dart:async';

void main() {
  var controller = StreamController<String>();

  // 添加数据
  controller.add('Event 1');
  controller.add('Event 2');

  // 监听
  controller.stream.listen((data) {
    print('Received: $data');
  });

  controller.close();
}

// Broadcast Stream(多订阅者)
var broadcastCtrl = StreamController<String>.broadcast();
broadcastCtrl.stream.listen((d) => print('Listener 1: $d'));
broadcastCtrl.stream.listen((d) => print('Listener 2: $d'));
broadcastCtrl.add('Hello');

9. 错误处理

dart 复制代码
// try-catch-finally
try {
  int result = int.parse('not a number');
  print(result);
} on FormatException catch (e) {
  // 捕获特定异常
  print('格式错误: $e');
} catch (e, stackTrace) {
  // 捕获所有异常
  print('未知错误: $e');
  print('堆栈: $stackTrace');
} finally {
  print('无论是否异常都会执行');
}

// 自定义异常
class ApiException implements Exception {
  final int statusCode;
  final String message;
  ApiException(this.statusCode, this.message);

  @override
  String toString() => 'ApiException($statusCode): $message';
}

void callApi() {
  throw ApiException(404, 'Not Found');
}

// rethrow
Future<void> fetchWithRetry() async {
  try {
    await fetchData();
  } catch (e) {
    print('第一次失败,重试中...');
    // 可以在这里做日志、上报等
    rethrow; // 继续向上抛出
  }
}

10. 常用库与工具

10.1 核心库
dart 复制代码
// dart:convert - JSON 编解码
import 'dart:convert';

void jsonExample() {
  // 编码
  var user = {'name': 'Kris', 'age': 28};
  String jsonStr = jsonEncode(user);  // {"name":"Kris","age":28}

  // 解码
  Map<String, dynamic> decoded = jsonDecode(jsonStr);
  print(decoded['name']); // Kris
}

// dart:math - 数学运算
import 'dart:math';

void mathExample() {
  print(max(10, 20));
  print(min(10, 20));
  print(sqrt(16));
  print(Random().nextInt(100));
}

// dart:io - 文件与网络(非 Flutter 场景)
import 'dart:io';

void fileExample() async {
  var file = File('data.txt');
  await file.writeAsString('Hello, Dart!');
  String content = await file.readAsString();
  print(content);
}

// dart:collection - 高级集合
import 'dart:collection';

void collectionExample() {
  var queue = Queue<int>();
  queue.addFirst(1);
  queue.addLast(2);

  var linkedMap = LinkedHashMap<String, int>();
}
10.2 包管理与常用第三方库
yaml 复制代码
# pubspec.yaml 示例
name: my_app
environment:
  sdk: '>=3.0.0 <4.0.0'

dependencies:
  http: ^1.1.0          # HTTP 请求
  dio: ^5.3.0           # 更强大的 HTTP 客户端
  shared_preferences: ^2.2.0  # 本地键值存储
  path_provider: ^2.1.0      # 文件路径
  provider: ^6.0.0           # 状态管理
  riverpod: ^2.4.0           # 状态管理(Provider 的升级版)
  get_it: ^7.6.0             # 依赖注入
  json_annotation: ^4.8.0    # JSON 序列化注解

dev_dependencies:
  test: ^1.24.0          # 单元测试
  build_runner: ^2.4.0   # 代码生成
  json_serializable: ^6.7.0  # JSON 序列化代码生成
  mockito: ^5.4.0        # Mock 框架


Dart 理论深度总结

A. Dart 设计哲学与语言定位

Dart 由 Google 于 2011 年发布,最初目标是替代 JavaScript。虽然这个目标没有实现,但 Dart 在 Flutter 生态中找到了自己的核心定位------客户端优化的编程语言

核心设计目标:

目标 说明
开发效率 JIT 热重载,毫秒级看到修改效果,媲美脚本语言体验
运行性能 AOT 编译为原生机器码,接近 C/C++ 的性能
跨平台可移植 同一套 Dart 代码编译为 ARM/x64 机器码或 JavaScript
渐进式类型 可选的类型标注,兼顾灵活性与安全性

与其他语言的差异化定位:

Dart 所做的取舍很值得品味。它没有像 Go 那样追求极致简洁,因为 UI 开发需要丰富的表达力来声明式描述界面;它也没有像 Kotlin/Swift 那样深度绑定单一平台,因为 Flutter 的目标是跨端。Dart 在 "表达能力" 与 "编译性能" 之间找到了一个适合 UI 开发的平衡点。

markdown 复制代码
            简洁性                   表达力
Go  ············●························
                      Dart
Kotlin  ························●········
                          Flutter 的理想区间

B. 类型系统深度解析

Dart 的类型系统属于 强类型 + 类型推断 + 运行时类型检查 的混合体。

强类型 vs 弱类型: 类型转换必须显式进行,不会隐式发生。这点与 Go 一致,与 PHP/JavaScript 截然不同。

dart 复制代码
int a = 1;
// String b = a;      // 编译错误!不会隐式转换
String b = a.toString(); // 必须显式转换

类型推断的工作机制: Dart 编译器在静态分析阶段推断类型。var 不是 dynamic,编译器会确定变量的实际类型。

dart 复制代码
var x = 1;      // 编译器推断为 int,后续 x = "hello" 会报错
dynamic y = 1;  // 真正的动态类型,y = "hello" 合法

泛型: Dart 泛型是 具体化(reified) 的,意味着泛型信息在运行时保留,这与 Java 的类型擦除完全不同,更接近 C#。

dart 复制代码
var list = <int>[1, 2, 3];
print(list is List<int>);  // true ------ 运行时可以判断泛型类型
print(list is List);       // true

// 在 Java 中,List<Integer> 在运行时只是 List,无法区分;
// 在 Dart 中,类型信息在运行时完整保留。

理论要点:具体化泛型是 Dart 能做 "Hot Reload" 的关键前提之一------运行时保留完整类型信息,使得 Flutter 的 Widget 树在增量更新时可以精确匹配类型,避免状态丢失。

C. 事件循环与并发模型深度解析

Dart 采用 单线程 Event Loop 模型,这与 JavaScript 高度相似,但与 Go 的 goroutine 模型完全不同。

vbnet 复制代码
        ┌─────────────────────────────────┐
        │        Event Loop(单线程)       │
        │                                  │
        │  Microtask Queue(优先级最高)     │
        │  ┌─────────┐ ┌─────────┐         │
        │  │ future.then  │ stream.listen │
        │  └─────────┘ └─────────┘         │
        │              ↓                    │
        │  Event Queue(一般优先级)          │
        │  ┌─────────┐ ┌─────────┐         │
        │  │ I/O 事件  │ │ 定时器   │         │
        │  └─────────┘ └─────────┘         │
        └─────────────────────────────────┘

事件循环的执行顺序:

  1. 执行 main() 中的同步代码
  2. 检查 Microtask Queue,逐个执行直到队列为空
  3. Event Queue 取一个事件执行
  4. 回到步骤 2,循环往复
dart 复制代码
void main() {
  print('1: 同步代码开始');

  Future(() => print('3: Event Queue'));            // 进入 Event Queue
  Future.microtask(() => print('2: Microtask'));    // 进入 Microtask Queue
  scheduleMicrotask(() => print('2.5: Microtask')); // 进入 Microtask Queue

  print('1.5: 同步代码结束');
}
// 输出顺序:1 → 1.5 → 2 → 2.5 → 3

为什么熟悉这个很重要 :在 Flutter 中,某些操作(如 setState)必须在 Microtask 全部清空、Event Queue 等待处理的间隙执行。理解事件循环能帮你避免 UI 卡顿和不一致的状态更新。

Isolate------Dart 的"多线程":

当需要 CPU 密集计算时,不能直接开新线程(内存不共享),而是创建 Isolate 。每个 Isolate 有独立的内存堆和独立的事件循环,Isolate 之间通过 消息传递(Port)通信。

scss 复制代码
┌──────────────┐    SendPort     ┌──────────────┐
│  Main Isolate │ ←──────────→  │ Worker Isolate │
│  (UI 线程)    │   消息传递      │  (计算线程)    │
│  独立堆内存    │                │  独立堆内存    │
└──────────────┘                └──────────────┘

这一点与 Go 的 goroutine 共享内存 模型根本不同,更接近 Erlang 的 Actor 模型。Dart 故意如此设计:无共享内存意味着无数据竞争、无锁,在 UI 线程中这非常重要------你不会因为某个后台任务修改了正在渲染的数据而看到撕裂的界面。

dart 复制代码
import 'dart:isolate';

void heavyCompute(SendPort sendPort) {
  // 耗时计算
  var result = 0;
  for (var i = 0; i < 1000000000; i++) {
    result += i;
  }
  sendPort.send(result);
}

void main() async {
  var receivePort = ReceivePort();
  await Isolate.spawn(heavyCompute, receivePort.sendPort);

  receivePort.listen((message) {
    print('计算结果: $message');
  });
}

D. Mixin 设计原理:组合优于继承

Mixin 是 Dart 对 "组合优于继承" 设计原则的语言级实现。要理解 Mixin 为什么存在,需要回顾 OOP 继承的经典困境:

问题场景 :你有一个 Animal 基类,你需要 Dog 会游泳、Bird 会飞、Duck 既会游泳又会飞。在单继承语言中,你无法让 Duck 同时继承 SwimmerFlyer

传统解决方案及缺陷:

方案 缺陷
多层继承 类层次爆炸,FlyingSwimmingAnimal 这种中间类泛滥
接口 + 重复实现 Swim / Fly 的实现代码在每个类中重复
组合(持有对象引用) 调用链变长:duck.swimmer.swim() 而非 duck.swim()

Dart Mixin 的解决方式 :Mixin 是带实现的"能力模块",通过 with 关键字线性化混入,解决了代码复用问题。

markdown 复制代码
        Animal
          │
    ┌─────┼─────┐
    │     │     │
   Dog   Duck  Bird
    │     │     │
    Swim  Swim  Fly
          Fly
dart 复制代码
mixin Swimmer {
  void swim() => print('游泳中...');
}

mixin Flyer {
  void fly() => print('飞行中...');
}

class Duck extends Animal with Swimmer, Flyer {
  // Duck 同时拥有 swim() 和 fly()
}

var duck = Duck();
duck.swim(); // 来自 Swimmer
duck.fly();  // 来自 Flyer

线性化(Linearization)规则 :当多个 Mixin 有同名方法时,最右边的 Mixin 优先(类似 CSS 的"后来居上")。

dart 复制代码
mixin A { void log() => print('A'); }
mixin B { void log() => print('B'); }

class C with A, B {}  // B 胜出
class D with B, A {}  // A 胜出

C().log(); // B
D().log(); // A

对后端开发者的建议 :Mixin 的思想在 Go 中可以通过 struct embedding 模拟,在 PHP 中就是 Trait。理解 Mixin 对于看懂 Flutter 源码至关重要------Flutter 框架内部大量使用 Mixin 来组合行为(如 SingleTickerProviderStateMixinAutomaticKeepAliveClientMixin)。

E. 空安全的设计意图

Dart 的空安全不仅仅是一个语法糖,它是类型系统的一部分。核心思想来自 Tony Hoare 的名言------他把空引用的发明称为"十亿美元的错误"。

设计目标:将空值检查从运行时提前到编译时。

复制代码
传统语言运行时流程:
  定义变量 → 忘记判空 → 运行时 NullPointerException → 崩溃

Dart 编译时流程:
  定义变量 → 编译器检查可空性 → 强迫处理空值 → 运行时安全

类型系统如何保证:

dart 复制代码
String     // 这个类型保证:此变量永远不为 null
String?    // 这个类型声明:此变量可能为 null,使用时必须处理

// 编译器强制流分析:
String? maybeName;
// print(maybeName.length);  // 编译错误!没有处理 null 的可能

if (maybeName != null) {
  print(maybeName.length);   // OK:编译器通过流分析确定此处非空
}

这与 Go 的零值哲学完全不同。Go 用零值(""0falsenil)来代替 null,问题是 nil 依然是合法的值,编译器不强制你处理它。Dart 的做法是在类型层面区分"可能为空"与"绝不为空",这种类型级别的保证比运行时检查更加可靠。

对比总结

  • Go:零值策略,简洁但缺乏编译时安全网
  • PHP:动态类型,运行时才暴露 null 问题
  • Dart/Kotlin/Swift:类型系统级别的空安全,编译时预防

第二部分:Flutter 快速入门

1. Flutter 简介与架构

Flutter 是 Google 的跨平台 UI 框架,使用 Dart 编写,一套代码同时运行在 iOS、Android、Web、Desktop。

核心思想:Everything is a Widget。

层级 说明
Framework (Dart) Material / Cupertino Widgets, Rendering, Animation
Engine (C++) Skia 渲染引擎, Dart Runtime, 文本排版
Embedder (平台相关) 平台 Channels, 事件循环, 渲染 Surface

与 Vue.js 开发对比:

概念 Vue.js Flutter
组件 .vue 单文件组件 Widget(Dart 类)
模板语法 <template> + 指令 纯 Dart 代码构建 UI
响应式数据 ref() / reactive() setState() / Provider / Riverpod
样式 CSS / scoped style Theme + Widget 属性
路由 vue-router Navigator
网络请求 axios / fetch http / dio

2. 项目结构与入口

bash 复制代码
my_flutter_app/
├── android/           # Android 原生工程
├── ios/               # iOS 原生工程
├── lib/               # Dart 代码(主开发目录)
│   ├── main.dart      # 入口文件
│   ├── models/        # 数据模型
│   ├── pages/         # 页面
│   ├── widgets/       # 可复用组件
│   └── services/      # 网络请求等服务
├── test/              # 测试
└── pubspec.yaml       # 依赖配置

lib/main.dart 入口示例:

dart 复制代码
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

3. 基础 Widget

Flutter 中几乎所有东西都是 Widget。Widget 分为两大类:

  • StatelessWidget:无状态,属性不可变,UI 依赖外部数据。
  • StatefulWidget:有状态,内部维护可变状态。
3.1 StatelessWidget
dart 复制代码
class GreetingCard extends StatelessWidget {
  final String name;

  const GreetingCard({super.key, required this.name});

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Text(
          'Hello, $name!',
          style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
        ),
      ),
    );
  }
}
3.2 StatefulWidget
dart 复制代码
class CounterPage extends StatefulWidget {
  const CounterPage({super.key});

  @override
  State<CounterPage> createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('计数器')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('当前计数:', style: TextStyle(fontSize: 18)),
            Text('$_count', style: TextStyle(fontSize: 48, fontWeight: FontWeight.bold)),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: _increment,
              child: const Text('+1'),
            ),
          ],
        ),
      ),
    );
  }
}
3.3 常用基础 Widget
dart 复制代码
// Text - 文本
Text(
  'Hello Flutter',
  style: TextStyle(
    fontSize: 24,
    fontWeight: FontWeight.bold,
    color: Colors.blue,
  ),
  textAlign: TextAlign.center,
  maxLines: 2,
  overflow: TextOverflow.ellipsis,
)

// Image - 图片
Image.network('https://example.com/image.png')
Image.asset('assets/images/logo.png')
Image.file(File('/path/to/image.png'))

// Icon - 图标
Icon(Icons.home, size: 32, color: Colors.blue)

// ElevatedButton - 按钮
ElevatedButton(
  onPressed: () => print('点击'),
  style: ElevatedButton.styleFrom(
    backgroundColor: Colors.blue,
    foregroundColor: Colors.white,
    padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
  ),
  child: const Text('提交'),
)

// TextButton / OutlinedButton
TextButton(onPressed: () {}, child: const Text('取消'))
OutlinedButton(onPressed: () {}, child: const Text('更多'))

4. 布局 Widget

4.1 主轴与交叉轴

Flutter 布局中的两个核心概念:

sql 复制代码
Row(水平)          Column(垂直)
主轴 → 水平         主轴 → 垂直
交叉轴 ↑ 垂直       交叉轴 → 水平
4.2 常用布局示例
dart 复制代码
// Row - 水平排列
Row(
  mainAxisAlignment: MainAxisAlignment.spaceBetween, // 主轴对齐
  crossAxisAlignment: CrossAxisAlignment.center,      // 交叉轴对齐
  children: [
    Icon(Icons.star),
    Text('标题'),
    Icon(Icons.more_vert),
  ],
)

// Column - 垂直排列
Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    Text('第一行'),
    SizedBox(height: 10),    // 间距
    Text('第二行'),
  ],
)

// Wrap - 自动换行
Wrap(
  spacing: 8,     // 水平间距
  runSpacing: 4,  // 垂直间距
  children: [
    Chip(label: Text('Tag 1')),
    Chip(label: Text('Tag 2')),
    Chip(label: Text('Tag 3')),
    Chip(label: Text('Tag 4')),
  ],
)

// Stack - 层叠布局(类似 CSS position: absolute)
Stack(
  children: [
    Container(width: 200, height: 200, color: Colors.blue),
    Positioned(
      top: 20,
      right: 20,
      child: Icon(Icons.star, color: Colors.white),
    ),
  ],
)

// Expanded / Flexible - 弹性布局(类似 CSS flex-grow)
Row(
  children: [
    Expanded(flex: 2, child: Container(color: Colors.red)),   // 占 2/3
    Expanded(flex: 1, child: Container(color: Colors.blue)),  // 占 1/3
  ],
)

// Padding - 内边距
Padding(
  padding: const EdgeInsets.all(16.0),  // 四周
  // EdgeInsets.symmetric(horizontal: 16, vertical: 8)
  // EdgeInsets.only(top: 8, left: 16)
  child: Text('带内边距的文字'),
)

// Container - 万能容器(可同时设置大小、边距、装饰)
Container(
  width: 200,
  height: 100,
  margin: const EdgeInsets.all(10),
  padding: const EdgeInsets.symmetric(horizontal: 16),
  decoration: BoxDecoration(
    color: Colors.blue.shade50,
    borderRadius: BorderRadius.circular(12),
    border: Border.all(color: Colors.blue, width: 2),
    boxShadow: [
      BoxShadow(color: Colors.black26, blurRadius: 6, offset: Offset(2, 2)),
    ],
  ),
  child: const Text('Container 示例'),
)
4.3 布局调试技巧
dart 复制代码
// 开发时用 Flutter Inspector 或给 Container 加背景色快速看布局
Container(
  color: Colors.red.withOpacity(0.3),  // 临时背景色
  child: Text('调试用'),
)

5. 列表与滚动

5.1 ListView
dart 复制代码
// 少量固定项
ListView(
  children: [
    ListTile(title: Text('Item 1'), subtitle: Text('副标题')),
    ListTile(title: Text('Item 2')),
    ListTile(title: Text('Item 3')),
  ],
)

// 大量数据 - 懒加载构建(推荐)
ListView.builder(
  itemCount: 1000,
  itemBuilder: (context, index) {
    return ListTile(
      leading: CircleAvatar(child: Text('${index + 1}')),
      title: Text('Item ${index + 1}'),
      trailing: Icon(Icons.chevron_right),
      onTap: () => print('点击了 $index'),
    );
  },
)

// 带分割线
ListView.separated(
  itemCount: 50,
  separatorBuilder: (_, __) => const Divider(height: 1),
  itemBuilder: (context, index) => ListTile(title: Text('Item $index')),
)
5.2 GridView
dart 复制代码
GridView.builder(
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 3,       // 每行 3 个
    childAspectRatio: 1.0,   // 宽高比
    crossAxisSpacing: 8,
    mainAxisSpacing: 8,
  ),
  itemCount: 20,
  itemBuilder: (context, index) {
    return Container(
      color: Colors.blue.shade100,
      child: Center(child: Text('Item $index')),
    );
  },
)
5.3 SingleChildScrollView(单个子项可滚动)
dart 复制代码
SingleChildScrollView(
  padding: const EdgeInsets.all(16),
  child: Column(
    children: [
      Text('很长的内容...'),
      // 动态内容可能超出屏幕
    ],
  ),
)
5.4 RefreshIndicator(下拉刷新)
dart 复制代码
RefreshIndicator(
  onRefresh: () async {
    // 刷新数据
    await Future.delayed(Duration(seconds: 2));
  },
  child: ListView.builder(
    itemCount: items.length,
    itemBuilder: (_, i) => ListTile(title: Text(items[i])),
  ),
)

6. 导航与路由

6.1 基本导航
dart 复制代码
// 跳转到新页面
Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => DetailPage(id: 123)),
);

// 返回上一页
Navigator.pop(context);

// 带返回值
// 从 DetailPage 返回
Navigator.pop(context, '返回的数据');

// 接收返回值
final result = await Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => SelectionPage()),
);
print('选择了: $result');
6.2 命名路由
dart 复制代码
// 在 MaterialApp 中配置
MaterialApp(
  initialRoute: '/',
  routes: {
    '/': (context) => const HomePage(),
    '/detail': (context) => const DetailPage(),
    '/settings': (context) => const SettingsPage(),
  },
);

// 跳转
Navigator.pushNamed(context, '/detail');

// 带参数
Navigator.pushNamed(context, '/detail', arguments: {'id': 123});

// 接收参数
class DetailPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final args = ModalRoute.of(context)!.settings.arguments as Map;
    return Scaffold(
      appBar: AppBar(title: Text('详情 #${args['id']}')),
    );
  }
}
6.3 GoRouter(推荐的生产级路由)
yaml 复制代码
# pubspec.yaml
dependencies:
  go_router: ^12.0.0
dart 复制代码
import 'package:go_router/go_router.dart';

final router = GoRouter(
  initialLocation: '/',
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomePage(),
    ),
    GoRoute(
      path: '/detail/:id',
      builder: (context, state) {
        final id = state.pathParameters['id']!;
        return DetailPage(id: int.parse(id));
      },
    ),
  ],
);

// 使用
MaterialApp.router(
  routerConfig: router,
);

// 跳转
context.go('/detail/123');
context.push('/detail/123');

7. 状态管理

7.1 setState(局部状态,最简单)

适用于单个 Widget 的状态,前面 CounterPage 就是例子。局限性:状态不能跨 Widget 共享。

7.2 InheritedWidget / InheritedNotifier(Flutter 内置)
dart 复制代码
class CounterNotifier extends ValueNotifier<int> {
  CounterNotifier() : super(0);
  void increment() {
    value++;
    notifyListeners();
  }
}

class CounterProvider extends InheritedNotifier<CounterNotifier> {
  const CounterProvider({
    super.key,
    required CounterNotifier notifier,
    required super.child,
  }) : super(notifier: notifier);

  static CounterNotifier of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<CounterProvider>()!.notifier!;
  }
}
7.3 Provider(推荐,入门首选)
yaml 复制代码
dependencies:
  provider: ^6.1.0
dart 复制代码
import 'package:provider/provider.dart';

// 1. 定义 Model(继承 ChangeNotifier)
class CartModel extends ChangeNotifier {
  final List<String> _items = [];

  List<String> get items => List.unmodifiable(_items);
  int get count => _items.length;

  void add(String item) {
    _items.add(item);
    notifyListeners();  // 通知 UI 更新
  }

  void remove(String item) {
    _items.remove(item);
    notifyListeners();
  }
}

// 2. 在顶层注入
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => CartModel(),
      child: const MyApp(),
    ),
  );
}

// 3. 在任意子 Widget 中使用
class CartPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 读取(监听变化)
    final cart = context.watch<CartModel>();

    return Scaffold(
      appBar: AppBar(title: Text('购物车(${cart.count})')),
      body: ListView.builder(
        itemCount: cart.items.length,
        itemBuilder: (_, i) => ListTile(title: Text(cart.items[i])),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 读取(不监听变化,仅调用方法)
          context.read<CartModel>().add('Item ${cart.count + 1}');
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}
7.4 状态管理方案对比
方案 复杂度 适用场景
setState 单 Widget 局部状态
Provider 中小型应用,官方推荐入门
Riverpod 中高 Provider 的改进版,编译安全
Bloc 大型应用,事件驱动
GetX 快速开发,全家桶

8. 网络请求

8.1 http 包(简单请求)
dart 复制代码
import 'package:http/http.dart' as http;
import 'dart:convert';

Future<List<dynamic>> fetchUsers() async {
  final response = await http.get(
    Uri.parse('https://jsonplaceholder.typicode.com/users'),
    headers: {'Content-Type': 'application/json'},
  );

  if (response.statusCode == 200) {
    return jsonDecode(response.body) as List<dynamic>;
  } else {
    throw Exception('请求失败: ${response.statusCode}');
  }
}
8.2 dio 包(推荐,功能更强)
yaml 复制代码
dependencies:
  dio: ^5.3.0
dart 复制代码
import 'package:dio/dio.dart';

// 创建 Dio 实例(可全局复用)
final dio = Dio(BaseOptions(
  baseUrl: 'https://api.example.com',
  connectTimeout: const Duration(seconds: 5),
  receiveTimeout: const Duration(seconds: 3),
));

// 拦截器
dio.interceptors.add(InterceptorsWrapper(
  onRequest: (options, handler) {
    // 添加 Token
    options.headers['Authorization'] = 'Bearer your_token';
    print('请求: ${options.method} ${options.path}');
    handler.next(options);
  },
  onResponse: (response, handler) {
    print('响应: ${response.statusCode}');
    handler.next(response);
  },
  onError: (error, handler) {
    print('错误: ${error.message}');
    handler.next(error);
  },
));

// GET 请求
Future<Map<String, dynamic>> getUser(int id) async {
  try {
    final response = await dio.get('/users/$id');
    return response.data;
  } on DioException catch (e) {
    if (e.response != null) {
      // 服务器返回错误
      throw ApiException(e.response!.statusCode ?? 500, e.message ?? '');
    } else {
      // 网络错误
      throw Exception('网络连接失败');
    }
  }
}

// POST 请求
Future<void> createUser(Map<String, dynamic> data) async {
  final response = await dio.post('/users', data: data);
  return response.data;
}

// 文件上传
Future<void> uploadFile(String path) async {
  final formData = FormData.fromMap({
    'file': await MultipartFile.fromFile(path),
  });
  await dio.post('/upload', data: formData);
}
8.3 JSON 序列化
dart 复制代码
// 手动序列化(适合简单场景)
class User {
  final int id;
  final String name;
  final String email;

  User({required this.id, required this.name, required this.email});

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'] as int,
      name: json['name'] as String,
      email: json['email'] as String,
    );
  }

  Map<String, dynamic> toJson() => {
    'id': id,
    'name': name,
    'email': email,
  };
}

// 使用
var user = User.fromJson(jsonData);

9. 表单与输入

dart 复制代码
class LoginPage extends StatefulWidget {
  @override
  State<LoginPage> createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  final _formKey = GlobalKey<FormState>();
  final _emailCtrl = TextEditingController();
  final _passCtrl = TextEditingController();
  bool _obscurePass = true;

  void _submit() {
    if (_formKey.currentState!.validate()) {
      // 表单验证通过
      print('Email: ${_emailCtrl.text}');
      print('Password: ${_passCtrl.text}');
    }
  }

  @override
  void dispose() {
    _emailCtrl.dispose();
    _passCtrl.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('登录')),
      body: Padding(
        padding: const EdgeInsets.all(24.0),
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              TextFormField(
                controller: _emailCtrl,
                decoration: const InputDecoration(
                  labelText: '邮箱',
                  prefixIcon: Icon(Icons.email),
                  border: OutlineInputBorder(),
                ),
                keyboardType: TextInputType.emailAddress,
                validator: (value) {
                  if (value == null || value.isEmpty) return '请输入邮箱';
                  if (!value.contains('@')) return '邮箱格式不正确';
                  return null;
                },
              ),
              const SizedBox(height: 16),
              TextFormField(
                controller: _passCtrl,
                obscureText: _obscurePass,
                decoration: InputDecoration(
                  labelText: '密码',
                  prefixIcon: const Icon(Icons.lock),
                  border: const OutlineInputBorder(),
                  suffixIcon: IconButton(
                    icon: Icon(
                      _obscurePass ? Icons.visibility_off : Icons.visibility,
                    ),
                    onPressed: () => setState(() => _obscurePass = !_obscurePass),
                  ),
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) return '请输入密码';
                  if (value.length < 6) return '密码至少 6 位';
                  return null;
                },
              ),
              const SizedBox(height: 24),
              SizedBox(
                width: double.infinity,
                child: ElevatedButton(
                  onPressed: _submit,
                  child: const Text('登录'),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

10. 常用组件速查表

分类 Widget 用途
布局 Row / Column 水平/垂直排列
Stack + Positioned 层叠布局
Expanded / Flexible 弹性空间
Wrap 自动换行
Align / Center 对齐
基础 Text 文本
Image 图片
Icon 图标
Container 万能容器
Padding 内边距
SizedBox 固定尺寸 / 间距
按钮 ElevatedButton 凸起按钮
TextButton 文本按钮
OutlinedButton 描边按钮
IconButton 图标按钮
FloatingActionButton 浮动按钮
列表 ListView 滚动列表
GridView 网格列表
SingleChildScrollView 单子项滚动
导航 Scaffold 页面架子
AppBar 顶部导航栏
BottomNavigationBar 底部导航栏
TabBar + TabBarView 选项卡
Drawer 侧边抽屉
反馈 SnackBar 轻提示
AlertDialog 对话框
BottomSheet 底部弹出
CircularProgressIndicator 加载中
LinearProgressIndicator 进度条
输入 TextField 文本输入
Checkbox 复选框
Radio 单选框
Switch 开关
Slider 滑块
DropdownButton 下拉选择
DatePicker 日期选择
展示 Card 卡片
Chip 标签
Divider 分割线
Tooltip 提示气泡
Badge 徽标
DataTable 数据表格

学习路线建议

  1. Dart 基础(1-2 天):变量、函数、集合、OOP、空安全
  2. Dart 异步(1 天):Future、async/await、Stream
  3. Flutter 环境搭建(半天):SDK + IDE + 模拟器
  4. Flutter 基础 Widget(2-3 天):Text、Container、Row、Column、Stack
  5. 列表与导航(1-2 天):ListView、GridView、Navigator
  6. 状态管理(2-3 天):setState → Provider → Riverpod
  7. 网络与持久化(2-3 天):dio、shared_preferences、sqflite
  8. 实战项目(持续):做一个完整的 App 巩固所学

Tips :作为有经验的开发者,重点理解 Widget 树概念和声明式 UI 的思维转变。Flutter 的 build 方法就如同 Vue 的 render 函数------用代码描述 UI 当前状态应该长什么样。



Dart 进阶特性

1. 泛型深入

泛型不仅用于集合,在架构设计中也是核心工具。

1.1 泛型约束
dart 复制代码
// 约束类型参数必须继承自某个类
class Repository<T extends BaseModel> {
  final List<T> _items = [];

  void add(T item) => _items.add(item);
  T? getById(int id) {
    try { return _items.firstWhere((e) => e.id == id); }
    catch (_) { return null; }
  }
}

// 使用时必须传入 BaseModel 的子类
abstract class BaseModel { int get id; }
class User extends BaseModel {
  final int id; final String name;
  User({required this.id, required this.name});
}

var userRepo = Repository<User>();  // OK
// var strRepo = Repository<String>();  // 编译错误!String 不是 BaseModel
1.2 泛型方法
dart 复制代码
// 泛型方法:返回类型由参数决定
T? firstOrNull<T>(List<T> list) => list.isEmpty ? null : list.first;

var nums = [1, 2, 3];
int? first = firstOrNull(nums);     // 推断为 int?
String? s = firstOrNull(['a','b']); // 推断为 String?

// 泛型回调
void fetch<T>(String url, T Function(Map<String, dynamic>) fromJson) async {
  // ...网络请求...
  final json = {'id': 1, 'name': 'Kris'};
  T result = fromJson(json);
  print(result);
}
1.3 泛型的型变

Dart 的泛型默认是 不变(invariant) 的,但可以通过关键字调整:

dart 复制代码
// 协变(covariant):子类型可以替代父类型
class Animal {}
class Dog extends Animal {}

// 默认不变:List<Dog> 不是 List<Animal> 的子类型
List<Animal> animals = <Dog>[];  // 编译错误!

// 使用协变(out 语义,只读)
List<Animal> animals2 = <Dog>[Dog(), Dog()]; // Dart 在某些上下文允许

// 逆变(contravariant):父类型可以替代子类型
// Dart 通过函数参数支持
void trainer(void Function(Dog) handler) {
  // 可以传入处理 Animal 的函数(逆变)
  void handleAnimal(Animal a) => print('处理动物');
  handler = handleAnimal;  // OK
}

2. Records(记录类型,Dart 3+)

Records 是匿名、不可变的聚合类型,类似 Go 的多返回值或 PHP 的关联数组,但有类型安全。

dart 复制代码
// 创建 Record
var record = ('Kris', 28, isAdmin: true);  // (String, int, {bool isAdmin})
print(record.$1);       // Kris ------ 位置字段
print(record.$2);       // 28
print(record.isAdmin);  // true ------ 命名字段

// 多返回值(替代 PHP 的 list() 或 Go 的多返回值)
(String name, int age) getUser() {
  return ('Kris', 28);
}

var user = getUser();
print(user.name);  // Kris
print(user.age);   // 28

// 解构
var (name, age) = getUser();
print('$name is $age years old');

// 在 switch 中匹配
switch (user) {
  case (String name, int age) when age >= 18:
    print('$name 已成年');
  case (String name, _):
    print('$name 未成年');
}

3. Patterns(模式匹配,Dart 3+)

Patterns 是 Dart 3 最重要的新特性,远远超越了传统的 switch

dart 复制代码
// 解构 List
var list = [1, 2, 3, 4, 5];
var [a, b, ...rest] = list;
print('$a, $b, $rest');  // 1, 2, [3, 4, 5]

// 解构 Map
var json = {'name': 'Kris', 'age': 28};
var {'name': n, 'age': a} = json;
print('$n is $a');

// 逻辑模式(and / or)
String describe(dynamic obj) {
  return switch (obj) {
    int(isEven: true) && > 0   => '正偶数',
    int(isEven: false) && > 0  => '正奇数',
    < 0                        => '负数',
    _                          => '其他',
  };
}

// 类型模式 + 守卫
String handleResponse(Object? response) {
  return switch (response) {
    null                    => '无响应',
    String s when s.isEmpty => '空字符串',
    String s                => '内容: $s',
    int code when code >= 400 => '错误码: $code',
    int code                => '状态码: $code',
    _                       => '未知类型',
  };
}

4. Sealed Class(密封类,Dart 3+)

Sealed class 让你在编译时保证穷尽性检查,非常适合状态建模。

dart 复制代码
// 定义密封类族
sealed class Result<T> {}

class Success<T> extends Result<T> {
  final T data;
  Success(this.data);
}

class Failure<T> extends Result<T> {
  final String error;
  Failure(this.error);
}

class Loading<T> extends Result<T> {}

// 使用------编译器保证所有情况都被处理
String handleResult(Result<int> result) {
  return switch (result) {
    Success(data: var d) => '成功: $d',
    Failure(error: var e) => '失败: $e',
    Loading()            => '加载中...',
  };  // 如果遗漏某个 case,编译器会直接报错!
}

// 实际应用场景:网络请求状态
sealed class ViewState {}

class Idle extends ViewState {}
class Loading extends ViewState {}
class Loaded<T> extends ViewState {
  final T data;
  Loaded(this.data);
}
class Error extends ViewState {
  final String message;
  Error(this.message);
}

5. Extension Types(扩展类型,Dart 3+)

在不创建新对象的前提下,给现有类型添加全新的类型接口。

dart 复制代码
// 给 String 创建类型安全的 ID 包装
extension type UserId(String id) {
  bool get isValid => id.length == 36;  // UUID 格式
}

extension type Email(String email) {
  bool get isValid => email.contains('@');
}

// 类型安全------不能混用
void createUser(UserId id, Email email) { /* ... */ }

var id = UserId('abc-123');
var email = Email('test@example.com');
createUser(id, email);  // OK
// createUser(email, id);  // 编译错误!即使底层都是 String

Flutter 进阶专题

1. 动画系统

Flutter 的动画系统分为 隐式动画 (简单自动)和 显式动画(完全控制)两类。

1.1 隐式动画(Animated* 系列)

只需改变属性值,Flutter 自动插值过渡。

dart 复制代码
class AnimatedBox extends StatefulWidget {
  @override
  State<AnimatedBox> createState() => _AnimatedBoxState();
}

class _AnimatedBoxState extends State<AnimatedBox> {
  double _size = 100;
  Color _color = Colors.blue;
  double _radius = 0;

  void _toggle() {
    setState(() {
      _size = _size == 100 ? 200 : 100;
      _color = _color == Colors.blue ? Colors.red : Colors.blue;
      _radius = _radius == 0 ? 50 : 0;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        AnimatedContainer(
          duration: const Duration(milliseconds: 500),
          curve: Curves.easeInOut,
          width: _size,
          height: _size,
          decoration: BoxDecoration(
            color: _color,
            borderRadius: BorderRadius.circular(_radius),
          ),
        ),
        ElevatedButton(onPressed: _toggle, child: const Text('切换')),
      ],
    );
  }
}

隐式动画全家桶:

Widget 用途
AnimatedContainer 尺寸、颜色、边距、圆角过渡
AnimatedOpacity 透明度过渡
AnimatedPadding 内边距过渡
AnimatedAlign 对齐位置过渡
AnimatedPositioned Stack 内的位置过渡
AnimatedSwitcher Widget 切换过渡
AnimatedDefaultTextStyle 文字样式过渡
TweenAnimationBuilder 自定义属性插值
1.2 显式动画(AnimationController)

需要精确控制动画生命周期时使用。

dart 复制代码
class ExplicitAnimationDemo extends StatefulWidget {
  @override
  State<ExplicitAnimationDemo> createState() => _ExplicitAnimationDemoState();
}

class _ExplicitAnimationDemoState extends State<ExplicitAnimationDemo>
    with SingleTickerProviderStateMixin {  // 提供 vsync
  late final AnimationController _controller;
  late final Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,  // 来自 SingleTickerProviderStateMixin
    );

    // 定义动画曲线
    _animation = CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    );

    // 添加重复
    _controller.repeat(reverse: true);
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animation,
      builder: (context, child) {
        return Transform.scale(
          scale: _animation.value,
          child: child,
        );
      },
      child: Container(width: 100, height: 100, color: Colors.blue),
    );
  }
}
1.3 Hero 动画(页面间共享元素过渡)
dart 复制代码
// 列表页
Hero(
  tag: 'image-$id',
  child: Image.network(url, width: 80, height: 80),
)

// 详情页(同一个 tag)
Hero(
  tag: 'image-$id',
  child: Image.network(url, width: 300, height: 300),
)

// 跳转时自动产生飞入效果
Navigator.push(context, MaterialPageRoute(...));
1.4 交错动画
dart 复制代码
// 多个动画按时间间隔依次播放
class StaggeredAnimation extends StatefulWidget { /* ... */ }

// 在 initState 中
_controller = AnimationController(duration: Duration(seconds: 2), vsync: this);

// 各动画在不同时间段生效
_opacity = Tween<double>(begin: 0, end: 1).animate(
  CurvedAnimation(parent: _controller, curve: Interval(0, 0.3, curve: Curves.easeIn)),
);
_translate = Tween<double>(begin: -100, end: 0).animate(
  CurvedAnimation(parent: _controller, curve: Interval(0.3, 0.7, curve: Curves.easeOut)),
);
_scale = Tween<double>(begin: 0.5, end: 1).animate(
  CurvedAnimation(parent: _controller, curve: Interval(0.7, 1.0, curve: Curves.elasticOut)),
);

2. 手势系统

Flutter 的手势识别基于 竞技场(Gesture Arena) 机制,多个手势识别器竞争,胜者获得事件流。

2.1 基础手势
dart 复制代码
GestureDetector(
  // 点击
  onTap: () => print('单击'),
  onDoubleTap: () => print('双击'),
  onLongPress: () => print('长按'),

  // 缩放
  onScaleStart: (details) => print('缩放开始'),
  onScaleUpdate: (details) => print('缩放比例: ${details.scale}'),
  onScaleEnd: (details) => print('缩放结束'),

  // 拖拽
  onPanStart: (details) => print('拖拽开始'),
  onPanUpdate: (details) {
    // details.delta ------ 手指移动增量
    // details.globalPosition ------ 全局坐标
  },
  onPanEnd: (details) => print('拖拽结束'),

  // 滑动
  onHorizontalDragStart: (d) {},
  onHorizontalDragUpdate: (d) {},
  onHorizontalDragEnd: (d) {},

  child: Container(width: 200, height: 200, color: Colors.blue),
)
2.2 手势竞技场原理

当屏幕上有多个手势识别器时(如 PageView 内的水平拖拽 vs 列表的垂直滚动),Flutter 不会同时触发它们,而是让它们进入"竞技场"竞争:

markdown 复制代码
用户手指按下
      │
      ▼
所有候选手势识别器进入竞技场
      │
      ▼
手指移动 → 各识别器分析:符合我的模式吗?
      │
   ┌──┴──┐
  获胜    退出(被淘汰)
   │
   ▼
独占事件流直到手指抬起
dart 复制代码
// 示例:可滑动删除的列表项
Dismissible(
  key: Key(item.id),
  direction: DismissDirection.horizontal,
  // Dismissible 的水平拖拽和 ListView 的垂直滚动
  // 会在竞技场中竞争,Flutter 根据滑动方向自动判断
  onDismissed: (direction) => removeItem(item),
  child: ListTile(title: Text(item.name)),
)
2.3 自定义手势识别
dart 复制代码
// 创建三指点击手势
class TripleTapGestureRecognizer extends TapGestureRecognizer {
  TripleTapGestureRecognizer({super.debugOwner}) {
    super.onTapDown = null; // 禁用默认行为
  }
}

// 注册自定义手势
RawGestureDetector(
  gestures: {
    TripleTapGestureRecognizer:
        GestureRecognizerFactoryWithHandlers<TripleTapGestureRecognizer>(
      () => TripleTapGestureRecognizer(),
      (recognizer) {
        recognizer.onTap = () => print('三指点击');
      },
    ),
  },
  child: Container(/* ... */),
)

3. 平台通道(Platform Channels)

当需要调用原生 API(相机、蓝牙、GPS)时,通过平台通道与原生代码通信。

scss 复制代码
┌──────────────────────────────────────┐
│            Flutter (Dart)             │
│  MethodChannel('com.app/channel')     │
│     .invokeMethod('getBatteryLevel') │
└──────────────────┬───────────────────┘
                   │ 二进制消息(编解码)
                   ▼
┌──────────────────────────────────────┐
│            平台层 (Kotlin/Swift)       │
│  MethodChannel(同名字符串)             │
│     .setMethodCallHandler { ... }    │
└──────────────────────────────────────┘

Dart 端:

dart 复制代码
import 'package:flutter/services.dart';

class BatteryService {
  static const _channel = MethodChannel('com.example/battery');

  static Future<int> getBatteryLevel() async {
    try {
      final result = await _channel.invokeMethod('getBatteryLevel');
      return result as int;
    } on PlatformException catch (e) {
      print('平台异常: ${e.message}');
      return -1;
    }
  }
}

Android 端(Kotlin,MainActivity.kt):

kotlin 复制代码
class MainActivity: FlutterActivity() {
    private val CHANNEL = "com.example/battery"

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
            .setMethodCallHandler { call, result ->
                if (call.method == "getBatteryLevel") {
                    val level = getBatteryLevel()
                    result.success(level)
                } else {
                    result.notImplemented()
                }
            }
    }

    private fun getBatteryLevel(): Int {
        val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
        return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
    }
}

iOS 端(Swift,AppDelegate.swift):

swift 复制代码
@main
@objc class AppDelegate: FlutterAppDelegate {
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        let controller = window?.rootViewController as! FlutterViewController
        let channel = FlutterMethodChannel(
            name: "com.example/battery",
            binaryMessenger: controller.binaryMessenger
        )
        channel.setMethodCallHandler { call, result in
            if call.method == "getBatteryLevel" {
                UIDevice.current.isBatteryMonitoringEnabled = true
                let level = Int(UIDevice.current.batteryLevel * 100)
                result(level)
            } else {
                result(FlutterMethodNotImplemented)
            }
        }
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}
3.1 EventChannel(原生向 Flutter 推送事件流)
dart 复制代码
// Dart 端:监听原生推送的事件流
static const _eventChannel = EventChannel('com.example/events');
static Stream<int> get batteryStream {
  return _eventChannel.receiveBroadcastStream().map((e) => e as int);
}

4. 自定义绘制(CustomPainter)

当标准 Widget 无法满足设计需求时,用 CustomPainter 绘制任意图形。

dart 复制代码
class RingChartPainter extends CustomPainter {
  final double progress;
  RingChartPainter({required this.progress});

  @override
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
    final radius = size.width / 2 - 10;

    // 背景圆环
    final bgPaint = Paint()
      ..color = Colors.grey.shade300
      ..style = PaintingStyle.stroke
      ..strokeWidth = 12;
    canvas.drawCircle(center, radius, bgPaint);

    // 进度圆环
    final progressPaint = Paint()
      ..color = Colors.blue
      ..style = PaintingStyle.stroke
      ..strokeWidth = 12
      ..strokeCap = StrokeCap.round;
    canvas.drawArc(
      Rect.fromCircle(center: center, radius: radius),
      -3.14159 / 2,  // 从顶部开始
      3.14159 * 2 * progress,  // 扫描角度
      false,
      progressPaint,
    );

    // 中间文字
    final textPainter = TextPainter(
      text: TextSpan(
        text: '${(progress * 100).toInt()}%',
        style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
      ),
      textDirection: TextDirection.ltr,
    )..layout();
    textPainter.paint(
      canvas,
      center - Offset(textPainter.width / 2, textPainter.height / 2),
    );
  }

  @override
  bool shouldRepaint(RingChartPainter oldDelegate) =>
      oldDelegate.progress != progress;
}

// 使用
CustomPaint(
  size: const Size(200, 200),
  painter: RingChartPainter(progress: 0.75),
)

综合实战:天气查询 App

将前面学到的知识串联成一个完整的 CRUD 示例。

项目结构

bash 复制代码
weather_app/lib/
├── main.dart              # 入口
├── models/
│   └── weather.dart       # 数据模型
├── services/
│   └── weather_service.dart  # 网络请求
├── providers/
│   └── weather_provider.dart # 状态管理
├── pages/
│   ├── home_page.dart     # 首页(城市列表)
│   └── detail_page.dart   # 详情页
└── widgets/
    └── weather_card.dart  # 天气卡片组件

Step 1:数据模型

dart 复制代码
// models/weather.dart
class Weather {
  final String city;
  final double temperature;
  final String condition;   // sunny, cloudy, rainy
  final int humidity;
  final double windSpeed;

  const Weather({
    required this.city,
    required this.temperature,
    required this.condition,
    required this.humidity,
    required this.windSpeed,
  });

  factory Weather.fromJson(Map<String, dynamic> json) {
    return Weather(
      city: json['city'] as String,
      temperature: (json['temperature'] as num).toDouble(),
      condition: json['condition'] as String,
      humidity: json['humidity'] as int,
      windSpeed: (json['windSpeed'] as num).toDouble(),
    );
  }

  Map<String, dynamic> toJson() => {
    'city': city,
    'temperature': temperature,
    'condition': condition,
    'humidity': humidity,
    'windSpeed': windSpeed,
  };

  IconData get conditionIcon {
    return switch (condition) {
      'sunny'  => Icons.wb_sunny,
      'cloudy' => Icons.cloud,
      'rainy'  => Icons.umbrella,
      _        => Icons.help_outline,
    };
  }
}

Step 2:网络服务

dart 复制代码
// services/weather_service.dart
import 'package:dio/dio.dart';
import '../models/weather.dart';

class WeatherService {
  final Dio _dio = Dio(BaseOptions(
    baseUrl: 'https://api.weather.example.com',
    connectTimeout: const Duration(seconds: 5),
  ));

  Future<List<Weather>> fetchCities() async {
    final response = await _dio.get('/cities');
    final list = response.data as List<dynamic>;
    return list.map((e) => Weather.fromJson(e as Map<String, dynamic>)).toList();
  }

  Future<Weather> fetchDetail(String city) async {
    final response = await _dio.get('/weather/$city');
    return Weather.fromJson(response.data as Map<String, dynamic>);
  }
}

Step 3:状态管理

dart 复制代码
// providers/weather_provider.dart
import 'package:flutter/foundation.dart';
import '../models/weather.dart';
import '../services/weather_service.dart';

class WeatherProvider extends ChangeNotifier {
  final WeatherService _service = WeatherService();

  List<Weather> _cities = [];
  bool _loading = false;
  String? _error;

  List<Weather> get cities => List.unmodifiable(_cities);
  bool get loading => _loading;
  String? get error => _error;

  // 按条件搜索
  List<Weather> get hotCities =>
      _cities.where((w) => w.temperature >= 30).toList();
  List<Weather> get rainyCities =>
      _cities.where((w) => w.condition == 'rainy').toList();

  Future<void> loadCities() async {
    _loading = true;
    _error = null;
    notifyListeners();

    try {
      _cities = await _service.fetchCities();
    } catch (e) {
      _error = '加载失败: $e';
    } finally {
      _loading = false;
      notifyListeners();
    }
  }

  Future<void> refresh() async {
    await loadCities();
  }
}

Step 4:首页

dart 复制代码
// pages/home_page.dart
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('天气查询'),
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: () => context.read<WeatherProvider>().refresh(),
          ),
        ],
      ),
      body: Consumer<WeatherProvider>(
        builder: (context, provider, _) {
          if (provider.loading) {
            return const Center(child: CircularProgressIndicator());
          }
          if (provider.error != null) {
            return Center(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Text(provider.error!, style: TextStyle(color: Colors.red)),
                  ElevatedButton(
                    onPressed: provider.loadCities,
                    child: const Text('重试'),
                  ),
                ],
              ),
            );
          }
          return RefreshIndicator(
            onRefresh: provider.refresh,
            child: ListView.builder(
              itemCount: provider.cities.length,
              itemBuilder: (_, i) => WeatherCard(weather: provider.cities[i]),
            ),
          );
        },
      ),
    );
  }
}

Step 5:天气卡片组件

dart 复制代码
// widgets/weather_card.dart
class WeatherCard extends StatelessWidget {
  final Weather weather;
  const WeatherCard({super.key, required this.weather});

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
      child: InkWell(
        borderRadius: BorderRadius.circular(12),
        onTap: () => Navigator.push(
          context,
          MaterialPageRoute(
            builder: (_) => DetailPage(city: weather.city),
          ),
        ),
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Row(
            children: [
              Icon(weather.conditionIcon, size: 48, color: Colors.orange),
              const SizedBox(width: 16),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(weather.city,
                        style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                    Text('湿度: ${weather.humidity}%  风速: ${weather.windSpeed}m/s'),
                  ],
                ),
              ),
              Text('${weather.temperature}°C',
                  style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold)),
            ],
          ),
        ),
      ),
    );
  }
}

Step 6:入口整合

dart 复制代码
// main.dart
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => WeatherProvider()..loadCities(),
      child: const WeatherApp(),
    ),
  );
}

class WeatherApp extends StatelessWidget {
  const WeatherApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '天气查询',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

性能优化专题

1. const 构造函数------零成本优化

dart 复制代码
// ❌ 每次都创建新对象
Column(children: [
  Padding(padding: EdgeInsets.all(8), child: Text('Hi')),
])

// ✅ 编译时常量,永不重建
Column(children: const [
  Padding(padding: EdgeInsets.all(8), child: Text('Hi')),
])

原理const Widget 在编译时就被创建,Flutter 在 diff 时直接 === 引用比较,完全跳过子树遍历。

2. RepaintBoundary------隔离重绘区域

dart 复制代码
// 将频繁重绘的部分隔离开
RepaintBoundary(
  child: AnimatedWidgetThatRebuildsOften(),
)

// 典型场景:
// - 独立动画区域
// - 滚动列表中的视频播放器
// - 实时更新的图表

3. ListView 性能要点

dart 复制代码
// ✅ 懒加载构建(只创建可见项)
ListView.builder(
  itemCount: 10000,
  itemBuilder: (_, i) => ListTile(title: Text('Item $i')),
)

// ❌ 一次创建所有(内存爆炸)
ListView(children: List.generate(10000, (i) => Text('Item $i')))

// ✅ 固定高度的 item 性能更优
ListView.builder(
  itemExtent: 56,  // 固定每项高度,跳过布局计算
  itemBuilder: ...,
)

// ✅ 复杂列表用 itemExtent 或 prototypeItem
ListView.builder(
  prototypeItem: ListTile(title: Text('预估高度的模板')),
  itemBuilder: ...,
)

4. build 方法优化

dart 复制代码
// ❌ 在 build 中创建可变对象
@override
Widget build(BuildContext context) {
  var controller = TextEditingController();  // 每次 build 都创建!
  return TextField(controller: controller);
}

// ✅ 在 State 中创建,dispose 中释放
late final _controller = TextEditingController();

// ❌ build 中做复杂计算
@override
Widget build(BuildContext context) {
  var sorted = items.where((e) => e.active).toList()..sort();
  return ListView(children: sorted.map(...));
}

// ✅ 计算缓存到 State 中,仅在数据变化时更新
void _onDataChanged() {
  _filteredItems = items.where((e) => e.active).toList()..sort();
}

// ❌ 频繁重建的父 Widget 中创建闭包
ElevatedButton(
  onPressed: () => doSomething(id),  // 每次创建新闭包
)

// ✅ 把子 Widget 抽离为独立的 StatelessWidget

5. 性能分析工具

bash 复制代码
# Flutter DevTools------官方性能分析套件
flutter pub global activate devtools
flutter pub global run devtools

# 代码中插入性能标记
import 'dart:developer';

Timeline.startSync('load_data');
// ... 你的代码 ...
Timeline.finishSync();

测试专题

1. 单元测试(测试 Dart 逻辑)

dart 复制代码
// test/models/weather_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:weather_app/models/weather.dart';

void main() {
  group('Weather Model', () {
    test('fromJson 正确解析', () {
      final json = {
        'city': '北京',
        'temperature': 25.5,
        'condition': 'sunny',
        'humidity': 60,
        'windSpeed': 3.2,
      };

      final weather = Weather.fromJson(json);

      expect(weather.city, '北京');
      expect(weather.temperature, 25.5);
      expect(weather.condition, 'sunny');
    });

    test('conditionIcon 返回正确的图标', () {
      expect(Weather(condition: 'sunny', /*...*/).conditionIcon, Icons.wb_sunny);
      expect(Weather(condition: 'cloudy', /*...*/).conditionIcon, Icons.cloud);
    });

    test('toJson 与 fromJson 互逆', () {
      final original = Weather(city: '上海', temperature: 30, /*...*/);
      final restored = Weather.fromJson(original.toJson());
      expect(restored.city, original.city);
      expect(restored.temperature, original.temperature);
    });
  });
}

2. Widget 测试(测试 UI 行为)

dart 复制代码
// test/widgets/weather_card_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
import 'package:weather_app/widgets/weather_card.dart';
import 'package:weather_app/models/weather.dart';

void main() {
  testWidgets('WeatherCard 显示城市和温度', (tester) async {
    final weather = Weather(
      city: '杭州', temperature: 28, condition: 'sunny',
      humidity: 55, windSpeed: 2.5,
    );

    await tester.pumpWidget(
      MaterialApp(home: Scaffold(body: WeatherCard(weather: weather))),
    );

    // 验证文字显示
    expect(find.text('杭州'), findsOneWidget);
    expect(find.text('28°C'), findsOneWidget);

    // 验证图标
    expect(find.byIcon(Icons.wb_sunny), findsOneWidget);
  });

  testWidgets('点击卡片触发导航', (tester) async {
    final weather = Weather(/* ... */);

    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(body: WeatherCard(weather: weather)),
      ),
    );

    await tester.tap(find.byType(WeatherCard));
    await tester.pumpAndSettle();  // 等待动画完成

    // 验证导航到详情页
    // (具体验证取决于 DetailPage 的实现)
  });
}

3. Provider 的测试

dart 复制代码
// test/providers/weather_provider_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:weather_app/providers/weather_provider.dart';

void main() {
  test('加载城市数据后 hotCities 正确过滤', () async {
    final provider = WeatherProvider();
    // 这里需要 mock WeatherService,此处展示结构
    // 生产代码建议用 mockito 或 mocktail

    // provider._cities = [ ... ]
    // expect(provider.hotCities.length, expectedCount);
  });
}

4. 测试最佳实践

原则 说明
AAA 模式 Arrange(准备)→ Act(执行)→ Assert(断言)
group 嵌套 相关测试用 group() 组织
setUp / tearDown 每个 test 前后自动执行,用于初始化/清理
mock 外部依赖 网络、数据库等用 mock 隔离,推荐 mocktail
Widget 测试优先 Widget 测试比单元测试更能发现 UI 级 bug
Golden 测试 截图对比,防止 UI 意外变化

Dart 常用命令速查

bash 复制代码
# 创建新项目
dart create my_project          # Dart 纯后端/CLI 项目
flutter create my_app           # Flutter 项目

# 运行
dart run lib/main.dart
flutter run                     # 启动 Flutter App

# 热重载 / 热重启(Flutter)
r                               # 热重载(保持状态)
R                               # 热重启(重置状态)

# 格式化
dart format lib/
flutter format lib/

# 静态分析(lint)
dart analyze
flutter analyze

# 测试
dart test
flutter test

# 依赖管理
dart pub get                    # 安装依赖
dart pub upgrade                # 升级依赖
dart pub outdated               # 查看过时依赖

# 构建
flutter build apk               # Android APK
flutter build ios               # iOS IPA
flutter build web               # Web

文档持续更新中,欢迎反馈与补充。

相关推荐
货拉拉技术1 小时前
基于eBPF的主机安全探索与实践
后端
梨子同志1 小时前
Spring Security
后端
名字还没想好☜1 小时前
Spring Boot 全局异常处理:@ControllerAdvice 实战
java·spring boot·后端·spring·异常处理
还是鼠鼠2 小时前
AI掘金头条新闻系统 (Toutiao News)-缓存相关推荐新闻
后端·python·mysql·fastapi·web
Java内核笔记2 小时前
Spring Security 源码解析(八)方法安全授权与自定义扩展:@EnableMethodSecurity、AOP、SpEL 全链路
java·后端
逝水无殇2 小时前
C# 枚举(Enum)详解
开发语言·后端·c#
树獭叔叔2 小时前
从 Search 到 Extract:AI Agent 网页工具架构拆解
后端·agent·ai编程
梨子同志3 小时前
Nacos
后端
宠友信息3 小时前
Redis 内容社区源码架构优化与即时通讯数据一致性处理
spring boot·后端·websocket·mysql·uni-app