There was a moment where I fell in love with working with front-end, and that was when I met The Elm Architecture, more specifically the Elmish F# implementation. I wanted to have the same happiness I have working with Elmish in Flutter and almost found something similar, but it was still too much work for my lazy self. The basic building blocks of TEA (The Elm Architecture) or MVU (Model-View-Update) are:
有一段时间,我爱上了使用前端的那一刻,那是我遇到了Elm Architecture ,更具体地说是Elmish F#实现。 我希望拥有和Flum的Elmish一起工作时所拥有的幸福,并且几乎找到了类似的东西 ,但是对于我的懒惰自己来说,这仍然是太多的工作。 TEA(榆木体系结构)或MVU(模型-视图-更新)的基本构建块是:
Model — the state of your application
模型 -您的应用程序状态
View — a way to turn your state into widgets (or HTML)
视图 -一种将状态转换为小部件(或HTML)的方法
Update — a way to update your state based on messages
更新 -一种基于消息更新状态的方法
Originally the Update part is like a reducer. You have a function that takes the current model, a message that encodes what you want to change in the model and then you return a new model. That makes it a cool and nice state-machine. You can see in that function how the state will change as the messages arrive by external events or user interaction.
最初,Update部分就像一个减速器。 您有一个采用当前模型的函数,一条消息对要在模型中更改的内容进行编码,然后返回新模型。 这使它成为一个很棒的状态机。 您可以在该函数中看到随着外部事件或用户交互消息到达状态的变化。
While having a function to keep the state modification centralised is a good thing, Dart support for discriminated unions is kinda bad and having different types implementing a base message creates lots of noise. In the end the message makes the update function select a model transition, so why not encode it in a simple function?
虽然具有使状态修改集中化的功能是一件好事,但是Dart对受歧视的联合的支持有点不好,而使用基本消息的不同类型会产生很多噪音。 最后,该消息使更新函数选择模型转换,那么为什么不将其编码为简单函数呢?
Let’s talk a little about the remaining pieces, the Model and the View. One of the things that I love about MVU is that the model is immutable and the view is a pure function, meaning that it depends only on the current model to decide what will be shown to the user. If you want to test any feature, you can start the model at any intermediate step and be in the screen right before you want to click the button you just implemented, no need to pass through a login screen and click on things just to prepare the environment.
让我们谈谈剩余的部分,即模型和视图。 我最喜欢MVU的一件事是模型是不可变的,视图是纯函数,这意味着它仅取决于当前模型来决定向用户显示什么。 如果您想测试任何功能,则可以在任何中间步骤中启动模型,并在您要单击刚刚实现的按钮之前进入屏幕,而无需通过登录屏幕并单击任何东西即可进行准备。环境。
In Elmish the view function takes two arguments: The current model and a dispatch function to send messages. I like that, but now that I'm in OOP world, maybe I can have something that helps automatic code completion, so maybe I could have some sort of object with all possible transitions.
在Elmish中,视图函数采用两个参数:当前模型和用于发送消息的调度函数。 我喜欢那样,但是现在我在OOP领域中,也许我可以拥有有助于自动完成代码的功能,所以也许我可以拥有某种具有所有可能转换的对象。
And that’s what I did. Instead of dispatching messages, why not dispatch transitions between states directly and cut the middle update? Instead of scattered message definitions, why not have a single class that holds the methods that do the transitions?
这就是我所做的。 为什么不直接分派状态之间的过渡并削减中间更新,而不是分派消息? 为何不使用单个类来保存进行转换的方法,而不是使用分散的消息定义?
Well, it was not an easy journey as I was misguided for a long time, but in the end I believe that the end product creates simple and well behaved apps with little boilerplate.
好吧,这不是一个容易的旅程,因为我长期以来一直被误导,但最终我相信最终产品可以创建简单且表现良好的应用程序,且几乎没有样板。
Let's examine an example, nothing fancy for now, just the transformed sample app using the proposed state machine:
让我们来看一个示例,目前还不花哨,仅是使用建议的状态机的转换后的示例应用程序:
Starting simple, we have a title for the shown counter and a tally of the current number:
从简单开始,我们为显示的计数器提供标题,并为当前数字的总计:
class HomeModel {final String title;final int counter; HomeModel({this.title, this.counter}); HomeModel copyWith({title, counter}) => HomeModel(title: title ?? this.title, counter: counter ?? this.counter);}Here we have the main player! The messenger is where we will create methods that are like alias to our state machine transitions:
这是我们的主要参与者! 在Messenger中,我们将创建类似于状态机转换别名的方法:
We initialise it with an initial state, an Update instance that is what we use to pass along the new state. It has other features that will be explained later.
我们使用初始状态(用于Update状态的Update实例)对其进行初始化。 它具有其他功能,稍后将进行说明。
class HomeMessenger extends Messenger<HomeModel> { HomeMessenger(title) : super(Update(HomeModel(title: title, counter: 0)));void increment() => modelDispatcher((model) => model.copyWith(counter: model.counter + 1));}The modelDispatcher is a special simple case where you just want to create a new state using the previous one.
modelDispatcher是一种特殊的简单情况,您只想使用前一个状态创建一个新状态。
While in Elmish the view takes the model and the dispatcher, our view takes a BuildContext together with our messenger and the current model:
在Elmish中,视图采用模型和调度程序,而在视图中采用BuildContext以及Messenger和当前模型:
Widget homeBuilder(BuildContext context, HomeMessenger messenger, HomeModel model) {return Scaffold( appBar: AppBar( title: Text(model.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text('You have pushed the button as MVU this many times:', ), Text('${model.counter}', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: messenger.increment, tooltip: 'Increment', child: Icon(Icons.add), ), );}Here we use the title and the counter from the model, and the increment function from the messenger.
在这里,我们使用来自模型的标题和计数器,以及来自Messenger的increment函数。
How do we use the previous code? That is the answer:
我们如何使用先前的代码? 那就是答案:
void main() { runApp(MyApp());}class MyApp extends StatelessWidget { @override Widget build(BuildContext context) {return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MsgBuilder<HomeMessenger, HomeModel>( messenger: HomeMessenger('Flutter MVU Demo Home Page'), builder: homeBuilder), ); }}We have a MsgBuilder that takes a Messenger and a view function to create a stateful widget that will listen the changes of the model in the messenger and recreate the widget as new state changes arrive.
我们有一个MsgBuilder ,它带有一个Messenger和一个视图函数来创建一个有状态的窗口小部件,该状态窗口小部件将侦听Messenger中模型的更改,并在新的状态更改到达时重新创建该窗口小部件。
And that is it for the simplest case. For more complex cases, we can use more capabilities of the Update object. Its definition looks like this:
这是最简单的情况。 对于更复杂的情况,我们可以使用Update对象的更多功能。 其定义如下所示:
class Update<Model> {final Cmd<Model> commands;final bool doRebuild;final Model model;const Update(this.model, {this.commands = const Cmd.none(), this.doRebuild = true});}The next state. No secrets here.
下一个状态。 这里没有秘密。
It's a list of actions that will generate or not transitions in a later time. As every transition is acted on the latest model, there is no risk of race conditions and using a stale model, even in out-of-order execution.
这是将在以后生成或不生成过渡的操作列表。 由于每个过渡均采用最新模型,因此即使出现乱序执行,也不会出现竞争状况和使用陈旧模型的风险。
So you can do something like this:
因此,您可以执行以下操作:
Result getSomethingFromSomewhere() => // long running function// inside the messengervoid askForSomething() => dispatcher((model) => Update(model.copyWith(loading:true), commands: Cmd.ofFunc(getSomethingFromSomewhere, onSuccessModel: (model, result) => model.copyWith(loading: false, result: result), onErrorModel: (model, ex) => model.copyWith(loading: false, error: true))));All commands are async, so you can choose to keep handling the user interaction while the function completes and then when it succeeds or fails, you do something with it using the state that the user changed with their actions.
所有命令都是异步的,因此您可以选择在功能完成时继续处理用户交互,然后在成功或失败时使用用户通过其操作更改的状态来对其进行操作。
If false, the new model won't be sent to the MsgBuilder widget, so it won't trigger a new render.
如果为false,则不会将新模型发送到MsgBuilder小部件,因此不会触发新的渲染。
If the list is updated, this one should be the 32nd state management library for Flutter, so I want to have more feedback before publishing it. In the little research I did, I couldn’t find and exact replica of what I did, but it's probably achievable in other libraries with some extra steps.
如果列表已更新,则该列表应该是Flutter的第32个状态管理库,因此我希望在发布它之前获得更多反馈。 在我所做的少量研究中,我找不到并精确复制了我所做的事情,但是在其他库中可以通过一些额外的步骤来实现。
EDIT: I published it anyway! It should be on mvu_layer if everything goes well!
编辑:我还是发表了它! 如果一切顺利,它应该在mvu_layer上 !
I created a sample repository with the simple app and a bigger one with a Todo. You can test it by cloning the GitHub repository I created. You can create issues with questions or corrections of things I might have overlooked. Honestly I'm new in Flutter and most of my previous relevant work is all in F#. Maybe there isn't something similar because it won’t solve most problems? I'll be glad to know, so I can fix it or keep using it!
我使用简单的应用程序创建了一个示例存储库,使用Todo创建了一个更大的存储库。 您可以通过克隆我创建的GitHub存储库进行测试。 您可以提出问题或对我可能忽略的事情进行更正。 老实说,我是Flutter的新手,以前的大部分相关工作都在F#中进行。 也许没有类似的东西,因为它不能解决大多数问题? 我很高兴知道,所以我可以修复它或继续使用它!
Thank you for reading!
感谢您的阅读!
翻译自: https://medium.com/swlh/mvu-inspired-state-management-for-flutter-1a487c4e1b1e
相关资源:四史答题软件安装包exe