数据驱动应该是从flux/redux
+ react
这种模式开始流行的。
他的背后不仅仅是数据驱动这么简单,在复杂的系统中,我觉得它解决了一个很关键的问题就是模块间的交互/通信。有很多文章拿他和mvc/mvvm去比较,我个人觉得没有特别的可比性,因为解决的问题不同。
以往处理模式
一个稍微复杂点的例子:
假如有这么一个页面,我们按照以往模式开发,首先模块化开发,拆分成A,B,C 三个模块,然后每个模块有自己的子模块。
如果需求简单还比较好解决,每个模块中自己解决自己的逻辑,解耦的非常清晰。父子之间的关系也非常明确。
- 例如销毁
C模块
,会自动销毁它的子模块C1
和C101
。 - 模块间的关系也很清晰,
B1
不会和B2
有直接关系,他们之间需要通过B模块
去传递。同理,B模块
和A模块
也没有直接关系,他们都需要通过外层页面
去处理关系。
但是假如有这么一个需求,A2
的显示和B2
(用户交互)以及C101
(用户交互)相关怎么办。
按照这种模式,它的解决方案是:
B2
如果发生改变,通知B模块
,B模块
在通知页面
,页面
调用A模块
和C模块
,C模块
调用C1
,C1
调用C101
获取C101
的数据处理,页面
调用A模块
,A模块
再调用A2
,再结合一下从C101
获取的数据,改变它的展示。
是不是看着很绕,从图上看是这么个关系:
图中仅仅显示了其中一个复杂交互,假如我们再多两个模块间关联的逻辑:
B1
和B2
模块影响A2
模块(图中黄线)C1
影响B1
模块(图中白线)
如下图:
3个复杂一点的交互,整个模块间的通信已经变成蜘蛛网了,重要的是,每一条关系线都需要开发者维护的,不仅影响开发效率,而且不好维护,容易引发bug,假如后期加新需求或者调整需求,开发成本都是比较高的。
可见,对于复杂的交互,或者模块间关系复杂时,这种依赖父子关系的通信,是一个很大的障碍。
但是我们怎么办,拒绝模块化开发吗?那样页面设计起来耦合度更大,更加不可维护。
首先一点,模块化开发是一个不可逆的趋势,然而在这种趋势下,解决模块化通信是一个非常重要的点。
模块间通信其他方案
在那个时候,我考虑最多的就是如何去解决模块之间的通信,如何让模块之间交互更加轻松,模块之间更加独立。
方案一:
当时考虑的一个方案是使用一个全局的event(全局的on和fire)。这样模块之间就不用依赖父子关系了。模块和模块间是可以之间交互的。
但是这样会有一些弊端:
- 事件名称如何定义,保证不重名
- 事件是否会重复的on
- 模块和模块之间会因为事件产生一些耦合
- 当交互特别复杂时,也会比较麻烦,还是上面的例子,
B2
通知C2
改变后,C2
还需要通知C101
获取一次数据,来确认改变
整体来看:
优势: 摆脱了模块间父子层级关系,可以简单的跨模块通信
劣势: 依然需要维护复杂的模块间关系,只是可以绕过父子依赖
方案二:
全局共享一个model + component模式。这种其实已经非常趋向与数据驱动了。每个模块都是共享全局的model,然后每个component都可以被全局获取到到,里面的功能属性可以直接被使用。
其实这种模式已经比较理想,页面上面的任何component都可以被直接调用到并且使用。
个人觉得缺点就是:
多了一个全局可调用component的功能。如果砍掉他可以实现完成数据驱动,如果模块调用时,使用多了直接获取component的功能,还是需要在模块间维护好和其他模块间的交互逻辑。
数据驱动
先看一个图,我感觉可以很好的体现数据驱动
提线木偶:他的特点就是每个动作都是,头,手臂,脚,金箍棒都是由操作的人手决定的,头和手臂直接没有任何关系。
数据驱动也可以这么理解,页面上面所以的展示都是由数据决定的,和页面其他地方没有任何关系。
再来看看上面那个例子如果加上数据驱动的设计思想。
页面之间每个模块,不用关心父子模块之间的关系,每个独立的模块都是由一个全局的model决定。
回到上面那个麻烦的场景。当B2
改变时,它会修改model中对应的数据(效验C101数据,结合B2的改变,修改A2的数据),然后A2的业务模块跟进A2的数据改变。
这种设计的核心是每一个模块的改变,全部都交给model处理。
然后model里面会和个个模块一一对应,每个模块无需关注其他模块的变化,只需要关注model里面对应自己数据的变化即可。所以模块间关系链条会显得非常简单。
重点在于,当交互逻辑不断增加时,这个关系链条依然不会增加,因为模块只和model里面对于的数据相关联。
当然,这种模式也无法去省略复杂的业务逻辑,只是业务逻辑全部都会聚集在model中。可以理解为页面上所有的操作都是对数据的操作。然后每个模块只需要监听关注的数据改变即可,这个监听关系就是图中唯一的一条关系线。
换一个理解,我们将直接的模块和模块直接的耦合关系全部转移到了数据中去体现。而数据的维护是远远比模块更好维护的。
Model如何对应View
还是上面页面为例子:
model
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 |
var page = { a: { isShow: true, children: [{ a1: { isShow: true } }, { a2: { isShow: true } }] }, b: { isShow: true, children: [{ b1: { isShow: true } }, { b2: { isShow: true } }] }, c: { isShow: true, children: [{ c1: { isShow: true, children: [{ c101: { isShow: true } }] } }] } } |
isShow 表示展示的意思。这个状态对应文章第一个图片。
当数据改变时,例如model发生变化如下:
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 |
var page = { a: { isShow: true, children: [{ a1: { isShow: true } }, { a2: { isShow: false } }] }, b: { isShow: true, children: [{ b1: { isShow: true } }, { b2: { isShow: false } }] }, c: { isShow: true, children: [{ c1: { isShow: false, children: [{ c101: { isShow: true } }] } }] } } |
对应下面这样:
换一个理解就是每一种数据状态对应一种页面的展示状态。页面想展示成什么样子,需要数据处理成什么样子。数据是这个页面的核心。
数据驱动开发关注点
第一点数据结构的处理,因为数据决定了整个页面的展示,数据结构开始的设计非常关键,数据结构的可扩展性决定了页面的可扩展性,如果开始数据模式不好,后期维护也会非常难受。
第二点是处理好模块和数据中对应的关系。
可以看到数据驱动的难点和关键点就是数据结构的设计。而这个也是很考验开发者能力的。数据结构的好坏直接决定了后期业务开发的质量。
数据驱动和mvc/mvvm的关系
文章开头说了,从我的角度理解数据驱动这种模式和mvc并没有什么竞争关系,在具体的实践中,每一个模块可以是一个mvc或者mvvm,模块的内部处理交给模块自己,可以是mvc,或者单例也可以。数据驱动主要是处理模块之间的一种逻辑。
那么为什么数据驱动和react这种结合的更加好了?因为react更进一步是讲模块内部也实现一个数据驱动,模块内部的数据改变了,模块的状态会跟着改变。