谈谈「数据模型」是如何助力前端开发的

60,983 阅读8分钟

一、定义

数据模型是数据特征的抽象,用来抽象定义一个业务对象。假如现在有一个用户模型,如果要抽象的描述这个用户对象,可以按照如下来定义:

const UserModel = {
    name:{
        type:String,
        property:'name',
        value:'zhangshang'
    },
    age:{
        type:Number,
        property:'age',
        value:26
    }
}

其中,type声明数据的类型,property指明映射路径,value是默认值。这里先有个大概的概念就行,后面我会通过实例来详细展开。

二、动机

前面介绍了数据模型的定义,那和前端开发又有什么关系呢?前端又不需要和数据库打交道,前端开发主要就是拿到数据显示就完了,那为什么需要数据模型呢?它是怎么助力前端开发的呢?我们先来看一下以下几个场景。

场景一

我们在前端开发中,通过ajax请求拿到服务端数据,然后将数据显示在视图上,经常会写如下代码:

如示例,假如我们要显示用户头像,通过取到headUrl的值绑定在src属性上即可。因为是异步加载获取的数据,在最终获取到headUrl的值之前,我们需要先判断cardData.buyerExperienceInfo的存在性,然后才能取值,否则在视图初次渲染之前会报如下错误:

在这种场景下,我们在开发中就不得不写一些防御性的代码,久而久之,项目中类似代码会越来越多,碰到层级深的,防御性代码就会写的越来越恶心。另外还有的就是,如果服务端在这中间某个字段删掉了,那就又得特殊处理了,否则会有一些未知的非空错误报错,这种编码方式会导致前端严重依赖服务端定义的数据结构,非常不利于后期维护。

场景二

平时开发中,我们拿到了服务端返回的数据,有些不是标准格式的,是无法直接在视图上直接使用的,是需要而外格式化处理的,比如我司服务端返回的的价格字段单位统一是,跟时间相关的字段统一是毫秒值,这个时候我们在组件的生命周期内,就不得不而外增加一些对数据处理的逻辑,还有就是这部分处理在很多组件都是公用的,我们就不得不频繁编写类似的代码,数据处理逻辑没有得到复用

场景三

在用户做了一些交互后,需要将一些数据存储到服务端,这个时候我们拿到的数据往往也是非标准的,就比如你要提交个表单,其中有个价格字段,你拿到价格单位可能是百位的,而服务端需要的单位必须是分位的,这个时候在提交数据之前,你又得对这部分数据进行处理,还有就是有些接口的参数是json字符串形式的,可能是多级嵌套的,你还要需要特意构造这样的参数数据格式,导致开发中编写了太多与业务无关的逻辑,随着项目逐渐扩大或者维护人员更迭,项目会越来越不好维护。

三、数据模型

在碰到这么多痛点之后,我就在想如何解决,回顾以上场景,总结下来存在以下几个问题:

  1. 前后端数据结构没有解耦,前端在应对不定的服务端数据结构前提下,需要编写过多的保护性代码,不利于维护的同时,代码健壮性也不高。
  2. 基础数据逻辑处理没有和UI视图解耦,容易阻塞视图渲染,同时,在视图组件上存在太多的基础数据逻辑处理,没有有效复用。

所以,这里我引入了数据模型的概念,那通过数据模型如何解决这类问题呢?下面我将通过两个实际案例来进一步呈现上述场景,以及引入了数据模型之后是如何解决的。

四、案列功能

这个案例使用Vue开发,功能很简单,就是通过ajax请求从服务端拿到数据,然后通过vue视图进行展现,效果如下:

五、常规实现

代码只展示主要功能代码,非完整实现

1.请求数据

created生命周期内,向服务端请求数据。

2.数据处理

获取到数据之后,因为拿到的数据和最终UI上显示的格式不一致,需要转化一下数据格式。

3.渲染数据

