【网赌平台哪个信誉好】复杂单页应用的数据层设计

复杂单页应用的数据层设计

2017/01/11 · JavaScript
·
单页应用

原文出处: 徐飞   

很多人看到这个标题的时候,会产生一些怀疑:

什么是“数据层”?前端需要数据层吗?

可以说,绝大部分场景下,前端是不需要数据层的,如果业务场景出现了一些特殊的需求,尤其是为了无刷新,很可能会催生这方面的需要。

我们来看几个场景,再结合场景所产生的一些诉求,探讨可行的实现方式。

RxJS字面意思就是:JavaScript的响应式扩展(Reactive Extensions for
JavaScript)。

视图间的数据共享

所谓共享,指的是:

同一份数据被多处视图使用,并且要保持一定程度的同步。

如果一个业务场景中,不存在视图之间的数据复用,可以考虑使用端到端组件。

什么是端到端组件呢?

我们看一个示例,在很多地方都会碰到选择城市、地区的组件。这个组件对外的接口其实很简单,就是选中的项。但这时候我们会有一个问题:

这个组件需要的省市区域数据,是由这个组件自己去查询,还是使用这个组件的业务去查好了传给这个组件?

两者当然是各有利弊的,前一种,它把查询逻辑封装在自己内部,对使用者更加有利,调用方只需这么写:

XHTML

<RegionSelector
selected=“callback(region)”></RegionSelector>

1
<RegionSelector selected=“callback(region)”></RegionSelector>

外部只需实现一个响应取值事件的东西就可以了,用起来非常简便。这样的一个组件,就被称为端到端组件,因为它独自打通了从视图到后端的整个通道。

这么看来,端到端组件非常美好,因为它对使用者太便利了,我们简直应当拥抱它,放弃其他所有。

端到端组件示意图:

A | B | C ——— Server

1
2
3
A | B | C
———
Server

可惜并非如此,选择哪种组件实现方式,是要看业务场景的。如果在一个高度集成的视图中,刚才这个组件同时出现了多次,就有些尴尬了。

尴尬的地方在哪里呢?首先是同样的查询请求被触发了多次,造成了冗余请求,因为这些组件互相不知道对方的存在,当然有几个就会查几份数据。这其实是个小事,但如果同时还存在修改这些数据的组件,就麻烦了。

比如说:在选择某个实体的时候,发现之前漏了配置,于是点击“立刻配置”,新增了一条,然后回来继续原流程。

例如,买东西填地址的时候,发现想要的地址不在列表中,于是点击弹出新增,在不打断原流程的情况下,插入了新数据,并且可以选择。

这个地方的麻烦之处在于:

组件A的多个实例都是纯查询的,查询的是ModelA这样的数据,而组件B对ModelA作修改,它当然可以把自己的那块界面更新到最新数据,但是这么多A的实例怎么办,它们里面都是老数据,谁来更新它们,怎么更新?

这个问题为什么很值得说呢,因为如果没有一个良好的数据层抽象,你要做这个事情,一个业务上的选择和会有两个技术上的选择:

  • 引导用户自己刷新界面
  • 在新增完成的地方,写死一段逻辑,往查询组件中加数据
  • 发一个自定义业务事件,让查询组件自己响应这个事件,更新数据

这三者都有缺点:

  • 引导用户刷新界面这个,在技术上是比较偷懒的,可能体验未必好。
  • 写死逻辑这个,倒置了依赖顺序,导致代码产生了反向耦合,以后再来几个要更新的地方,这里代码改得会很痛苦,而且,我一个配置的地方,为什么要管你后续增加的那些查询界面?
  • 自定义业务事件这个,耦合是减少了,却让查询组件自己的逻辑膨胀了不少,如果要监听多种消息,并且合并数据,可能这里更复杂,能否有一种比较简化的方式?

所以,从这个角度看,我们需要一层东西,垫在整个组件层下方,这一层需要能够把查询和更新做好抽象,并且让视图组件使用起来尽可能简单。

另外,如果多个视图组件之间的数据存在时序关系,不提取出来整体作控制的话,也很难去维护这样的代码。

添加了数据层之后的整体关系如图:

A | B | C ———— 前端的数据层 ———— Server

1
2
3
4
5
A | B | C
————
前端的数据层
————
  Server

那么,视图访问数据层的接口会是什么样?

