欢迎关注我的公众号:OpenFlutter,感恩。
一个纯函数是可预测的,并且没有副作用的函数。
dart
int increase(int x) {
int y = x + 1;
return y;
}
如果参数值相同,它总是会产生相同的结果。
并非总是能够写出纯函数。一些功能,例如 API 请求、本地和远程数据库连接,默认就带有副作用。
然而,我最近发现一个相当普遍的场景,即为模型添加不可变性会自动净化一个函数。
我以国际跳棋游戏为例,但这种情况显然不局限于游戏,它可以是任何应用功能。
假设我们有一个国际跳棋游戏界面,其架构如下:
- GameView --- 在屏幕上显示游戏;依赖于 GameViewModel;
- GameViewModel --- 维护状态和展示逻辑;依赖于 GameService;
- GameService --- 包含游戏逻辑;
- GameModel --- 是游戏状态的模型对象。
GameView 目前与我们无关。这里有一个来自另一篇文章的 GIF 动图,只是为了说明它可能是什么样子。

让我们看一下(简化后的)GameModel:
dart
class GameModel{
List<List<CheckersPiece?>> board;
CheckersPiece? selectedPiece;
const GameModel({
required this.board,
this.selectedPiece,
});
}
实现细节并不重要。不过,请注意它(GameModel
)是可变的。
在 GameViewModel 中,我们有一个 select
方法,当用户点击棋盘时,该方法会被调用:
dart
GameService _gameService;
late GameModel _state;
void select(int row, int col) {
_gameService.selectPiece(_state, row, col);
update(); // 或者如果我们的 ViewModel是一个ChangeNotifier,就调用notifyListeners。
}
在 GameService 中,我们有一个 selectPiece
方法,它有副作用(不是纯函数):
dart
void selectPiece (GameModel state, int row, int col) {
...
state.selectedPiece = state.board[row][col];
}
它(selectPiece
方法)隐式地更新了状态,当我们查看 GameViewModel
时,我们看不到这一点。(我们知道这一点是因为这个例子特意简化了 😎,但在更复杂的代码中,我们可能并不知道。)
我们可以尝试让代码更明确地表达这一点。
GameViewModel:
dart
void select(int row, int col) {
var newState = _gameService.selectPiece(_state, row, col);
_state = newState;
update();
}
GameService
dart
GameModel selectPiece (GameModel state, int row, int col) {
...
state.selectedPiece = state.board[row][col];
return state;
}
但是我们实际上做得更糟了,因为现在 selectPiece
方法假装 是纯函数,但它修改了 state
参数并将其作为新结果返回。
如果我们在 GameViewModel
中想要比较新旧状态,它们将永远相等,因为它们引用的是同一个对象。
ini
void select(int row, int col) {
var newState = _gameService.selectPiece(_state, row, col);
...
if (_state == newState) { //永远都是 true !!!
return;
}
_state = newState;
update();
}
那解决方案在哪里呢?
很简单,就是让我们的 GameModel
不可变:
dart
class GameModel{
final List<List<CheckersPiece?>> board;
final CheckersPiece? selectedPiece;
const GameModel({
required this.board,
this.selectedPiece,
});
GameModel copyWith({
List<List<CheckersPiece?>>? board,
CheckersPiece? selectedPiece,
}) {
return GameModel(
board: board ?? this.board,
selectedPiece: selectedPiece,
);
}
}
现在,我们的 GameService 无法修改 state
参数,只能被迫创建一个新对象:
dart
GameModel selectPiece (GameModel state, int row, int col) {
...
var newState = state.copyWith(state.board, state.board[row][col]);
return newState;
}
selectPiece
方法现在不再有副作用,变得像天使一样纯粹。

我们 GameViewModel 中的 select
方法没有改变,但它不再具有误导性了。
dart
void select(int row, int col) {
var newState = _gameService.selectPiece(_state, row, col);
...
if (_state == newState) { //不再总是 true !!!
return;
}
_state = newState;
update();
}
好了,就这些。
让你的模型不可变,然后快乐地编程吧!
如果你觉得这篇文章值得一读,那就找到👏按钮,然后尽可能久地长按它。