给当前Vue实例赋值,然后在template里通过模板语法进行渲染

可以看到常规写法,模板语法里面的写法特别不优雅,各种保护性代码(条件判断)

六、通过数据模型方式处理

1.定义数据模型

首先,我们可以专门建一个名叫model的文件夹,专门用来存放模型,然后定义卡片模型cardModel,其中数据定义格式如下:

  1. type 必填,用来描述该字段的类型,支持String、Number、Date等类型
  2. property 必填,数据路径,对应服务端数据结构的取值路径
  3. value 选填,数据默认值,可不填

通过new Model()进行初始化,后续只需要通过model.parse(data)或者model.traverse(data)这个两个方法就可以完成正向映射和反向映射的过程。

具体的使用方式可以查看API

2.请求数据

通过axios请求接口,在拿到数据之后,调用parse方法解析数据,在解析的过程中会去做赋值操作以及数据格式化。

3.数据渲染

拿到数据,赋值给vue组件实例后,在template模板里面直接使用我们事先定义好的数据字段,不需要再去写类似a&a.b&a.b.c这样的代码,且不管服务端数据字段如何变化,视图渲染都不受影响,从而实现和服务端数据结构进行解耦。

与此同时,针对类似价格、时间等需要格式化的数据,我们可以直接使用,不需要再去写对应的格式化处理逻辑,从而专注于视图组件渲染处理。

通过引入数据模型,我们可以看到在模板里面引入变量的时候不需要进行各种判断,写法非常优雅,而且健壮性很强,即使服务端某个字段没有返回,我们这里也不会因此存在报错的可能性。且在脚本里面没有了数据格式化处理代码,从而不会因为数据处理逻辑代码可能存在的错误,打断UI的渲染。从而带来的更大好处是,随着项目的不断迭代,数据和视图有着清晰的划分,前端和后端进行了解耦,项目的可维护性得到保证。

4.反向映射

在库里面,还提供了traverse方法,和parse方法类似,区别是traverse是反向数据生成以及格式还原。

七、模型库的原理

最后,我来讲讲这个数据模型库(ducker-model)的实现原理,源码总共不到200行,还是简单的,可以通过这里下载查看,主要实现逻辑如下:

  1. 声明一个名叫Model的类。
  2. 通过new Model(options),传入模型结构,初始化数据模型属性,对外主要使用的是parsetraverse方法,
  3. parse方法的实现过程就是遍历模型数据结构,拿到每个属性的数据路径,然后根据这个路径去取传入的的数据里面的数据,最后给事先定义好的属性赋值,在赋值的过程中,可以根据type格式化一些类似时间、价格类型的数据。
  4. traverse方法刚好和parse相反,同样是遍历数据模型结构,拿到每个属性的数据路径,然后根据这个数据路径去设置一个新对象的值,这期间,反向格式化数据类型,最后返回这个新对象。

八、更进一步

目前这个库还很基础,只支持了一些常规的功能,能做的事情还很多,比如:

  1. 目前每次拿到请求一次数据之后都需要解析一次,那是否有个缓存机制,在数据没有变动的时候,直接从缓存取数据呢,或者可以直接watch这个解析之后的数据,做到数据变动,视图变动呢?
  2. 现有支持的类型还不够多,可以根据具体业务情况增加一些类型,以应对更多场景,提供可扩展的机制。
  3. 插件机制,比如表单处理,我们是否可以在数据模型定义的时候就定义好字段格式,在提交的时候就可以直接进行格式检测,抛出提醒呢?
  4. 目前的操作方式还比较适合纯粹的渲染式组件,如何和复杂的携带业务交互的组件融合也是需要考虑的。
  5. ... ...

文章末尾会提供模型库下载地址,有需要的可以在此基础上进行扩展,欢迎一起完善这个库,另外,案例demo的地址也提供了,欢迎下载学习理解。

九、源码下载

案例:ducker-model-demo

模型库:ducker-model