概述

之前的学习中断断续续的接触过angular、react、flutter等前端的一些框架,这些都是目前比较流行的单页面应用的前端框架,angular最初在刚出来的时候号称是mvvm的框架,可以实现模型和视图的双向绑定,也就是说数据刷新之后视图也会立即更新,不过后续出来的一些前端框架如react、flutter数据都是单向流动的,因此这些框架中当数据刷新之后,视图并不会主动刷新,而是要通过setState来实现页面的更新。不过react、flutter原生提供的状态管理的机制实在有些太简单,使用起来也比较复杂,有(dom本身是具有父子结构的,子节点状态的更新要通过父节点传入props,父节点依赖子节点的更新同样需要使用回调函数来实现,一旦传递的层级深了,这种原生框架自带的状态管理的机制就不是那么好用了),因此催生了许多的状态管理的工具,react里面比较出名的状态管理的工具应该就是redux了,而flutter社区比较活跃的状态管理的诸如bloc、getx等,通过学习感觉getx是一个非常不错的状态管理框架(尽管有些说getx比较乱),尤其是对于后端开发工程师来说是比较有好的,之所以这么说是getx实现了DI的机制,这就有点类似于前端的框架中的spring了。

详解

getx提供了几种能力:

  • 状态管理:关于状态管理,我们可以理解为前端的数据库,通过这个前端内置的数据库,我们可以方便的在任何地方完成数据的更新(否则只能通过props、回调等更新)。
  • 依赖注入
  • 路由管理

状态管理&依赖注入

我们将通过一个例子实现来了解这些能力,:

  • 创建controller:这里的controller是用来管理状态的,如果把状态管理比作前端的数据库,那么controller就可以比作状态管理的一张表,实现了更精细的管理数据的能力,一般来说不同的业务对象就对应了不同的controller。controller在完成数据的更新之后,如果需要刷新视图,必须要调用update()方法来知会视图进行更新。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import 'package:get/get_state_manager/get_state_manager.dart';

    class TapController extends GetxController {
    int _x = 0;

    // 这里的get是一个关键字,表示我们可以通过x获取_x的数据
    int get x => _x;

    void increaseX() {
    _x++;
    // 这一步是必须要的,其类似于一个发出通知,getbuilder类似于一个listener,
    // 只有这样getbuilder才会接到通知,进而触发视图的更新
    update();
    print(_x);
    }
    }
  • 对象注入:我们在需要处理状态的地方完成对象的注入,这里其实就是controller对象的创建和初始化(初始化尤其重要,一般页面加载就要完成的动作)。

1
TapController tapController = Get.put(TapController());
  • 调用对象方法完成状态的更新:这一步比较简单,就是单纯的对方方法调用即可。

    1
    2
    3
    4
    onTap: () {
    // 完成状态的更新
    tapController.increaseX();
    },
  • 使用getbuilder完成视图的渲染:这里视图渲染有很多种方式,如果要使用getbuilder我们需要将待更新的视图用其包裹起来,getbuilder的使用要求我们传递controller对象(不是类),对象中包含了对应的状态,这样我们就可以在controller返回的视图里面使用状态信息,从而完成页面的展示。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 这里是固定的写法,GetBuilder接收一个泛型Controller
    GetBuilder<TapController>(
    // 这里是视图构建的时候需要传入的controller实例,不过由于这里都是单例,因此我们可以传入 _ ,并且直接使用我们注入的TapController对象
    builder: (tapController) {
    return Container(
    height: 100,
    width: double.maxFinite,
    margin: EdgeInsets.all(10),
    decoration: BoxDecoration(
    color: Colors.blue,
    ),
    child: Center(child: Text(tapController.x.toString())),
    );
    },
    ),

最后展示一下完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import 'package:chat/controller/tap_controller.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';

class TestMyHomePage extends StatelessWidget {
const TestMyHomePage({super.key});

@override
Widget build(BuildContext context) {
// 这一步是用来完成controller的装配和初始化
TapController tapController = Get.put(TapController());
return Scaffold(
body: Container(
// 这样可以占据整个页面
width: double.maxFinite,
height: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// 这里是固定的写法,GetBuilder接收一个泛型Controller
GetBuilder<TapController>(
// 这里是视图构建的时候需要传入的controller实例
builder: (tapController) {
return Container(
height: 100,
width: double.maxFinite,
margin: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.blue,
),
child: Center(child: Text(tapController.x.toString())),
);
},
),
GestureDetector(
onTap: () {
// 完成状态的更新
tapController.increaseX();
},
child: Container(
height: 100,
width: double.maxFinite,
margin: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.blue,
),
child: Center(child: Text("tap")),
),
)
],
),
),
);
}
}

