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 事件 │ │ 定时器 │ │
│ └─────────┘ └─────────┘ │
└─────────────────────────────────┘
事件循环的执行顺序:
- 执行
main()中的同步代码 - 检查 Microtask Queue,逐个执行直到队列为空
- 从 Event Queue 取一个事件执行
- 回到步骤 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 同时继承 Swimmer 和 Flyer。
传统解决方案及缺陷:
| 方案 | 缺陷 |
|---|---|
| 多层继承 | 类层次爆炸,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 来组合行为(如
SingleTickerProviderStateMixin、AutomaticKeepAliveClientMixin)。
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 用零值(""、0、false、nil)来代替 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 |
数据表格 |
学习路线建议
- Dart 基础(1-2 天):变量、函数、集合、OOP、空安全
- Dart 异步(1 天):Future、async/await、Stream
- Flutter 环境搭建(半天):SDK + IDE + 模拟器
- Flutter 基础 Widget(2-3 天):Text、Container、Row、Column、Stack
- 列表与导航(1-2 天):ListView、GridView、Navigator
- 状态管理(2-3 天):setState → Provider → Riverpod
- 网络与持久化(2-3 天):dio、shared_preferences、sqflite
- 实战项目(持续):做一个完整的 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
文档持续更新中,欢迎反馈与补充。