Vue数据组件规划与组件二次封装案例

设计平台的时候,不免要使用到各种事先规定好的元数据。这些元数据可能用在不同的页面中,以不同的实例存在。对于SPA,为减少Ajax次数,提高复用率,目前用过的最好的方法即把他们封装为数据组件。同时,将不需要时常获取的元数据存在状态中。这样在利用该组件的时候就可以实现快速无痛的复用。

设计复杂系统常会面临各种元数据的使用问题,如枚举、列表、映射等。这些数据根据即时性,可以大致划分为:

  • 极少修改的数据域,如版本号
  • 不需要即时同步的数据域,如配置枚举
  • 需要及时同步的数据域,如协同操作组件

对于不同的数据即时性,在前端,确切的说在Vue上,我们可以有不同的实现与封装方案。

数据即时性与设计

罕有变化的数据元素

诸如版本号、极少更变的元数据等,可以写在某个meta.js中。这些字段特点为极少改变且通常为人工运维。为减轻Ajax压力,可以写死在项目内。对于某些无状态自动生成的字段,像copy right中的年月,可以通过即时计算的方法来实现更变,如调moment.js库。这些元素都有一定的共性,不常变,更变速度甚至可能跟不上版本的迭代速度,无交互状态。于是往往不需要每次打开页面都发请求,这对于降低前端压力、降低交互逻辑复杂度是有帮助的。而数据组件在使用这些元素的时候,通过静态调用即可完成展示。

1
2
3
4
5
6
// meta.js
export let VERSION = '0.1 beta';

export let LANGUAGE = 'en';

// ....

Copy Right:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<footer> © {{ moment().year() }} improvedNPC Powered by Hexo & Icarus
</template>

<script>
import moment from 'moment';
export {
name: 'Footer',
data() {
return {
// ...
}
}
}
</script>

不需要即时同步的数据域

不需要及时同步的数据域,可以理解为一次请求/一次打开页面只需要获取一次的数据域。这些往往为配置枚举、映射等。但这些数据又会因状态变化而变化,例如需要响应配置中心的变化。因此,根据是否需要触发即获取将实现划分为:

  1. SPA加载的时候,获取到数据并保存到状态中。
    这种实现形式的好处是,数据仅需要被获取一次即可复用。除了SPA本身被加载的时候会有负担,其他时候的负担较小。如:在根组件的mounted钩子上调用API并写state。下次数据组件仅需要从state读取即可。

  2. 组件加载或触发的时候,获取数据域
    此类需要及时获取、需要在渲染时获得最新数据的组件,会将Ajax封装于自身中。为减少渲染、减少请求量,这些组件推荐被设置为懒加载,且只有触发改变的时候采取获取对应的数据。例如,通过组件懒加载减轻大规模数据组件的加载负担。

    1
    2
    3
    4
    5
    6
    7
    // 组件懒加载
    const IconList = () => import('components/base/icon-list')
    export default {
    components: {
    IconList
    }
    }

    又或者,对二次封装的数据组件,通过状态钩子进行加载。如iview Select组件的on-open-change。每次触发,刷新数据。这种做法有好有坏:如果每次触发都要加载大量数据,那是极不明智的,证明实时性需要更加细粒度的保证,例如细粒度的父子组件传diff。

即时同步的数据域

即时同步的数据域,例如股票交易数据,往往需要深层次的适配和优化。基于websocket还是基于tcp,用Protobuf还是flatbuffer,是否基于service worker,顺序性,粘包。这样的设计往往比组件设计自身重要。这类组件往往需要拆分工作组件(worker)、api组件(api.js),在浅层渲染组件上调用底层组件。渲染已经不是需要考量的重点,回调挂钩的方式应该反过来进行,即将回调注入到worker中。

协同操作的数据组件

这方面。。。由于不是专业前端。。。看这里

数据组件二次封装案例

二次封装的数据组件大体上要遵循这些实践方式:

  • 调用时,要数据交互跟原生UI组件有一样的设计原则,甚至更方便
  • 尽可能降低XHR依赖,依赖模式尽可能松散:如利用好状态和静态变量特性,异常及时捕捉
  • 满足原生组件的多状态条件

这几个设计原则,对于项目来说,意义在于:

  • 不影响二次拓展和API连贯性,不会因前人写死了代码导致毫无维护和拓展的意义
  • 减少数据依赖,阻止异常对外输送
  • 不会限制了原生组件功能的使用

例子:iView Select

iview的Select组件是一个典型的数据组件案例:为提高代码聚合度,通常不会到处散布XHR请求数据并渲染的零散代码,而是将一个选择类别进行二次封装。

这种情况下,只要对数据实时性不会有很高的要求,较为优雅的实践是,在加载页面时通过根mounted钩子发XHR将枚举、映射存储到状态中。数据组件在计算属性上导入相应的状态,通过v-for渲染Option

这个时候,我们设计时要尽可能满足Select的API设计思路:

  • 满足clearablefilterablemulipledisabled等属性的等价性,因此需要把这些属性当作自身的属性;
  • 满足value的双向绑定特性,通过计算属性直接绑定Select的value
  • 在设计multiple的时候,要注意返回的是数组还是字符串
  • 对钩子事件尽可能实现(并非完全重现,根据需要执行)

代码样例:

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<template>
<div id="select">
<Select v-model="val" :multiple="multiple" transfer filterable clearable placeholder="选择方法">
<Option v-for="(item, index) in selectMap" :key="'ip-' + index" :value="index">
{{ item }}
</Option>
</Select>
</div>
</template>

<script>
import { mapState } from "vuex";

export default {
name: 'SelfSelect',
props: {
multiple: {
type: Boolean,
default: false
},
value: {
type: String,
default: ''
}
},
model: {
prop: 'value',
event: 'change'
},
data: function () {
return {
}
},
computed: {
// 通过状态获取选项
...mapState(['selectMap']),
val: {
get() {
// 根据multiple,返回所需要的数据和数据结构
return this.multiple ? (this.value.length > 0 ? this.value.split(",") : []) : this.value;
},
set(e) {
// 根据业务场景,可以用逗号分割字符串返回,也可以返回原生数组
// 自定义事件名,默认为input
this.$emit('change', this.multiple ? e.join(",") : e)
}
}
},
mounted: function () {

},
methods: {
}
}
</script>

<style>
@import "style.css";
</style>

评论

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×