路由

上面我们看到了getx提供的状态管理和依赖注入的示例,接下来我们看一下通过getx实现路由的过程:

  • 配置main.dart:getx对状态的管理是不需要做什么特殊配置的,只需要我们的controller继承自GetXController,但是路由的话就需要我们新增配置项,将MatericalApp替换为GetMaterialApp

  • 路由跳转:直接通过如下代码,实现页面的跳转,需要注意的是to方法接收的参数是一个箭头函数。

    1
    2
    3
    4
    onTap: () {
    // 完成状态的更新
    Get.to(() => TestFirstPage());
    },
  • 路由返回:如果我们需要从跳转过的页面返回到跳转之前的页面,我们当然可使用Get.to(),不过路由本身使用的是栈的数据结构来存放的,正确的做法是我们使用Get.back()方法来返回到之前的页面:

    1
    2
    3
    onPressed: () {
    Get.back();
    },

跨页面状态访问

  • 获取controller: 对于状态数据的访问,我们都需要依赖controller,并且controller只应该创建一次,当我们完成controller的创建之后就可以在其他的地方查找并使用这个对象这是依赖注入提供给我们的能力
    1
    TapController controller = Get.find();

如上,我们只需要在使用到controller的地方获取到对应的对象就可以了。如果我们仅仅是需要访问状态的信息也可以将上述代码写作:Get.find(),这样就不用再创建一个controller变量了。

上面我们看到了状态数据跨页面的访问,有时候我们需要在新的页面实现状态的更新,并实现即时可视,这样我们要做的操作和上面也是类似的,这里就不废话了,不过需要注意的是状态的更新必须要使用:update()、GetBuilder

变型写法

  • 变量定义: 上文我们的状态是int类型的,我们需要在更新了之后主动调用update()方法才可以从其他视图获取最新的数据。还有一种不需要主动调用update的方法,我们在controller里面新增如下变量:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    RxInt _y = 0.obs;
    RxInt get y => _y;

    void increaseY() {
    _y.value++;
    }

    void decreaseY() {
    _y.value--;
    }
  • 视图中获取变量:上面我们在controller里面定义obs对象,如果想要视图每次都获取最新的数据的话,就需要使用Obx将其包裹起来,具体如下:

    1
    2
    3
    4
    5
    6
    7
    8
    Obx(() => Container(
    margin: EdgeInsets.all(10),
    width: double.maxFinite,
    height: 100,
    decoration: BoxDecoration(color: Colors.blue),
    child: Center(
    child: Text("y value " + controller.y.value.toString())),
    )),

依赖注入管理

上面的代码中我们看到controller可以在使用的时候通过put来完成注入,不过在controller多起来的时候,这种方式就会显得有些蹩脚了,我们需要一个地方来集中完成依赖注入,当前存在两种方式:

  • 继承Bindings实现 dependencies 方法,并在该方法中完成对象的装配:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import 'package:get/instance_manager.dart';

    class InitDep extends Bindings {
    @override
    void dependencies() {
    // TODO: implement dependencies
    Get.lazyPut(() => TapController());
    }
    }

    上面我们在完成了controller的装配之后,还需要在GetMaterialApp的一个属性中调用才可以:

    1
    2
    3
    4
    GetMaterialApp(
    initialBinding: InitDep(),
    .......
    )
  • 无需通过继承Bindings,可以通过异步函数来完成初始化,并在main函数中调用:

    • 定义异步函数

      1
      2
      3
      Future<void> init() async {
      Get.lazyPut(() => TapController());
      }
    • main函数中完成对象的初始化

      1
      2
      3
      4
      5
      Future<void> main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await init();
      runApp(const MyApp());
      }

完成上面两步之后我们就可以继续愉快的使用controller来完成状态的更新及处理了

小结

详细代码请查看

getx教学视频