为什么我不再使用MVC框架?( 三 )


既然我们有了一种方式将Model与View进行解耦,那么下一个问题就是:在这里该如何创建完整的应用模型呢?“控制器”该是什么样子的?为了回答这个问题,让我们重新回到MVC上来 。
苹果公司了解MVC的基本情况,因为他们在上世纪80年代初,从Xerox PARC“偷来了”这一模式,从那时起,他们就坚定地实现这一模式:

为什么我不再使用MVC框架?

文章插图
图3:MVC模式
Andre Medeiros曾经清晰地指出,这里核心的缺点在于,MVC模式是“交互式的(interactive)”(这与反应型截然不同) 。在传统的MVC之中,Action(Controller)将会调用Model上的更新方法,在成功(或出错)之时会确定如何更新View 。他指出,其实并非必须如此,这里还有另外一种有效的、反应型的处理方式,我们只需这样考虑,Action只应该将值传递给Model,不管输出是什么,也不必确定Model该如何进行更新 。
那核心问题就变成了:该如何将Action集成到反应型流程中呢?如果你想理解Action的基础知识的话,那么你应该看一下TLA+ 。TLA代表的是“Action中的逻辑时序(Temporal Logic of Actions)”,这是由Dr. Lamport所提出的学说,他也因此获得了图灵奖 。在TLA+中,Action是纯函数:
data’ = A (data)
我真的非常喜欢TLA+这个很棒的理念,因为它强制函数只转换给定的数据集 。
按照这种形式,反应型MVC看起来可能就会如下所示:
V = f( M.present( A(data) ) )
这个表达式规定当Action触发的时候,它会根据一组输入(例如用户输入)计算一个数据集,这个数据是提交到Model中的,然后会确定是否需要以及如何对其自身进行更新 。当更新完成后,View会根据新的Model状态进行更新 。反应型的环就闭合了 。Model持久化和获取其数据的方式是与反应型流程无关的,所以,它理所应当地“不应该由前端工程师来编写” 。不必因此而感到歉意 。
再次强调,Action是纯函数,没有状态和其他的副作用(例如,对于Model,不会包含计数的日志) 。
反应型MVC模式很有意思,因为除了Model以外,所有的事情都是纯函数 。公平来讲,Redux实现了这种特殊的模式,但是带有React不必要的形式,并且在reducer中,Model和Action之间存在一点不必要的耦合 。Action和接口之间是纯粹的消息传递 。
这也就是说,反应型MVC并不完整,按照Dan喜欢的说法,它并没有扩展到现实的应用之中 。让我们通过一个简单的样例来阐述这是为什么 。
假设我们需要实现一个应用来控制火箭的发射:一旦我们开始倒计时,系统将会递减计数器(counter),当它到达零的时候,会将Model中所有未定的状态设置为规定值,火箭的发射将会进行初始化 。
为什么我不再使用MVC框架?

文章插图
这个应用有一个简单的状态机:
为什么我不再使用MVC框架?

文章插图
图4:火箭发射的状态机
其中decrement和launch都是“自动”的Action,这意味着我们每次进入(或重新进入)counting状态时,将会保证进行转换的评估,如果计数器的值大于零的话,decrement Action将会继续调用,如果值为零的话,将会调用launchAction 。在任何的时间点都可以触发abort Action,这样的话,控制系统将会转换到aborted状态 。
在MVC中,这种类型的逻辑将会在控制器中实现,并且可能会由View中的一个计时器来触发 。
这一段至关重要,所以请仔细阅读 。我们已经看到,在TLA+中,Action没有副作用,只是计算结果的状态,Model处理Action的输出并对其自身进行更新 。这是与传统状态机语义的基本区别,在传统的状态机中,Action会指定结果状态,也就是说,结果状态是独立于Model的 。
在TLA+中,所启用的Action能够在状态表述(也就是View)中进行触发,这些Action不会直接与触发状态转换的行为进行关联 。换句话说,状态机不应该由连接两个状态的元组(S1, A, S2)来进行指定,传统的状态机是这样做的,它们元组的形式应该是(Sk, Ak1, Ak2,…),这指定了所有启用的Action,并给定了一个状态Sk,Action应用于系统之后,将会计算出结果状态,Model将会处理更新 。
当我们引入“state”对象时,TLA+提供了一种更优秀的方式来对系统进行概念化,它将Action和view(仅仅是一种状态的表述)进行了分离 。
我们样例中的Model如下所示:
model = {
counter: ,


推荐阅读