一、为什么需要不可变数据和纯函数
想象一下你正在开发一个购物车功能。如果购物车的数据可以被随意修改,可能会出现这样的情况:A 页面显示购物车有 3 件商品,B 页面却显示有 5 件,因为某个地方偷偷修改了数据。这就是可变数据带来的问题。
不可变数据就像拍照 - 每次修改都会生成新的"照片",原始数据永远不会变。这样做的好处是:
- 调试更容易:数据流动像流水线一样清晰
- 并发更安全:多个操作不会互相干扰
- 代码更可预测:输入相同输出就一定相同
纯函数则是指:
- 只依赖输入参数
- 不修改外部状态
- 相同的输入永远得到相同的输出
// 技术栈:Dart
// 不纯的函数示例 - 依赖外部变量且会修改它
var discount = 0.9; // 外部状态
double applyDiscount(double price) {
return price * discount; // 依赖外部变量
}
// 纯函数示例 - 只依赖参数,不修改外部状态
double applyPureDiscount(double price, double discount) {
return price * discount; // 只使用传入的参数
}
二、Dart 中实现不可变数据
Dart 中实现不可变性有几种常用方法:
- 使用 final/const 声明变量
- 使用不可变集合
- 使用 freeze 模式(通过 package)
// 技术栈:Dart
// 1. 基本不可变变量
final user = User('Alice', 30); // 运行时常量
const maxRetries = 3; // 编译时常量
// 2. 不可变集合
final numbers = const [1, 2, 3]; // 完全不可变列表
final config = Map.unmodifiable({'theme': 'dark'}); // 不可变Map
// 3. 使用 freeze package (需要添加依赖)
import 'package:freezed_annotation/freezed_annotation.dart';
@freezed
class User with _$User {
const factory User(String name, int age) = _User;
}
void main() {
final user = User('Bob', 25);
// user.age = 26; // 编译错误,不可修改
final newUser = user.copyWith(age: 26); // 正确做法是创建新实例
}
三、纯函数的实际应用场景
纯函数特别适合处理业务逻辑和数据处理。来看几个典型场景:
- 价格计算
- 数据转换
- 状态管理
// 技术栈:Dart
// 场景1:价格计算
double calculateTotal(List<double> prices, double taxRate) {
final subtotal = prices.fold(0, (sum, price) => sum + price);
return subtotal * (1 + taxRate); // 纯函数:只依赖参数
}
// 场景2:数据转换
List<String> formatUserNames(List<User> users) {
return users.map((user) => '${user.name} (${user.age})').toList();
}
// 场景3:Redux中的reducer
AppState reducer(AppState state, dynamic action) {
if (action is AddItemAction) {
return AppState(
[...state.items, action.newItem], // 创建新数组而不是修改
state.selectedItem
);
}
return state;
}
四、结合Flutter的状态管理
在Flutter中,不可变数据与纯函数能极大简化状态管理。以Riverpod为例:
// 技术栈:Dart + Flutter
// 不可变状态类
@immutable
class CartState {
final List<Item> items;
final double discount;
const CartState(this.items, this.discount);
CartState copyWith({List<Item>? items, double? discount}) {
return CartState(
items ?? this.items,
discount ?? this.discount
);
}
}
// 纯函数的notifier
class CartNotifier extends StateNotifier<CartState> {
CartNotifier() : super(const CartState([], 0.0));
// 添加商品(纯函数方式)
void addItem(Item newItem) {
state = state.copyWith( // 创建新状态而不是修改
items: [...state.items, newItem]
);
}
// 计算总价(纯函数)
double get total {
return state.items.fold(0, (sum, item) => sum + item.price) *
(1 - state.discount);
}
}
五、性能优化技巧
有人担心不可变数据会带来性能问题,其实有优化方案:
- 使用 const 构造
- 采用结构共享
- 合理设计数据结构
// 技术栈:Dart
// 1. 尽可能使用const
const defaultSettings = Settings(theme: 'light'); // 只创建一次
// 2. 结构共享示例
final original = ['a', 'b', 'c'];
final updated = [...original, 'd']; // 共享'a','b','c'的内存
// 3. 高效的数据结构设计
@freezed
class ProductList with _$ProductList {
const factory ProductList({
@Default(<Product>[]) List<Product> items,
String? lastUpdated,
}) = _ProductList;
// 添加产品的纯函数方法
ProductList addProduct(Product newProduct) {
return copyWith(
items: [...items, newProduct],
lastUpdated: DateTime.now().toString()
);
}
}
六、常见问题与解决方案
在实际开发中会遇到一些典型问题:
- 深拷贝问题
- 循环引用处理
- 与第三方库的兼容
// 技术栈:Dart
// 1. 深拷贝解决方案
final original = { 'user': User('Alice', {'age': 30}) };
final copy = jsonDecode(jsonEncode(original)); // 通过序列化实现深拷贝
// 2. 循环引用处理
@freezed
class TreeNode with _$TreeNode {
const factory TreeNode(
String value,
@JsonKey(toJson: _childrenToJson, fromJson: _childrenFromJson)
List<TreeNode>? children,
) = _TreeNode;
// 自定义序列化逻辑处理循环引用
static List<Map<String, dynamic>>? _childrenToJson(List<TreeNode>? children) {
return children?.map((e) => e.toJson()).toList();
}
static List<TreeNode>? _childrenFromJson(List<dynamic>? json) {
return json?.map((e) => TreeNode.fromJson(e)).toList();
}
}
// 3. 与可变第三方库交互
final mutableList = thirdPartyLib.getMutableList();
final immutableCopy = List.unmodifiable(mutableList); // 转为不可变
七、最佳实践总结
经过多个项目实践,我总结出以下经验:
- 小步快跑:先从关键业务逻辑开始应用
- 分层处理:UI层可以可变,核心逻辑保持不可变
- 工具辅助:使用freezed、built_value等包
- 团队约定:建立代码规范文档
// 技术栈:Dart
// 分层架构示例
// 核心层 - 完全不可变
@freezed
class CoreState with _$CoreState {
const factory CoreState({
required AuthInfo auth,
required AppConfig config,
}) = _CoreState;
}
// UI层 - 允许局部可变
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
// 转换层 - 纯函数处理数据转换
UiModel convertToUiModel(CoreState state) {
return UiModel(
userName: state.auth.user.name,
theme: state.config.theme
);
}
记住,函数式编程不是非黑即白的选择。在Dart中,你可以灵活地混合使用面向对象和函数式风格,找到最适合你项目的平衡点。
评论