我们考虑耦合的问题。如果要减少耦合,很必然的就是这么一种形式:

  • 变更的数据产生某种消息
  • 使用者订阅这个消息,做一些后续处理

因此,数据层应当尽可能对外提供类似订阅方式的接口。

RxJS是一个利用可观察(observable)序列和LINQ查询操作符来处理异步以及基于事件程序的一个库。通过RxJS,
开发人员用Observables表示
异步数据流,用LINQ运算符查询
异步数据流,并使用Schedulers参数化
异步数据流中的并发。简而言之,Rx = Observables + LINQ + Schedulers。

服务端推送

如果要引入服务端推送,怎么调整?

考虑一个典型场景,WebIM,如果要在浏览器中实现这么一个东西,通常会引入WebSocket作更新的推送。

对于一个聊天窗口而言,它的数据有几个来源:

  • 初始查询
  • 本机发起的更新(发送一条聊天数据)
  • 其他人发起的更新,由WebSocket推送过来
视图展示的数据 := 初始查询的数据 + 本机发起的更新 + 推送的更新

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f4b62cb7b7061328078-1">
1
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f4b62cb7b7061328078-1" class="crayon-line">
视图展示的数据 := 初始查询的数据 + 本机发起的更新 + 推送的更新
</div>
</div></td>
</tr>
</tbody>
</table>

这里,至少有两种编程方式。

查询数据的时候,我们使用类似Promise的方式:

JavaScript

