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的部分


<<:  async/await 连体婴

>>:  Day20 让电脑透过数据机和有线、无线网路传递讯息

Day 3 - 条件式

条件式就是小学常写的造样造句:如果...就(否则)...的概念。 这边会介绍几种常用的条件式语句 i...

新新新手阅读 Angular 文件 - ngStyle - Day16

本文内容 本篇内容为阅读官方文件 ngStyle 的笔记内容。 ngStyle 使用时机 昨天 Da...

JS中的排序法_上

在Day7时候有提到排序法的简介,并且简介常见的6个演算法,在Icebear学习5天JS语法之後,在...

MITRE Engenuity ATT&CK Evaluations 测试报告

才刚提到趋势科技去年在 MITRE Engenuity 的 ATT&CK Evaluatio...

Day 22 菜鸟的 helm 纪录 - 进阶篇

在昨天介绍了Helm这一工具,那们今天就来介绍如何建立属於自己的Helm repo吧!! ps.如果...