前段时间接触了下微信小程序,对于写了几个月RN的我,小程序的语法还是不太容易让我接受,于是我往里面加了点之前用得还挺顺手的东西(mobx、async、await),于是整理了下,出了这么一个微信小程序自制脚手架,分享出来共同探讨下。
项目地址:https://github.com/bbbond/wx-demo
写在前面
首先声明,本脚手架适合习惯小程序自带wxml、wxss方式写法的小伙伴们(我是不太喜欢这样的方式,写到想吐)
其次对于项目较大的小程序来说,不太推荐本框架,默认的写法下项目不太好管理,推荐wepy,GitHub地址,不过本人还未使用过,只是看好多博客有推荐。之后有机会去体验下。言归正传开始介绍下这个框架。
脚手架结构
目录结构
首先根据习惯我的项目如下:
1 | ./ |
网络封装
为了统一请求风格,对请求框架进行了一次简单封装。(可自行根据业务进行修改)
-
接口返回JSON结果风格:
字段 类型 说明 statusCode int 错误状态码,当值不等于200时表示返回异常结果 data.code int 错误码,当值不等于0时表示返回异常结果 data.message string 错误信息,错误所对应的 data object 服务端返回数据 -
底层封装(以GET方式为例)
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// fetchHelper.js
export const get = (url, headers) => {
return new Promise((resolve, reject) => {
logRequest(url);
wx.request({
url: url,
header: headers || {},
success: (res) => {
let data = res.data;
if (Number(res.statusCode) !== 200) {
data = {
...data,
code: res.statusCode,
msg: res.data.message,
};
}
logSuccess('GET', url, headers, undefined, data);
// 将服务端返回的结果整理好抛给上层
resolve(data);
},
fail: (error) => {
logFailed('GET', url, headers, undefined, error);
// 由于本机产生的问题直接异常抛出
reject({code: 1, msg: "网络请求失败", ...error});
}
});
});
}; -
上层封装(以GET为例)
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// baseRequest.js
export const baseGetRequest = (api, params, header) => {
let requestHeader = {
...getBaseHeader(),
...header
};
params && Object.keys(params).map((key) => {
api = api.replace(`{${key}}`, params[key])
});
return new Promise((resolve, reject) => {
get(api, requestHeader)
.then(async result => {
if (result.code) {
// 返回结果存在code则抛出异常信息,(针对不同错误类型进行不同的处理)
reject(result.msg || '未知错误')
} else {
// 无错误正常返回结果
resolve(result);
}
})
.catch(error => {
reject(error.msg)
});
})
};这一层demo中只是简单的进行封装,在具体业务下需要自行进行处理,(例如token失效的处理)
-
应用层使用
1
2
3
4
5
6
7
8
9// request.js
const API = {
IN_THEATERS: `${domain}/movie/in_theaters?city={city}&start={start}`,
};
export const getInTheatersReq = (city, start = 0) => baseGetRequest(
API.IN_THEATERS, {city, start}
);使用没什么好说的,看上面。
mobx状态管理及缓存
-
mobx介绍
如果还没接触过mobx,可以去mobx GitHub了解下,这是一款连redux创始人多说好的状态管理框架。 -
mobx+cache
首先得要对mobx的store进行处理,添加初始化值(initialState)和cache白名单(xxxWhiteList),并在mobx初始化的时候赋值。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// indexStore.js
/** ================== 初始化值 ================== **/
const initialState = {
subjects: [],
};
/** ================== cache白名单 ================== **/
const indexWhiteList = [
'subjects'
];
class IndexStore {
constructor() {
extendObservable(this, {
subjects: this.store && this.store.subjects || initialState.subjects,
});
}
getInTeater = async (city, start = 0) => {
let inTeater;
// ...
this.subjects = inTeater.subjects;
};
}
module.exports = {
IndexStore,
indexWhiteList
};之后还需要一个方法初始化Store,监听Store变化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// storeCache.js
const settingStoreAutoRun = (key, store, whiteList) => {
// 将缓存塞入store
store.prototype.store = JSON.parse(wx.getStorageSync(key) || '{}') || {};
let storeObj = new store();
mobx.autorun(() => {
let app = mobx.toJS(storeObj);
let temp = {};
whiteList.map((key) => {
temp[key] = app[key];
});
wx.setStorage({
key: key,
data: JSON.stringify(temp)
});
});
return storeObj;
};然后找个地方中将所有Store都初始化
1
2
3
4
5
6
7
8
9
10// stores.js
const { settingStoreAutoRun, getCacheKey } = require('../libs/storeCache.js');
let { IndexStore, indexWhiteList } = require('../pages/index/indexStore');
const stores = {
index: settingStoreAutoRun(getCacheKey('INDEX'), IndexStore, indexWhiteList),
};
module.exports = stores;最后在App.js中将初始化后的stores放入globalData中
1
2
3
4
5
6
7
8
9
10
11//app.js
const stores = require('./store/stores.js');
App(observer({
globalData: {
...stores
},
onLaunch: function () {
},
}));细心的小伙伴们一定发现上面突然乱入了一个
observer,这也是mobx的一个用法,无论是App还是page都要包一层,这样才能接收到store的变化1
2
3App(observer(app));
Page(observer(page));
组件化开发
组件开发
组件化的好处就不多说了,在开发过程中,不但能减少很多开发时间,还能让代码更清晰明了(其实更能应对需求变动)。
编写一个组件需要准备三个文件.wxml、.wxss、.js。
-
.wxml
组件的wxml和其他界面的wxml没什么区别,就不具体说明了。 -
.wxss
组件的样式文件,与其他样式文件无异,需要注意的是避免由于类选择器重名而造成的影响。 -
.js(以demo中的search为例)
props为mobx传入的属性,用于接收不可直接改变的值。
在.wxml中通过使用。
注意:需要接收store的实例,若直接接收store的某个属性,那么该属性变化后不会触发界面重新渲染1
2
3
4props: {
getInTeater: app.globalData.index.getInTeater,
index: app.globalData.index,
}data为mobx中组件的状态,类似于React的state。
注意:由于组件的属性、方法最后将会和调用处属性、方法合并,因此注意不要和调用处重名
建议:对于data将组件所需要的状态存在同一个对象中(入demo中的search),对于组件内的方法,我的做法是在方法名前加上__,对于组件抛出的方法正常使用驼峰命名即可1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18data: {
search: {
currentCity: '',
city: app.globalData.index.city,
title: app.globalData.index.title,
}
},
__onInputCity: function(e) {
this.setData({
search: {
...this.data.search,
currentCity: e.detail.value
}
})
},
__onSearch: function(e) {
// ...
},最后导出组件
1
2
3
4
5
6module.exports = {
props,
data,
__onInputCity,
__onSearch
}
组件使用
组件的使用也需要在.wxml、.wxss、.js三个地方声明。
-
.wxml
wxml中引入组件界面,这没什么好说的。1
<include src="./search/search.wxml" />
-
.wxss
wxss中引入组件样式,这也没什么好说的。1
@import "./search/search.wxss";
-
.js
组件的使用方式如下:
其中关键是将组件的属性、方法和自身的属性、方法进行合并。1
2
3
4
5
6
7
8
9
10
11
12//index.js
let { combine } = require('../../libs/combine');
let search = require('./search/search');
let page = {
props,
data,
};
combine(page, search);
Page(observer(page)); -
combine方法
合并方法参考了慕课的一片文章,原文链接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// 方法来自 https://www.imooc.com/article/19908
export const combine = (target, ...source) => {
source.forEach(function (arg) {
if ('object' === typeof arg) {
for (let p in arg) {
if ('object' === typeof arg[p]) {
// 对于对象,直接采用 Object.assign
target[p] = target[p] || {};
Object.assign(target[p], arg[p])
} else if ('function' === typeof arg[p]) {
// 函数进行融合,先调用组件事件,然后调用父页面事件
let fun = target[p] ? target[p] : function () {
};
delete target[p];
target[p] = function () {
arg[p].apply(this, arguments);
fun.apply(this, arguments)
}
} else { // 基础数据类型,直接覆盖
target[p] = target[p] || arg[p]
}
}
}
})
};
其他注意点
async/await的引用
async/await 用了都说好,谁用谁知道,可惜小程序不支持,那我们只能自己引入了。
不过由于限制必须在每个使用的文件中都加入如下代码
1 | const regeneratorRuntime = require('../../libs/runtime'); |
小程序的一些限制
-
代码体积限制
由于小程序的理念,其代码体积必须小于2M。经试验,若代码体积大于2M在微信Android版8.5.3中无法打开,会报内部异常。 -
最低版本库设置
若用户的基础库版本低于要求,则提示更新微信版本。此设置需要在iOS 6.5.8或安卓6.5.7及以上微信客户端版本生效
以上为微信原话,看到这句话瞬间感觉头皮发麻,也就是说对于微信6.5.7以下(iOS 6.5.8)的版本我们得要手动判断是否支持,并作相应处理。
虽然有wx.canIUse可以进行API可用性的判断,但是这个方法也是之后的基础库才加入的,因此有一个断层,让人没法好好玩耍。最后索性使用wx.getSystemInfo进行版本判断,对过低版本直接屏蔽,显示不可用,并提示更新,wx.getSystemInfo具体说明点这里 -
其他限制
嗯,等我想到再补充。
最后
这是我第一次写脚手架,一定会有不足之处,感兴趣的小伙伴们可以一起来完善它。