经验分享 —— 漫谈 Flutter 状态管理方案及选择参考

349 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

前言

自从被拉到某个讨论群,关于状态管理的话题,几乎每两三天就会出现一次。渐渐就理解了大佬为啥总是潜水,可能实在是说烦了,哈哈哈。不过高频的出现也证明了它的重要性,可以说是 Flutter 从入门到顺手必经的一关,今天就来总结分享下自己的整体理解。

从日常使用视角出发,说几个核心问题:

  1. 啥是状态?
  2. 为啥要管理?
  3. 该怎么选?

后面是一些自己使用中的体会和看法。

状态 是啥

无论任何语言和框架,变量肯定是为人熟知的,我们用它储存需要变化的数据。状态其实也是一堆数据,为啥要单独给它个名字呢?因为框架的架构风格发生了变化。

以前大多是命令式的风格,按钮上显示什么文字,啥时候显示啥时候改变,都需要我们用代码亲自控制。比如先通过 id 获取到元素,然后再给他设置一个颜色,设置一个标题等等。

而 Flutter 和 SwiftUI 等这些新的框架,基本都采用了声明式的风格。区别很简单:声明式框架接管了所有的UI更新过程。按钮上的文字和颜色都只是个属性,你给他的值更新了,框架就会帮你自动刷新过去,不用再手动设置每个控件了。

简单来说,命令式风格需要控制整个过程,更加细致复杂;而声明式风格只需要你维护一份状态——框架会自动把这堆数据的变化对应到UI变化上——让你能专心于UI本身和业务逻辑,

PS. 这里说的状态是指这个概念,不是 State 这个类哦。

为啥需要 状态管理

在默认的新建项目中,Flutter 用一个精妙的计数器例子来展示如何更新状态。没错,这就是终极 & 唯一的状态更新方法:setState(),名字真是大道至简呀hhh。

用这个方法告诉框架你更新了状态,框架就会根据状态的更新情况来更新UI。

乍一看好像没啥问题,往 StatefulWidget 的 State 里放需要的数据,然后按需更新即可。为啥还需要管理呢?

因为不同场景下需要的状态不同

如果是个简单的单页 App,一处地方就能放下所有的数据,那当然不需要任何管理。但实际上应用总是比较复杂的,有些数据你希望是全局通用的,比如登录用户的 id;有些数据你只是这个页面用一下,其他地方不需要,比如用户的个性签名。有时你想让通用的数据唯一,一处地方改变了,其他地方也跟着变;有时你又想让数据的变化保持在当前页面,用户保存之前不要同步……

这时候就不得不管理起来了,因为如果把这些错综复杂的状态(数据)都挤在一起,维护起来会让你分分钟爆炸,无从下手。

如何选择 状态管理方案

State 和 SetState

其实顺着上面这个场景,很自然的就会想到封装。

先把页面按是否需要更新,以及层级关系合理拆分成不同的 StatefulWidget 和 StatelessWidget。

然后再处理状态。临时的状态就放在当前的组件下,内部使用。通用的状态就往上层提,让父组件传下来给各个子组件使用,修改就通过回调方法或者用 GlobaKey 找到父组件来修改。

如果能在脑海里就能理清整个逻辑,同时App只有两三个页面,以后也不会有很大的改动和拓展,那就可以采用这种最简单基本的方式。

除了写 Demo,其他情况还是算了吧。真不适合作为整个App的状态管理方案。只适合小组件内部用。

Provider

当页面多起来,数据的依赖复杂度也高起来之后,再用回调和 GlobalKey 就开始显得吃力了,而且也不符合框架本身的声明式风格。

有没有什么方法能便捷的获取到上层的状态呢?Flutter 专门提供了一个代理组件 InheritedWidget,它可以把状态一直向后续的子孙节点传递。这样就不用手动层层传递了,再配合一套观察者模式的监听-订阅通知类,就可以优雅的代替掉手动回调那一套了。

但是这套自带的方法比较底层,为了能更简单的理解和使用,Provider 就出现了。因为封装的恰到好处,也是最有 Flutter 味的,所以一经出现就很火爆,目前也是官方推荐的状态管理方案。

最大的弊病在于对树形结构的强依赖,非父子组件之间的状态管理毫无办法,只能走全局或者单例啥的。

这里其实也有其他的诸如 Redux,BloC 等等的,各有侧重点和不足,不过也都是被官方肯定的,根据实际情况哪个熟悉或哪个方便都能拿来用。挑 Provider 来说只是因为比较有代表性,各方面均衡一点。

RiverPod

这个库和 Provider 的作者是同一个人,是针对前者的改进。主要在保留原始风格的前提下,去掉了对于上下文 BuildContext 的依赖,增加了同层级状态的互相调用,各种状态间的依赖和多种提供者实现形式等。可以说是彻底解决了 Provider 的弊病,算是另一种形式实现的加强版。

如果你使用 Provider 遇到了瓶颈或是别扭的地方,RiverPod 很可能就是你想要的东西。

这也是我的首选方案之一,解决了 Provider 的几个痛点,使用非常的灵活,能很好的解决复杂场景下的各种状态依赖问题,目前好像还没看到什么硬伤。强推。

GetX

应该是整个 Flutter 社区争议最大的库之一了,褒贬不一,跟上面那些都不同,很值得说道一下。

它拥有极高的 Star 数以及大量黑魔法一样简单高效的API。除了极简版的跨组件状态管理外,作者还加入了包括路由跳转,依赖注入,网络访问,持久化保存,主题切换等等的功能。

就核心功能本身而言,状态管理做的还是挺好的。用法非常简单,学习成本几乎没有,开发效率也相当高。

许多大佬比较反感的原因主要有两个:

  1. 隐藏了太多细节。让使用者难以修改和了解原理,出 bug 不好排查,文档也不够全面。
  2. 大而全。把所有东西放在一个库里,部分代码比较混乱,缺少测试覆盖等。如果把所有功能都用起来,基本就把 Flutter 改造成另外一种写法了。

总结起来就是对于部分功能不放心,一旦出问题解决成本非常高。

实际体验下来,如果只用它的状态管理和路由,还是很香的。尤其是状态围绕页面组织的App,几乎天然符合它 View 与 Controller 的结构,可以把代码分离的十分干净。

不同页面几乎不需要状态共享的首选,推荐只使用核心功能,即使后面需要迁移也很方便。代码分离清晰,不会影响UI和业务逻辑方法。

总结

状态管理需要各取所需,因地制宜。脱离了场景就没法判断是否合适。没有银弹,任何情况下都最优的解决方案是不存在的。

人话版:

  • demo 不用
  • 短平快我选 GetX
  • 复杂大型就上 Riverpod