mpvue在前端项目的应用设计

闲话少叙,书归正传。

0x00 故事背景

在有限的人力成本情况下,需要按时上线产品,主要包括:

前端项目:小程序A(C端)、小程序B(B端)、PC管理端
后端项目:平台API项目

其实站在后端开发来讲,由于是服务于业务的系统,又是产品前期,所以也就Spring Cloud一套打完,这里主要想细说的是前端几个项目的情况。

前端开发,在有些朋友描述中可能就是画画界面获取界面数据调接口显示数据,然后一直重复着这个过程,就算使用了一些主流框架,也只是更好、更快的画界面、获取界面数据、调接口、显示数据。其实,简单(初级)的前端也好像真是这样,要是真这么理解,那人活着也多没劲呀,整体就是吃、睡,哈哈哈~

其实自从前端开发宣布独立后,“阶级矛盾”好像也就变成了业务逻辑的前置还是后置的矛盾。尤其是在一些“大前端”的实践中,当然,我也有幸在其他地方看到过,整个后端服务就是一个简单的CRUD服务了。问:后端服务主要做什么用?答:为前端开发提供数据库访问接口。 哈哈,好像也不是没有道理,站在安全的角度考虑,我一直建议业务逻辑后置的,前端尽量回归纯数据展、交互就行。

0x01 鬼知道我怎么想的

三个前端项目同步进行开发,那么必然将会有大部分的工作量是重叠的。相信做开发的朋友应该也都了解,前端界面开发、模拟数据交互等工作都是可以直接人工堆出来了。但是,碰到接口联调,再附加上业务联调,多数会因为设计的不完善而造成改改改,在工期上一般会比较耗时。那么这次就想着将三个项目的接口联调给统一化。所以,为了达到统一,首先就需要解决的是微信小程序web的接口请求了。

这里不打算详细介绍微信小程序技术,但是也放张图(来自网上)出来,先了解一下为什么要达到一统。

小程序示意图

微信小程序本质也是一种Hybird App,于是乎熟悉的window、document对象被消失了,当然,也找不到XMLHttpRequest了,还Ajax个鬼。所以,也只能通过官方的wx.request接口来发起网络请求。我们的目标是统一化,所以只能代码对wx.requestXMLHttpRequest进行兼容了。这里顺带推荐一个开源项目Fly.js来作为HttpClien使用。

Fly.js的设计刚好符合我们现阶段的需求,很好的将API请求逻辑和具体的请求执行(引擎)进行了分离,开发人员只需要调用.get().post()等函数发起请求,至于是使用wx.request还是XMLHttpRequest,就是我们底层的配置了,与具体业务代码就没有半点关联了,对上层的请求调用逻辑,很好的屏蔽了底层的请求执行。

闲白:在以前的PC时代,曾一直喜欢桌面端工具开发,喜欢做一些小工具给大家玩儿。从C#的纯桌面开发到WinFrom + WebKit,再到Silverlight、WPF,微软一直不放弃的更新,可是总感觉一直在学习,从未被应用,哈哈哈,难道是我离坑太远了? 一直到现在的Nodejs跨平台技术,移动端的WebView等跨平台技术,其实思想多数都是一样的,只是一直在更新优化。

0x02 犹抱琵琶半遮面

了解过微信小程序开发的同学,应该都比较熟悉setData函数,是的,和其他框架的setState啥的都是一个作用,都是用来主动触发渲染引擎的update操作,最终执行效果是会通知框架渲染逻辑进行数据更新展示。

很好的一个函数,可是你见过满屏幕的setData逻辑么?哈哈,我有看到过。setData的滥用确实比较严重,一大段逻辑代码中,动不动就来一个setData,尤其是再涉及到数据中key的N层嵌套问题,看着就发燥,是不是很怀念其他框架中的XXX.key = value方式。

其实不然,就像有这么一句话:当你感觉到轻松(方便)的时候,是因为有人在帮你承担压力。从编码方式来看,直接赋值确实简洁一些,但是相应带来的却是框架在一直监听着你的set操作,从性能上考虑的话,我还没有做过具体的测试,会有多大影响,这个说不好。可我就喜欢直接赋值的方式,哈哈哈,任性。

到不完全是个人原因,而是从其他方面考虑。说了这么多,这里为什么要先吐槽微信小程序的setData呢,其实主要原因是,微信小程序自身目前并没有很好的状态管理容器,多数情况需要自己去实现简易的管理,或者直接不使用。所以为了快速、方便的开发,必须要集成成熟的**redux或者vuex**来实现状态管理。

由于PC版的技术优先确定了下来,需要使用iview-admin框架来开发,那么开发人员使用vue来做。那么,考虑为了实现微信小程序项目与PC版的一些组件最大限度的复用,WePy还是mpvue的选择上也就没啥争议了,管他什么亲儿子的,直接就确定mpvue了,因为都是vue全家桶,哈哈,也要相应的考虑下开发人员的学习成本。

至此,整体的前端技术方案也就明确了下来:

小程序开发:mpvue
PC版开发:iview-admin
状态管理:vuex
请求框架:Fly.js

接下来,对整体的技术方案进行下介绍。

