100行代码自建Flutter状态管理库

1,322 阅读4分钟

Flutter状态管理库有许多,到底哪个才是最好最合适的呢?许多库都看起像有一些黑魔法,怎么才能完全掌握他们呢?

求人不如求己,这篇文章我们来写一个自己的状态管理库吧,完成之后就会对状态管理有新的理解。

只需要100行代码,我们就能有一个完整的状态管理库,完成像Riverpod,GetX那样的响应功能。而且没有黑魔法,一切都很好理解。听起来是不是不错?

思路

说起状态管理,其实最重要的一点是要想清楚状态的变化怎么传递。

有些方案用Stream来传递状态(flutter_bloc, flutter_redux, GetX),有些方案依赖Flutter自带的Widget(provider),还有些方案自己写了监听模式(riverpod, mobx)。

但回到最基本的概念,想象一个Counter程序,我们需要什么?

  • 需要一个东西来存一个整数。
  • 如果整数改变,改变一个Widget 。

听起来是不是需要建立一个依赖关系,类似这样的:

image.png

表达依赖关系最简单的数据结构是什么?有向图呀!

  • 节点可以储存状态。状态可以是integer, object, 或者是Widget。
  • 边表示状态传递的方向。

创建图和节点

那我们就写个有向图吧!

我们把节点叫做Creator,把图叫做Ref,然后用一个邻接矩阵来存储边。那么图Ref大概是这个样子:

image.png

接下来定义节点Creator。为了让节点之间能建立依赖关系,我们把图Ref传给它。同时为了安全,我们把Creator定义成const的,用一个内部的结构Element来存储状态。类似这样的:

image.png

好了,尝试构造两个节点看看:

image.png

这里我们通过 Ref.watch来创建边,这样一个Creator就可以依赖多个Creator了。下面我们来实现它:

image.png

是不是很简单?加几个API来更新和传递状态吧:

image.png

最后实现一下Element的状态更新函数:

image.png

好啦,我们的有向图就搞定了!让我们在DartPad里试一下吧(链接):

image.png

更新Widget

有了这个有向图,我们只需要用它来更新Widget就好了。这里需要做三个事情:

  1. 在Flutter里拿到Ref

  2. 按需更新Widget。

  3. 适时销毁Creator释放资源。

其实都不难。

  1. 在Flutter里拿到Ref

如果你用过 InheritedWidget的话就很简单:

image.png

现在只要用了CreatorGraph,然后就可以通过 context.ref 访问Ref:

image.png

  1. 按需更新Widget。

新建一个 StatefulWidget来存Creator<Widet>。同时提供一个builder参数让用户用起来容易一点。如果依赖的状态改变了,我们就setState,让Flutter负责更新Widget。

image.png

  1. 适时销毁Creator释放资源。

如果一个Creator被销毁了,我们需要检查一下它依赖的Creator,如果他们没用了,需要同时被销毁。

image.png

好了,都搞定了!总共102行代码加上一些注释。来写一个Counter程序吧(DartPad链接):

image.png

看看效果吧

虽然我们的这个库看起来很简单,其实它完成度已经很高了,能简洁优美的完成许多工作。来看一些例子吧:

  • Counter。基本例子。 (DartPad)
  • Counter。增减API。 (DartPad)
  • Weather。调用后端API的天气app。(DartPad)
  • News。无限长的分页新闻。(DartPad)

image.png

在生产环境中使用

怎么样?是不是只要想清楚如何存状态和如何传递状态,状态管理其实很简单?不过想要在生产环境中使用,还是需要更多的工作:

  • self 从语法中删掉。 Creator((ref, self) => ref.watch(number, self) * 2)应该变成Creator((ref) => ref.watch(number) * 2), 这样不仅更简洁,而且更安全。
  • 支持动态变化的依赖关系:
final C = Creator((ref) {
  final value = ref.watch(A);
  return value >= 0 ? value : ref.watch(B);
});
  • 更好的支持async。Creator<Future<T>> 现在即使产生相同的 T, Future<T> 也是不同的,会触发不必要的状态更新。

此外还需要一些其他的优化:

  • CreatorGraph 需要在销毁的时候释放资源。
  • Watcher 现在有一些多余的build。
  • 处理exception,支持测试,支持logging。

最后最重要的事情

怎么才能完成这些工作呢。。。

不如直接来看最后的成品吧!

github.com/terryl1900/…

哈哈,其实我是这个最新的状态管理库的主要作者,这篇文章其实是记录了我怎么思考这个问题从而完成Creator库的第一版的。

如果你已经看完了这篇文章,那应该很快就能读完Creator的源代码。核心部分只有500行代码。

个人认为Creator相比目前流行的方案,更加简洁易用。实现了类似Stream的效果,但没有Stream的开销。最关键的是源代码简洁,我希望能让每个人都够通过源代码而完全理解状态管理是如何实现的。

欢迎大家试用Creator或者把它介绍给更多的人。也欢迎各种意见建议批评指正!

(特别感谢一下 @恋猫de小郭 大佬介绍Creator给更多的人)