datamanager.js 基于observer思想的数据(源)管理器

在《设计一个基于observer思想的数据管理器》这篇文章里面,我详细描述了为什么要做datamanager,以及实现的思路是什么。这篇文章主要是介绍datamanager的使用方法和一些核心概念。帮助开发者可以更好的理解datamanager的设计思想和使用的注意事项。

安装和使用

非常简单,就像一个普通的npm包一样,你只需要一条命令就可以安装datamanager了:

npm install --save datamanager.js

安装好之后,你就可以使用它。datamanager是一个类,使用时,实例化,得到一个实例,这个实例会附赠很多接口方法,这些方法可以操作(主要是获取)数据。

import DataManager from 'datamanager.js'

let dm = new DataManager(options)

function render() {
  let data = dm.get(dataSourceId)
  if (data === undefined) {
    return
  }
  // 用data干点什么
}

dm.autorun(render)

这些在github上都有非常详细的介绍,这里就不再赘述,本文主要是对一些概念做介绍,防止开发者不理解而乱用。

概念介绍

datamanager里面也没有什么新概念,只不过有些点如果不加以解释,会导致使用者理解错误而带来bug。

没有请求数据的概念

虽然datamanager提供了request方法,但是不推荐使用,这个方法是为了方便初学者过渡而提供了。正确的姿势是使用get方法。把datamanager当做一个数据管理仓库,每一格存放着一个数据源的所有信息,当然也包括了最终需要的数据。注册一个数据源(datasource)就是开辟一格,这一格里面有后端服务器的url信息、一些初始化信息。当你调用get方法时,datamanager会根据你传入的id确定从哪一个拿数据给你。你可以想象在get这个过程里面,datamanager从仓库中找到一格,取出数据直接返回给你,当数据不存在时,会返回undefined。

至于datamanager如何从后端api接口去拿数据的,这对于开发者而已,可以不用了解,除非你对原理比较感兴趣,想自己研究。

没有订阅的概念

这个非常重要,订阅是开发者自己写的代码里面,通过subscribe方法,传入一个回调函数。这个回调函数,会在对应的dataSourceId的数据发生变化时执行。什么时候数据会发生变化呢?当然是datamanager通过内部机制,去后端api取新数据,把仓库里面的老数据(或没有数据)替换掉之后。一般而言,你需要在回调函数中通过get方法再去获取新数据。不过为了兼容过渡思想,回调函数的第一个参数我还是传出了新的data。但是这个data不允许被修改,绝对不允许,它仅仅用来做一些判断,而不应该作为数据使用(除非你自己深拷贝一份)。

但是!subscribe方法都不提倡使用。更推荐的方法是直接使用autorun方法。就像上文的示例代码一样,你没有看到subscribe方法,程序照样能够在数据被更新时重新执行一遍render函数。因此,要从“订阅-发布”的思维中跳出来。新的思维叫“依赖收集”。

它分为两个部分:

  1. autorun函数:立即运行传入的函数A
  2. 在函数A中使用get方法

get方法会收集所有需要的dataSource,建立一个内部机制,当这些被依赖的dataSource的数据发生变动时,就会重新执行函数A。这听上去很神奇,你没有自己创建subscribe,它是怎么做到知道哪些dataSource是函数A依赖的呢?这是一个比较大的问题,有兴趣可以去了解vue的计算属性,相信你对vue还是比较感兴趣的。

此时此刻,你应该建立新的数据使用的思维:从datamanager里面直接获取数据,通过一个函数来渲染界面,当数据源发生变化时,函数会自动重新执行,从而界面也会发生变化。你不再需要设计一整套根据条件而改变界面的逻辑了,代码会变的少不止一倍以上。在这里,你不考虑两个点:1.数据如何通过ajax请求回来,2.数据请求回来之后如何触发界面更新。

你要考虑的是,如何正确合理的编写render函数,因为autorun只需要它作为参数,非常简单,所有的难度就落在render函数的编写上了。

逻辑

前文我提到过,如果你第一次打开某个界面,而此时datamanager里面的某个datasource还没有数据,那么get方法会返回undefined。这是一种特殊的状态,面对这种状态你不能简单跳过,而需要特殊逻辑进行处理,特殊逻辑就是,在render函数中经过判断,直接return打断程序:

let somedata = dm.get(someid)
if (somedata === undefined) {
  return
}

可以说,在每一个,或每一组连续的get操作之后,你都要做这样的一个逻辑,虽然有点烦人。所谓“每一组”,是说你可以这样:

let data1 = dm.get(id1)
let data2 = dm.get(id2)
if (data1 === undefined || data2 === undefined) {
  return
}

但是,在没有经过判断之前,绝对不要尝试使用该数据进行某些操作,下面是错误代码示范:

// 错误代码示范
function getName() {
  return dm.get('name')
}

function getAge() {
  return dm.get('age')
}

function render() {
  let name = getName()
  let age = getAge()
  document.querySelector('#text').innerHTML = name + ':' + age
}

dm.autorun(render)

看上去没什么毛病,但是你会发现,第一次页面打开的时候,name和age都是undefined,会引起一些错误。你有两种选择,一种是在getName和getAge这两个函数里面直接写上面的if逻辑,另一种是在render函数中写if逻辑,第一种是推荐的方法,因为某些情况下,getName不会像这里这样简单,可能在get操作之后还有后续动作。所以,在每一次执行dm.get()之后做逻辑判定是最合理的。

总结

datamanager给了前端一种新的写作思路,就是把数据请求完全抽离开开发,假定数据已经存在直接写作。从某种角度讲,这种思路是一种将异步操作改为同步操作的方法。

2018-01-07

为价值买单

向我捐赠1-5RMB