闲白:叨叨了这么久才算进入正题?列位也别嫌我絮叨,在家歇着天天一个人,就话多,哈哈。在以前的C#和Java Swing的开发中,各类组件处理数据展示,尤其是在多线程的情况下,UI主线程的控件同步访问等问题,都是比较烦人的。记得那时有讨论提出要将窗体绘制、数据展示、业务数据处理进行分离,应以数据为主线,数据的变更来驱动界面展示,而不应该是用界面逻辑来驱直接处理数据,应该是业务数据在某一个状态应该由哪个界面来展示。记得当时还专门实现了一套基于插件模式开发的框架,将项目的UI模块作为一个显示插件来使用,通过数据绑定来完成渲染展示。现在想来,也算比较符合当前的Flux思想。

0x03 整体介绍

整体方案确定后,那就按照相应的流程给大家画一个整体设计图

整体设计图

从左向右来看

  • 微信小程序基于mpvue开发,PC版使用iview-admin开发,分别来完成界面的开发和数据的交互。vue通过绑定Store中的数据来提供给界面渲染使用。
  • StoreVuex的状态(数据)管理,通过actionsmutations提供相应的业务逻辑处理。在actions中调用Services的模块服务来完成API请求。
  • Services中的模块服务调用Fly.js来完成API请求执行。

这里有一个地方在实际应用的时候需要考虑一下,那就是StoreServices的拆分。

其实这里也有是一些顾虑的,如果拆分两层的话,在开发工作量上会有所增加,如何合并都到Store中也是可以的,直接在Store通过HttpClient(fly.js)来请求接口,然后完成响应数据解析处理,也不是不可。最后决定还是进行拆分,主要是不希望Store中有太多的响应数据解析逻辑,应该更专注业务数据的逻辑处理,这样也可以通过在Services层中来模拟数据来进行快速测试,将Services层作为一个轻薄的数据访问(协议)层。

具体的示例代码来一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//Services中的Article模块
import config from '../config'
import http from '../util/HttpClient'
//导入接口配置
const {
list
} = config.article;
//模块代码开始
export default {
//声明查询接口
async query(params){
//POST参数,获取响应数据
const {status,content} =await http.post(list, params)
//过滤掉接口参数,只返回业务数据
if (http.isOk(status)) {
return content
}
throw http.error(status);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//Store中对服务调用
import articleService from '../../service/ArticleService'
//Vuex state
const state = {
listData: []
}
//actions声明
const actions ={
/**
* 查询列表
*/
async query ({commit, dispatch, state}, {op = 0}){
//调用服务获取数据
const queryResult = await articleService.query(state.query);
//...
}
}
1
2
3
4
5
6
7
8
//vue组件使用
export default {
computed: {
listData () {
return this.$store.state.article.listData
}
}
}

整体代码模块层级可以表示为这样的:

整体层级图

可以看到,我们将Store放在了每个项目中来进行初始化,然后通过VuexModule模式,来按照需要的业务模块可以进行自由组合。

在小程序A示例代码中的Store片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Vue from 'vue'
import Vuex from 'vuex'
//导入vuex modules
//项目自有模块,必须要和其他项目共享
import UserModule from './modules/user'
//公共模块,可与其他项目共享
import Activity from 'XXXX/store/modules/activity'
import Article from 'XXXX/store/modules/article'
import EnumModule from 'XXXX/store/enum'
//
Vue.use(Vuex)
//小程序A的Store创建,组合需要的模块
export default new Vuex.Store({
modules: {
user: UserModule,
activity: Activity,
article: Article,
enum: EnumModule
}
})

当总体的设计都清晰了之后,那么就该来看下项目的结构该如何组织。这里按照上面所述,拆分了5个项目,具体结构如下:

项目图

可以看出,这里主要分为三个主项目(上层)和两个支撑项目(下层)。支持的项目为公共项目,分别是XXXX-front-apimp-common,以npm module方式被小程序A、小程序B、Web管理平台来进行依赖引用。

  • XXXX-front-api:该项目主要为业务公共项目,包括共用的Vuex Module各模块的ServicesFly.js的封装以及常用的Utils等。
  • mp-common:该项目主要对微信小程序项目开发,对小程序的API进行封装,将小程序API的success回调转换为Promise方式,用以支持async/await方式。提供共用的小程序组件样式工具类等。

接下来还要介绍下在项目搭建中要注意的问题,nodejs项目的创建这里就不多说了,具体的npn init或相关框架的脚手架了解下,方便的很。

git中子项目使用

首先,使用git来创建我们规划的五个项目,其中有两个公共模块项目,所以需要使用git submodule的方式来在主项目中进行管理,结构如下:

1
2
3
4
5
6
7
8
9
10
11
#主项目-小程序A
/mp-a.git
/xxx-front-api.git
/mp-common.git
#主项目-小程序B
/mp-b.git
/xxx-front-api.git
/mp-common.git
#主项目PC版管理
/mc.git
/xxx-front-api.git

nodejs的本地模块使用

nodejs项目中,又涉及到公共模块项目的引用,在java maven中,通常使用mvn install来完成本地模块的安装,而在nodejs项目中,主要使用node_modules文件放置依赖,所以我们可以通过系统的软连接或者直接使用npm link来完成本地模块的软连接创建。这样在三个主项目中任意一个项目中对依赖项目的修改可以让其他项目达到同步使用。

1
2
3
4
5
6
7
#npm 安装本地项目
npm link ./mp-common
#
#在nodejs项目中使用模块
npm install mp-common 或者 yarn add mp-common
#
#项目中就可以直接使用模块了

前面主要介绍了小程序选型mpvue跨平台的API请求基于vuex的项目设计,以及项目在搭建过程中需要注意的地方,这次主要以设计为主,具体mpvue在开发中的使用与问题,咱们且听下回分解。