iT邦幫忙

2021 iThome 鐵人賽

DAY 22
0
Modern Web

Flutter web 的奇妙冒險系列 第 22

Day 22 | 狀態管理套件 MobX - 基本使用

昨天稍微提到了狀態管理及 MobX 的基本介紹那今天就要來說明 MobX 中的核心概念。

https://ithelp.ithome.com.tw/upload/images/20211005/2011290639ecTXdL3L.png

MobX 最重要的就是這三個東西: Observables、Actions、Reactions

Observables

就是我們昨天提到的被觀察者,也就是在這個程式中的 reactive-data ,當 Observables 被改變時會通知監聽這個Observable的觀察者。

Actions

則是我們實作「要如何更改 Observable 」 的方法,我們如果要變更 Observable 只能透過 Actions。

Reaction

reaction 會針對observables的任何變動做出回應,像是我們可以使用 when 讓我們某個 observables 變成特定的值時就做出額外的動作。

而其實MobX提供的Observer Widget 也算是 Reaction 的一種,因為他追蹤了observables 的變動當他有更新時就會重新build

在程式碼中他們長得像這樣:

import 'package:mobx/mobx.dart';

part 'counter.g.dart';

class Counter = CounterBase with _$Counter;

abstract class CounterBase with Store {
  @observable
  int value = 0;

  @action
  void increment() {
    value++;
  }
}

我們在一個 abstract class 中宣告了一個值及function 我們只要分別加上 @observable@action 這兩個decorator 就能完成這件事情。

當然你會想為什麼會有 with _$Counterpart 'counter.g.dart' 等等奇怪的東西,其實這些decorator 是為了讓 MobX_codegen 讓我們產生 observable 以及 action 的內部實作:

class Counter {
  Counter() {
    increment = Action(_increment);
  }

  final _value = Observable(0);
  int get value => _value.value;

  set value(int newValue) => _value.value = newValue;
  Action increment;

  void _increment() {
    _value.value++;
  }
}

如果不套用 MobX_codegen 的就要手動寫這些code。

但需要搭配codegen 算是MobX的缺點之一,代表你每次存檔後要等一段時間讓 codegen 將MobX的code產生出來,雖然時間很短但終究會有被打斷的感覺。

那我們就開始將MobX套入我們的專案吧

就在 pubspec.yaml 新增套件:

dependencies:
# ... 省略其他
    mobx: ^2.0.4
    flutter_mobx: ^2.0.2
# ... 省略其他

dev_dependencies:
	# ... 省略其他
	build_runner: ^2.1.4
    mobx_codegen: ^2.0.3
	# ... 省略其他

然後新增一個檔案todo_view_model.dart

把我們在 setState 的實作搬到這裡,然後將 List 改為 ObservableList ,這兩者差距其實在這個專案是看出不來的,最主要是差在說如果單純是 List 的話裡面的元素變化時並不會被監聽,除非是整個 List 有變化之類的。

import 'package:mobx/mobx.dart';
import 'package:todolist/model/todo_model.dart';

part 'todo_view_model.g.dart';

class TodoViewModel = _TodoViewModel with _$TodoViewModel;

abstract class _TodoViewModel with Store {
  @observable
  ObservableList<TodoModel> todoList =
      ObservableList.of([TodoModel(content: '123')]);

  @action
  void removeTodo(int hashCode) {
    todoList =
        ObservableList.of(todoList.where((todo) => todo.hashCode != hashCode));
  }

  @action
  void addTodo(String input) {
    todoList = ObservableList.of([...todoList, TodoModel(content: input)]);
  }

  @action
  void toggleStatus(int hashCode) {
    todoList = ObservableList.of(todoList.map((todo) {
      if (todo.hashCode == hashCode) {
        todo.isDone = !todo.isDone;
        return todo;
      } else {
        return todo;
      }
    }));
  }
}

然後在terminal 輸入:

flutter pub run build_runner watch

讓 MobX codegen 可以產生我們要的程式碼。

這樣我們就不需要使用 setState了在main.dart裡面就會變成:

final todoViewModel = TodoViewModel();
  void _handleAddNewTodo(String input) {
    todoViewModel.addTodo(input);
    _textEditingController.text = '';
  }

  void _handleRemoveTodo(int hashCode) {
    todoViewModel.removeTodo(hashCode);
  }

  void _handleToggleStatus(int hashCode) {
    todoViewModel.toggleStatus(hashCode);
  }

然後使用 Observer 包住我們要動態更新的部分

  Observer(
      builder: (_) {
        return Column(
          children: [
            // ... 省略
          ],
        );
      },
    );
  }

Computed

@computed 會對一個 observables 監聽後產生一個衍生的值,像是當我們想取得不同狀態的 todoList ,就可寫成這樣:

@computed
ObservableList<TodoModel> get completedTodos =>
      ObservableList.of(todoList.where((todo) => todo.isDone == true));

@computed
ObservableList<TodoModel> get pendingTodos =>
      ObservableList.of(todoList.where((todo) => todo.isDone != true));

程式碼:

https://github.com/zxc469469/flutter_todo_list/tree/Day21

今天大概說明了 MobX 基本用法,那這個小專案也差不多到這裡結束。

明天開始來串接API的部分


上一篇
Day 21 | 狀態管理套件 MobX - 到底什麼是狀態管理
下一篇
Day 23 | 在Flutter裡串接restful api - 我不使用HttpClient了 jojo
系列文
Flutter web 的奇妙冒險30

尚未有邦友留言

立即登入留言