在Dart的Mobx中如何仅触发ObservableMap单个元素的响应?
问题描述
我希望Observer仅对ObservableMap中的单个元素变化做出响应,但调试发现修改任意单个元素时,所有元素对应的Observer都会触发响应。点击单个按钮时,两个Counter对应的Observer都会重建,如何实现仅触发被修改的ObservableMap元素对应的响应?
原MyStore代码
import 'package:mobx/mobx.dart'; part 'my_store.g.dart'; class MyStore = MyStoreBase with _$MyStore; abstract class MyStoreBase with Store { @readonly ObservableMap<int, int> _counters = ObservableMap<int, int>.of({1: 0, 2: 0}); @action void incrementCounter(int id) { _counters[id] = _counters[id]! + 1; } }
原Main代码
import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx_list_observation/my_store.dart'; import 'package:mobx/mobx.dart'; void main() { mainContext.config = mainContext.config.clone( isSpyEnabled: true, ); mainContext.spy(print); 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.deepPurple), useMaterial3: true, ), home: const MyHomePage(title: 'Mobx Test Home Age'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { final _counterStore = MyStore(); void incrementCounter1() { _counterStore.incrementCounter(1); } void incrementCounter2() { _counterStore.incrementCounter(2); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( 'You have incremented counter 1 this many times:', ), Observer( builder: (_) { print('Counter 1 rebuilt'); return Text( '${_counterStore.counters[1]}', style: Theme.of(context).textTheme.headlineMedium, ); }, ), const Text( 'You have incremented counter 2 this many times:', ), Observer( builder: (_) { print('Counter 2 rebuilt'); return Text( '${_counterStore.counters[2]}', style: Theme.of(context).textTheme.headlineMedium, ); }, ), ], ), ), floatingActionButton: Stack( children: <Widget>[ Align( alignment: Alignment.bottomRight, child: Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ FloatingActionButton( onPressed: incrementCounter1, tooltip: 'Increment counter 1', child: Icon(Icons.one_k), ), SizedBox(height: 16), FloatingActionButton( onPressed: incrementCounter2, tooltip: 'Increment counter 2', child: Icon(Icons.two_k), ), ], ), ), ], ), ); } }
原日志输出
flutter: reaction(START) Observer Observer constructed from: _MyHomePageState.build (package:mobx_list_observation/main.dart:64:13) flutter: Counter 1 rebuilt flutter: reaction(END after 2ms) Observer Observer constructed from: _MyHomePageState.build (package:mobx_list_observation/main.dart:64:13) flutter: reaction(START) Observer Observer constructed from: _MyHomePageState.build (package:mobx_list_observation/main.dart:76:13) flutter: Counter 2 rebuilt flutter: reaction(END after 0ms) Observer Observer constructed from: _MyHomePageState.build (package:mobx_list_observation/main.dart:76:13) flutter: action(START) MyStoreBase.incrementCounter flutter: action(END after 1ms) MyStoreBase.incrementCounter flutter: reaction(START) Observer Observer constructed from: _MyHomePageState.build (package:mobx_list_observation/main.dart:64:13) flutter: Counter 1 rebuilt flutter: reaction(END after 0ms) Observer Observer constructed from: _MyHomePageState.build (package:mobx_list_observation/main.dart:64:13) flutter: reaction(START) Observer Observer constructed from: _MyHomePageState.build (package:mobx_list_observation/main.dart:76:13) flutter: Counter 2 rebuilt flutter: reaction(END after 0ms) Observer Observer constructed from: _MyHomePageState.build (package:mobx_list_observation/main.dart:76:13)
问题原因
MobX的ObservableMap默认会在任何键值对发生变化时通知所有依赖它的Observer。你的代码中,每个Observer都直接访问了_counterStore.counters这个完整的ObservableMap,所以不管修改哪个键,所有监听该Map的Observer都会被触发重建。
解决方案
方法一:用Observable.value包装每个Map值
将ObservableMap的value类型改为Observable<int>,让每个计数器值成为独立的可观察对象,Observer只会监听对应键的Observable值,不会受其他键变化影响。
修改后的MyStore代码
import 'package:mobx/mobx.dart'; part 'my_store.g.dart'; class MyStore = MyStoreBase with _$MyStore; abstract class MyStoreBase with Store { @readonly ObservableMap<int, Observable<int>> _counters = ObservableMap<int, Observable<int>>.of({ 1: Observable(0), 2: Observable(0), }); @action void incrementCounter(int id) { _counters[id]!.value++; } }
修改后的UI Observer代码
// Counter 1的Observer Observer( builder: (_) { print('Counter 1 rebuilt'); return Text( '${_counterStore.counters[1]!.value}', style: Theme.of(context).textTheme.headlineMedium, ); }, ), // Counter 2的Observer Observer( builder: (_) { print('Counter 2 rebuilt'); return Text( '${_counterStore.counters[2]!.value}', style: Theme.of(context).textTheme.headlineMedium, ); }, ),
方法二:为每个计数器添加Computed属性
在Store中为每个计数器定义单独的@computed属性,Observer依赖这个computed属性,而computed只会在对应的Map键值变化时更新。
修改后的MyStore代码
import 'package:mobx/mobx.dart'; part 'my_store.g.dart'; class MyStore = MyStoreBase with _$MyStore; abstract class MyStoreBase with Store { @readonly ObservableMap<int, int> _counters = ObservableMap<int, int>.of({1: 0, 2: 0}); @computed int get counter1 => _counters[1]!; @computed int get counter2 => _counters[2]!; @action void incrementCounter(int id) { _counters[id] = _counters[id]! + 1; } }
修改后的UI Observer代码
// Counter 1的Observer Observer( builder: (_) { print('Counter 1 rebuilt'); return Text( '${_counterStore.counter1}', style: Theme.of(context).textTheme.headlineMedium, ); }, ), // Counter 2的Observer Observer( builder: (_) { print('Counter 2 rebuilt'); return Text( '${_counterStore.counter2}', style: Theme.of(context).textTheme.headlineMedium, ); }, ),
效果验证
修改后,点击按钮1只会触发Counter1的Observer重建,点击按钮2只会触发Counter2的Observer重建,符合预期。
内容的提问来源于stack exchange,提问作者Rsevero
相关产品推荐
相关产品推荐

