前段时间接触了下微信小程序,对于写了几个月RN的我,小程序的语法还是不太容易让我接受,于是我往里面加了点之前用得还挺顺手的东西(mobx、async、await),于是整理了下,出了这么一个微信小程序自制脚手架,分享出来共同探讨下。
项目地址:https://github.com/bbbond/wx-demo
写在前面
首先声明,本脚手架适合习惯小程序自带wxml
、wxss
方式写法的小伙伴们(我是不太喜欢这样的方式,写到想吐)
其次对于项目较大的小程序来说,不太推荐本框架,默认的写法下项目不太好管理,推荐wepy,GitHub地址,不过本人还未使用过,只是看好多博客有推荐。之后有机会去体验下。言归正传开始介绍下这个框架。
脚手架结构
目录结构
首先根据习惯我的项目如下:
./
├── README.md
├── app.js
├── app.json
├── app.wxss
├── assets
├── constants
│ └── CONFIG.js
├── libs
│ ├── combine.js
│ ├── mobx.min.js
│ ├── moment.min.js
│ ├── observer.js
│ ├── runtime.js
│ └── storeCache.js
├── pages
│ └── index
│ ├── index.js
│ ├── index.wxml
│ ├── index.wxss
│ ├── indexStore.js
│ ├── request.js
│ └── search
│ ├── search.js
│ ├── search.wxml
│ └── search.wxss
├── project.config.json
├── store
│ └── stores.js
└── utils
├── baseRequest.js
└── fetchHelper.js
网络封装
为了统一请求风格,对请求框架进行了一次简单封装。(可自行根据业务进行修改)
-
接口返回JSON结果风格:
字段 类型 说明 statusCode int 错误状态码,当值不等于200时表示返回异常结果 data.code int 错误码,当值不等于0时表示返回异常结果 data.message string 错误信息,错误所对应的 data object 服务端返回数据 -
底层封装(以GET方式为例)
// 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为例)
// 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失效的处理)
-
应用层使用
// 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初始化的时候赋值。// 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变化
// 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都初始化
// 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中
//app.js const stores = require('./store/stores.js'); App(observer({ globalData: { ...stores }, onLaunch: function () { }, }));
细心的小伙伴们一定发现上面突然乱入了一个
observer
,这也是mobx的一个用法,无论是App
还是page
都要包一层,这样才能接收到store的变化App(observer(app)); Page(observer(page));
组件化开发
组件开发
组件化的好处就不多说了,在开发过程中,不但能减少很多开发时间,还能让代码更清晰明了(其实更能应对需求变动)。
编写一个组件需要准备三个文件.wxml
、.wxss
、.js
。
-
.wxml
组件的wxml和其他界面的wxml没什么区别,就不具体说明了。 -
.wxss
组件的样式文件,与其他样式文件无异,需要注意的是避免由于类选择器重名而造成的影响。 -
.js(以demo中的search为例)
props为mobx传入的属性,用于接收不可直接改变的值。
在.wxml中通过{{props.xxx}}使用。
注意:需要接收store的实例,若直接接收store的某个属性,那么该属性变化后不会触发界面重新渲染props: { getInTeater: app.globalData.index.getInTeater, index: app.globalData.index, }
data为mobx中组件的状态,类似于React的state。
注意:由于组件的属性、方法最后将会和调用处属性、方法合并,因此注意不要和调用处重名
建议:对于data将组件所需要的状态存在同一个对象中(入demo中的search),对于组件内的方法,我的做法是在方法名前加上__
,对于组件抛出的方法正常使用驼峰命名即可data: { 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) { // ... },
最后导出组件
module.exports = { props, data, __onInputCity, __onSearch }
组件使用
组件的使用也需要在.wxml
、.wxss
、.js
三个地方声明。
-
.wxml
wxml中引入组件界面,这没什么好说的。<include src="./search/search.wxml" />
-
.wxss
wxss中引入组件样式,这也没什么好说的。@import "./search/search.wxss";
-
.js
组件的使用方式如下:
其中关键是将组件的属性、方法和自身的属性、方法进行合并。//index.js let { combine } = require('../../libs/combine'); let search = require('./search/search'); let page = { props, data, }; combine(page, search); Page(observer(page));
-
combine方法
合并方法参考了慕课的一片文章,原文链接// 方法来自 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
用了都说好,谁用谁知道,可惜小程序不支持,那我们只能自己引入了。
不过由于限制必须在每个使用的文件中都加入如下代码
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
具体说明点这里 -
其他限制
嗯,等我想到再补充。
最后
这是我第一次写脚手架,一定会有不足之处,感兴趣的小伙伴们可以一起来完善它。
脚手架项目地址:https://github.com/bbbond/wx-demo
转载请注明来源:http://blog.bbbond.cn/2018/02/04/微信小程序自制脚手架/