getListData().then(data => { // 处理数据 })

1
2
3
getListData().then(data => {
  // 处理数据
})

而响应WebSocket的时候,用类似事件响应的方式:

JavaScript

ws.on(‘data’, data => { // 处理数据 })

1
2
3
ws.on(‘data’, data => {
  // 处理数据
})

这意味着,如果没有比较好的统一,视图组件里至少需要通过这两种方式来处理数据,添加到列表中。

如果这个场景再跟上一节提到的多视图共享结合起来,就更复杂了,可能很多视图里都要同时写这两种处理。

所以,从这个角度看,我们需要有一层东西,能够把拉取和推送统一封装起来,屏蔽它们的差异。

无论你在用
Node.js编写一个web端应用还是服务端应用,你都必须经常处理异步和基于事件的编程。Web应用程序和Node.js应用程序都会碰到I
/
O操作和计算耗时的任务,这些任务可能需要很长时间才能完成,并可能会阻塞主线程。而且,处理异常,取消和同步也很麻烦,并且容易出错。

缓存的使用

如果说我们的业务里,有一些数据是通过WebSocket把更新都同步过来,这些数据在前端就始终是可信的,在后续使用的时候,可以作一些复用。

比如说:

在一个项目中,项目所有成员都已经查询过,数据全在本地,而且变更有WebSocket推送来保证。这时候如果要新建一条任务,想要从项目成员中指派任务的执行人员,可以不必再发起查询,而是直接用之前的数据,这样选择界面就可以更流畅地出现。

这时候,从视图角度看,它需要解决一个问题:

  • 如果要获取的数据未有缓存,它需要产生一个请求,这个调用过程就是异步的
  • 如果要获取的数据已有缓存,它可以直接从缓存中返回,这个调用过程就是同步的

如果我们有一个数据层,我们至少期望它能够把同步和异步的差异屏蔽掉,否则要使用两种代码来调用。通常,我们是使用Promise来做这种差异封装的:

JavaScript

function getDataP() : Promise<T> { if (data) { return
Promise.resolve(data) } else { return fetch(url) } }

1
2
3
4
5
6
7
function getDataP() : Promise<T> {
  if (data) {
    return Promise.resolve(data)
  } else {
    return fetch(url)
  }
}

这样,使用者可以用相同的编程方式去获取数据,无需关心内部的差异。

使用RxJS,你可以用Observer 对象来表示多个异步数据流
(那些来自多个数据源的,比如,股票报价,微博,计算机事件,
网络服务请求,等等。),还可以用Observer
对象订阅事件流。无论事件何时触发,Observable 对象都会通知订阅它的
Observer对象。

数据的聚合

很多时候,视图上需要的数据与数据库存储的形态并不完全相同,在数据库中,我们总是倾向于储存更原子化的数据,并且建立一些关联,这样,从这种数据想要变成视图需要的格式,免不了需要一些聚合过程。

通常我们指的聚合有这么几种:

  • 在服务端先聚合数据,然后再把这些数据与视图模板聚合,形成HTML,整体输出,这个过程也称为服务端渲染
  • 在服务端只聚合数据,然后把这些数据返回到前端,再生成界面
  • 服务端只提供原子化的数据接口,前端根据自己的需要,请求若干个接口获得数据,聚合成视图需要的格式,再生成界面

大部分传统应用在服务端聚合数据,通过数据库的关联,直接查询出聚合数据,或者在Web服务接口的地方,聚合多个底层服务接口。

我们需要考虑自己应用的特点来决定前端数据层的设计方案。有的情况下,后端返回细粒度的接口会比聚合更合适,因为有的场景下,我们需要细粒度的数据更新,前端需要知道数据之间的变更联动关系。

所以,很多场景下,我们可以考虑在后端用GraphQL之类的方式来聚合数据,或者在前端用类似Linq的方式聚合数据。但是,注意到如果这种聚合关系要跟WebSocket推送产生关联,就会比较复杂。

我们拿一个场景来看,假设有一个界面,长得像新浪微博的Feed流。对于一条Feed而言,它可能来自几个实体:

Feed消息本身

网赌平台哪个信誉好,JavaScript

class Feed { content: string creator: UserId tags: TagId[] }

1
2
3
4
5
class Feed {
  content: string
  creator: UserId
  tags: TagId[]
}

Feed被打的标签

JavaScript

class Tag { id: TagId content: string }

1
2
3
4
class Tag {
  id: TagId
  content: string
}

人员

JavaScript

class User { id: UserId name: string avatar: string }

1
2
3
4
5
class User {
  id: UserId
  name: string
  avatar: string
}

如果我们的需求跟微博一样,肯定还是会选择第一种聚合方式,也就是服务端渲染。但是,如果我们的业务场景中,存在大量的细粒度更新,就比较有意思了。

比如说,如果我们修改一个标签的名称,就要把关联的Feed上的标签也刷新,如果之前我们把数据聚合成了这样:

JavaScript

class ComposedFeed { content: string creator: User tags: Tag[] }

1
2
3
4
5
class ComposedFeed {
  content: string
  creator: User
  tags: Tag[]
}

就会导致无法反向查找聚合后的结果,从中筛选出需要更新的东西。如果我们能够保存这个变更路径,就比较方便了。所以,在存在大量细粒度更新的情况下,服务端API零散化,前端负责聚合数据就比较合适了。

当然这样会带来一个问题,那就是请求数量增加很多。对此,我们可以变通一下:

做物理聚合,不做逻辑聚合。

这段话怎么理解呢?

我们仍然可以在一个接口中一次获取所需的各种数据,只是这种数据格式可能是:

JavaScript

{ feed: Feed tags: Tags[] user: User }

1
2
3
4
5
{
  feed: Feed
  tags: Tags[]
  user: User
}

不做深度聚合,只是简单地包装一下。

在这个场景中,我们对数据层的诉求是:建立数据之间的关联关系。

因为可观察序列是数据流,你可以用Observable的扩展方法实现的标准查询运算符来查询它们。从而,你可以使用这些标准查询运算符轻松筛选,投影(project),聚合,撰写和执行基于时间轴(time-based)的多个事件的操作。此外,还有一些其他反应流特定的操作符允许强大的查询写入。
通过使用Rx提供的扩展方法,还可以正常处理取消,异常和同步。

综合场景

以上,我们述及四种典型的对前端数据层有诉求的场景,如果存在更复杂的情况,兼有这些情况,又当如何?

Teambition的场景正是这么一种情况,它的产品特点如下:

  • 大部分交互都以对话框的形式展现,在视图的不同位置,存在大量的共享数据,以任务信息为例,一条任务数据对应渲染的视图可能会有20个这样的数量级。
  • 全业务都存在WebSocket推送,把相关用户(比如处于同一项目中)的一切变更都发送到前端,并实时展示
  • 很强调无刷新,提供一种类似桌面软件的交互体验

比如说:

当一条任务变更的时候,无论你处于视图的什么状态,需要把这20种可能的地方去做同步。

当任务的标签变更的时候,需要把标签信息也查找出来,进行实时变更。

甚至:

  • 如果某个用户更改了自己的头像,而他的头像被到处使用了?
  • 如果当前用户被移除了与所操作对象的关联关系,导致权限变更,按钮禁用状态改变了?
  • 如果别人修改了当前用户的身份,在管理员和普通成员之间作了变化,视图怎么自动变化?

当然这些问题都是可以从产品角度权衡的,但是本文主要考虑的还是如果产品角度不放弃对某些极致体验的追求,从技术角度如何更容易地去做。

我们来分析一下整个业务场景:

  • 存在全业务的细粒度变更推送 => 需要在前端聚合数据
  • 前端聚合 => 数据的组合链路长
  • 视图大量共享数据 => 数据变更的分发路径多

这就是我们得到的一个大致认识。

RxJS可与诸如数组,集合和映射之类的同步数据流以及诸如Promises之类的单值异步计算进行互补和顺畅的互操作,如下图所示:

技术诉求

以上,我们介绍了业务场景,分析了技术特点。假设我们要为这么一种复杂场景设计数据层,它要提供怎样的接口,才能让视图使用起来简便呢?

从视图角度出发,我们有这样的诉求:

  • 类似订阅的使用方式(只被上层依赖,无反向链路)。这个来源于多视图对同一业务数据的共享,如果不是类似订阅的方式,职责就反转了,对维护不利
  • 查询和推送的统一。这个来源于WebSocket的使用。
  • 同步与异步的统一。这个来源于缓存的使用。
  • 灵活的可组合性。这个来源于细粒度数据的前端聚合。

根据这些,我们可用的技术选型是什么呢?

单返回值 多返回值
Pull/Synchronous/Interactive Object Iterables (Array / Set / Map / Object)
Push/Asynchronous/Reactive Promise Observable

主流框架对数据层的考虑

一直以来,前端框架的侧重点都是视图部分,因为这块是普适性很强的,但在数据层方面,一般都没有很深入的探索。

  • React, Vue
    两者主要侧重数据和视图的同步,生态体系中有一些库会在数据逻辑部分做一些事情
  • Angular,看似有Service这类可以封装数据逻辑的东西,实际上远远不够,有形无实,在Service内部必须自行做一些事情
  • Backbone,做了一些业务模型实体和关联关系的抽象,更早的ExtJS也做了一些事情

综合以上,我们可以发现,几乎所有现存方案都是不完整的,要么只做实体和关系的抽象,要么只做数据变化的封装,而我们需要的是实体的关系定义和数据变更链路的封装,所以需要自行作一些定制。

那么,我们有怎样的技术选型呢?

推送模式 vs 拉取模式

在交互式编程中,应用程序为了获取更多信息会主动遍历一个数据源,通过检索一个代表数据源的序列。这种行为就像是JavaScript数组,对象,集合,映射等的迭代器模式。在交互式编程中,必须通过数组中的索引或通过ES6
iterators
来获取下一项。

在拉取模式中,应用程序在数据检索过程中处于活动状态:
它通过自己主动调用next来决定检索的速度。
此枚举模式是同步的,这意味着在轮询数据源时可能会阻止您的应用程序的主线程。
这种拉取模式好比是你在图书馆翻阅一本书。
你阅读完成这本书后,你才能去读另一本。

另一方面在响应式编程中,应用程序通过订阅数据流获得更多的信息
(在RxJS中称为可观测序列),数据源的任何更新都传递给可观测序列。这种模式下应用是被动接收数据:除了订阅可观察的来源,并不会主动查询来源,而只是对推送给它的数据作出反应。事件完成后,信息来源将向用户发送通知。这样,您的应用程序将不会被等待源更新阻止。

这是RxJS采用的推送模式。
这好比是加入一个图书俱乐部,在这个图书俱乐部中你注册了某个特定类型的兴趣组,而符合你兴趣的书籍在发布时会自动发送给你。
而不需要排队去搜索获取你想要的书籍。
在重UI应用中,使用推送数据模式尤其有用,在程序等待某些事件时,UI线程不会被阻塞,这使得在具有异步要求的JavaScript运行环境中非常重要。
总之,利用RxJS,可使应用程序更具响应性。

Observable / Observer的可观察模式就是Rx实现的推送模型。
Observable对象会自动通知所有观察者状态变化。
请使用Observablesubscribe方法来订阅,subscribe方法需要Observer对象并返回Disposable对象。
这使您能够跟踪您的订阅,并能够处理订阅。
您可以将可观察序列(如一系列的鼠标悬停事件)视为普通的集合。
RxJS对可观察序列的内置实现的查询,允许开发人员在基于推送序列(如事件,回调,Promise,HTML5地理定位API等等)上组合复杂的事件处理。有关这两个接口的更多信息,请参阅探索
RxJS的主要概念

RxJS

遍观流行的辅助库,我们会发现,基于数据流的一些方案会对我们有较大帮助,比如RxJS,xstream等,它们的特点刚好满足了我们的需求。

以下是这类库的特点,刚好是迎合我们之前的诉求。

  • Observable,基于订阅模式
  • 类似Promise对同步和异步的统一
  • 查询和推送可统一为数据管道
  • 容易组合的数据管道
  • 形拉实推,兼顾编写的便利性和执行的高效性
  • 懒执行,不被订阅的数据流不执行

这些基于数据流理念的库,提供了较高层次的抽象,比如下面这段代码:

JavaScript

function getDataO(): Observable<T> { if (cache) { return
Observable.of(cache) } else { return Observable.fromPromise(fetch(url))
} } getDataO().subscribe(data => { // 处理数据 })

1
2
3
4
5
6
7
8
9
10
11
12
function getDataO(): Observable<T> {
  if (cache) {
    return Observable.of(cache)
  }
  else {
    return Observable.fromPromise(fetch(url))
  }
}
 
getDataO().subscribe(data => {
  // 处理数据
})

这段代码实际上抽象程度很高,它至少包含了这么一些含义:

  • 统一了同步与异步,兼容有无缓存的情况
  • 统一了首次查询与后续推送的响应,可以把getDataO方法内部这个Observable也缓存起来,然后把推送信息合并进去

我们再看另外一段代码:

JavaScript

const permission$: Observable<boolean> = Observable
.combineLatest(task$, user$) .map(data => { let [task, user] = data
return user.isAdmin || task.creatorId === user.id })

1
2
3
4
5
6
const permission$: Observable<boolean> = Observable
  .combineLatest(task$, user$)
  .map(data => {
    let [task, user] = data
    return user.isAdmin || task.creatorId === user.id
  })

这段代码的意思是,根据当前的任务和用户,计算是否拥有这条任务的操作权限,这段代码其实也包含了很多含义:

首先,它把两个数据流task$和user$合并,并且计算得出了另外一个表示当前权限状态的数据流permission$。像RxJS这类数据流库,提供了非常多的操作符,可用于非常简便地按照需求把不同的数据流合并起来。

我们这里展示的是把两个对等的数据流合并,实际上,还可以进一步细化,比如说,这里的user$,我们如果再追踪它的来源,可以这么看待:

某用户的数据流user$ := 对该用户的查询 +
后续对该用户的变更(包括从本机发起的,还有其他地方更改的推送)

如果说,这其中每个因子都是一个数据流,它们的叠加关系就不是对等的,而是这么一种东西:

  • 每当有主动查询,就会重置整个user$流,恢复一次初始状态
  • user$等于初始状态叠加后续变更,注意这是一个reduce操作,也就是把后续的变更往初始状态上合并,然后得到下一个状态

这样,这个user$数据流才是“始终反映某用户当前状态”的数据流,我们也就因此可以用它与其它流组合,参与后续运算。

这么一段代码,其实就足以覆盖如下需求:

  • 任务本身变化了(执行者、参与者改变,导致当前用户权限不同)
  • 当前用户自身的权限改变了

这两者导致后续操作权限的变化,都能实时根据需要计算出来。

其次,这是一个形拉实推的关系。这是什么意思呢,通俗地说,如果存在如下关系:

JavaScript

c = a + b //
不管a还是b发生更新,c都不动,等到c被使用的时候,才去重新根据a和b的当前值计算

1
c = a + b     // 不管a还是b发生更新,c都不动,等到c被使用的时候,才去重新根据a和b的当前值计算

如果我们站在对c消费的角度,写出这么一个表达式,这就是一个拉取关系,每次获取c的时候,我们重新根据a和b当前的值来计算结果。

而如果站在a和b的角度,我们会写出这两个表达式:

JavaScript

c = a1 + b // a1是当a变更之后的新值 c = a + b1 // b1是当b变更之后的新值

1
2
c = a1 + b     // a1是当a变更之后的新值
c = a + b1    // b1是当b变更之后的新值

这是一个推送关系,每当有a或者b的变更时,主动重算并设置c的新值。

如果我们是c的消费者,显然拉取的表达式写起来更简洁,尤其是当表达式更复杂时,比如:

JavaScript

e = (a + b ) * c – d

1
e = (a + b ) * c – d

如果用推的方式写,要写4个表达式。

所以,我们写订阅表达式的时候,显然是从使用者的角度去编写,采用拉取的方式更直观,但通常这种方式的执行效率都较低,每次拉取,无论结果是否变更,都要重算整个表达式,而推送的方式是比较高效精确的。

但是刚才RxJS的这种表达式,让我们写出了形似拉取,实际以推送执行的表达式,达到了编写直观、执行高效的结果。

看刚才这个表达式,大致可以看出:

permission$ := task$ + user$

这么一个关系,而其中每个东西的变更,都是通过订阅机制精确发送的。

有些视图库中,也会在这方面作一些优化,比如说,一个计算属性(computed
property),是用拉的思路写代码,但可能会被框架分析依赖关系,在内部反转为推的模式,从而优化执行效率。

此外,这种数据流还有其它魔力,那就是懒执行。

什么是懒执行呢?考虑如下代码:

JavaScript

const a$: Subject<number> = new Subject<number>() const b$:
Subject<number> = new Subject<number>() const c$:
Observable<number> = Observable.combineLatest(a$, b$) .map(arr
=> { let [a, b] = arr return a + b }) const d$:
Observable<number> = c$.map(num => { console.log(‘here’) return
num + 1 }) c$.subscribe(data => console.log(`c: ${data}`))
a$.next(2) b$.next(3) setTimeout(() => { a$.next(4) }, 1000)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const a$: Subject<number> = new Subject<number>()
const b$: Subject<number> = new Subject<number>()
 
const c$: Observable<number> = Observable.combineLatest(a$, b$)
  .map(arr => {
    let [a, b] = arr
    return a + b
  })
 
const d$: Observable<number> = c$.map(num => {
  console.log(‘here’)
  return num + 1
})
 
c$.subscribe(data => console.log(`c: ${data}`))
 
a$.next(2)
b$.next(3)
 
setTimeout(() => {
  a$.next(4)
}, 1000)

注意这里的d$,如果a$或者b$中产生变更,它里面那个here会被打印出来吗?大家可以运行一下这段代码,并没有。为什么呢?

因为在RxJS中,只有被订阅的数据流才会执行。

主题所限,本文不深究内部细节,只想探讨一下这个特点对我们业务场景的意义。

想象一下最初我们想要解决的问题,是同一份数据被若干个视图使用,而视图侧的变化是我们不可预期的,可能在某个时刻,只有这些订阅者的一个子集存在,其它推送分支如果也执行,就是一种浪费,RxJS的这个特性刚好能让我们只精确执行向确实存在的视图的数据流推送。

RxJS与其它方案的对比

1. 与watch机制的对比

不少视图层方案,比如Angular和Vue中,存在watch这么一种机制。在很多场景下,watch是一种很便捷的操作,比如说,想要在某个对象属性变更的时候,执行某些操作,就可以使用它,大致代码如下:

JavaScript

watch(‘a.b’, newVal => { // 处理新数据 })

1
2
3
watch(‘a.b’, newVal => {
  // 处理新数据
})

这类监控机制,其内部实现无非几种,比如自定义了setter,拦截数据的赋值,或者通过对比新旧数据的脏检查方式,或者通过类似Proxy的机制代理了数据的变化过程。

从这些机制,我们可以得到一些推论,比如说,它在对大数组或者复杂对象作监控的时候,监控效率都会降低。

有时候,我们也会有监控多个数据,以合成另外一个的需求,比如:

一条用于展示的任务数据 := 这条任务的原始数据 + 任务上的标签信息 +
任务的执行者信息

如果不以数据流的方式编写,这地方就需要为每个变量单独编写表达式或者批量监控多个变量,前者面临的问题是代码冗余,跟前面我们提到的推数据的方式类似;后者面临的问题就比较有意思了。

监控的方式会比计算属性强一些,原因在于计算属性处理不了异步的数据变更,而监控可以。但如果监控条件进一步复杂化,比如说,要监控的数据之间存在竞争关系等等,都不是容易表达出来的。

另外一个问题是,watch不适合做长链路的变更,比如:

JavaScript

c := a + b d := c + 1 e := a * c f := d * e

1
2
3
4
c := a + b
d := c + 1
e := a * c
f := d * e

这种类型,如果要用监控表达式写,会非常啰嗦。

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图