You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 17:40:01