微信小程序项目开发总结

一、基础知识

小程序相关的项目构建,语法,设计等全部内容都在微信小程序的官方文档有详细的描述,所以基础知识很容易掌握,这里给出开发过程中需要一直翻看的官方文档地址;小程序的页面也有自己的一套设计规范,如果对产品没有特殊的要求,可以按照微信官方的设计规范进行UI设计,或则对产品的设计起到一个参考的作用。有必要一直翻看的还有WeUI:

  • 代码地址:GitHub
  • 设计规范:GitHub

因为小程序本身是微信的一部分,所以设计和使用上多少要和微信保持同步。有了这三个文档,基本就可以将小程序从无到有的搭建起来了,也得益于微信给力的官方文档,很细致。

二、项目框架选型

微信web开发者工具

使用微信自己的开发者工具,开发、调试、测试,提交都可以做,但是编码体验很不好,一般的做法是使用webstorm编码,微信web开发者工具只起到编译看效果,调试的作用。能大大提高编码效率。
webstorm引入微信小程序API live template :

  1. File -> import settings引入wecharCode.jar
  2. 重启后,在编码时使用wx.就会有代码提示

使用gulp进行编译构建

由于微信小程序本身对工程化几乎没有任何的支持,所以动手搭建一份:wxapp-redux-starter。
使用gulp进行编译构建,主要功能包括:

  • ✦ 集成了Redux,数据管理更方便
  • ✦ 开发过程中,使用xml取代wxml,对开发工具更友好
  • ✦ 开发过程中,使用less取代wxss,功能更强大
  • ✦ 引入es-promise,以便可以创建并使用Promise
  • ✦ 添加promisify工具函数,可以便捷的将官方Api转换成Pormise模式
  • ✦ 引入normalizr,可以将数据扁平化,更方便进行数据管理
  • ✦ 引入babel自动进行ES2015特性转换
  • ✦ 对wxml/wxss/js/img压缩,相对开发者工具提供的压缩,会减小一丢丢体积。

具体参考这篇文章

三、Tips

  • Webstorm 静态检查为警告的部分,要尽量修正,规避掉一些不必要的bug。
  • 规范小程序的目录结构:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    +—-project
    +——–common(公共代码)
    +——–http(网络请求)
    +——–icons(存放小图标)
    +——–model(模块工具)
    +——–pages(页面)
    +——–templates(模版)
    +——–utils(工具)
    +——–work(文档等)
    +——–app.js、app.json、app.wxss
  • javascript语言虽然语句末尾可以不用’;’,但是为了规范都要加上’;’.

  • 根据小程序设计规范将各部分的标准样式整理到app.wxss里全应用统一使用。具体参照app.wxss
  • js文件的编码顺序建议如下:
    1. data: {}
    2. 生命周期方法
    3. 页面点击响应方法
    4. 私有方法(前面加下划线)
  • 网络返回的数据建议写法如下:
    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
    60
    61
    62
    63
    64
    65
    66
    //建议写法,因为数据结构可以一眼看出来
    let data = response["data"];
    let username = data["username"];

    //不建议写法,不容易看出数据的结构,不容易找错
    let data = response.data;
    let username = data.username;
    使用ES6特性–箭头表达式


    // ES5
    var net = require('../public/net');
    Page({
    data: {
    list: []
    },
    onShow: function () {
    var self = this;//ES5写法,要在这赋值
    net.get('/Index/getList', function (res) {
    res = res || {};
    var status = res.status,
    data = res.data,
    list = data.list;
    if(status == 2) {
    self.setData({list: list});//在这用,很容易忘了
    }
    });
    }
    });
    // ES6
    import * as net from '../public/net';
    Page({
    data: {
    list: []
    },
    onShow: function () {
    net.get('/Index/getList', (res = {}) => {
    let {status, data: {list}} = res;
    if (status == 2) {
    // 此处 this 的指向与 onShow 内一致,无需增加 self 变量
    this.setData({list});
    }
    });
    }
    });
    使用ES6特性–数组筛选

    // js
    var helpers = {
    // 判断是否有时间参数
    hasTime: (i) => {
    return !isNaN(parseInt(i.stamp));
    },
    // 时间转换处理
    parseTime: (i) => {
    i.time = new Date(i.stamp + '000');
    return i;
    }
    };
    net.get('/Index/getList', (res = {}) => {
    let {status, data: {list}} = res;
    this.set({
    list: list.filter(helpers.hasTime) // 筛选掉没有时间戳字段的数据
    .map(helpers.parseTime) // 将时间戳字段转化为 JS 的 Date 对象
    });
    });

四、其他

使用开发工具的正确姿势

微信提供的开发工具的编辑功能实在是太难用,用它写代码简直就是在浪费生命.它的正确用法##作为运行和调试工具.##

那么适合作为编辑工具的是: ##webStorm##.

基于IntelJ内核,开启Dracula主题,跟Android studio的使用习惯非常接近,so cool!各种方法提示,自动保存,快速查找…应有尽有.闭源的微信开发工具就不要用来写代码了,珍惜生命.
webStorm要识别wxml和wxss,还需要配置一下文件类型:

Preferences -> File Types -> HTML : 添加.wxml
Preferences -> File Types -> CasCading Style Sheet : 添加
.wxss

开发时,使用webstorm写代码,web开发者工具调试、运行,效率会高很多。

网络请求的封装

微信提供了网络请求的API,但是真实项目中使用时,还是需要封装一下的。

1
2
3
4
5
6
7
8
9
10
11
12
13
wx.request({
url: 'test.php', //仅为示例,并非真实的接口地址
data: {
x: '' ,
y: ''
},
header: {
'content-type': 'application/json'
},
success: function(res) {
console.log(res.data)
}
})

封装微信网络框架

  1. 定义好携带构建请求的对象
    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
    function requestConfig() {
    //url
    this.url = '';
    //请求头
    this.header = {
    'Content-Type': 'application/json',
    };
    //请求参数
    this.params = {};
    //是否显示等待对话框
    this.loading = true;
    this.netMethod = 'POST';
    //通信加解密数据
    this.safeSdk = {};
    this.callback = {
    httpCallPreFilter: () => {
    //网络请求前处理
    },
    httpCallBackPreFilter: (response) => {
    //网络请求返回前处理
    },
    doHttpSucess: (response) => {
    //网络请求成功--公共处理
    },
    doHttpFailure: (response) => {
    //网络请求失败--公共处理
    },
    doComplete: () => {
    //网络请求结束--公共处理
    }
    };
    this.setMethodGet = function () {
    this.netMethod = 'GET';
    return this;
    };
    this.send = function () {
    _request(this);
    };
    this.upload = function (filePath) {
    _upload(this, filePath);
    }
    }

构建requestConfig

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
/**
* 注意,此方法调用后还要调用.send()才是发送出去.
* @param Interfaces
* @param protocol
* @param params
* @param callback 拷贝上方注释区的代码使用
* @param loading
* @returns {requestConfig}
*/
function buildRequest(url, params, callback, loading) {
let config = new requestConfig();
config.loading = typeof loading === 'undefined' ? true : loading;
let baseUrl = "";
baseUrl = constants.url;
config.url = baseUrl + url;
config.params = params;
if (_isFunction(callback.httpCallPreFilter)) {
let pubHttpCallPreFilter = config.callback.httpCallPreFilter;
config.callback.httpCallPreFilter = () => {
if (!callback.httpCallPreFilter()) {
pubHttpCallPreFilter();
}
}
}
if (_isFunction(callback.httpCallBackPreFilter)) {
let pubHttpCallBackPreFilter = config.callback.httpCallBackPreFilter;
config.callback.httpCallBackPreFilter = (response) => {
if (!callback.httpCallBackPreFilter(response)) {
pubHttpCallBackPreFilter(response);
}
}
}
if (_isFunction(callback.doHttpSucess)) {
let pubDoHttpSucess = config.callback.doHttpSucess;
config.callback.doHttpSucess = (response) => {
if (!callback.doHttpSucess(response)) {
pubDoHttpSucess(response);
}
}
}
if (_isFunction(callback.doHttpFailure)) {
let pubDoHttpFailure = config.callback.doHttpFailure;
config.callback.doHttpFailure = (response) => {
if (!callback.doHttpFailure(response)) {
pubDoHttpFailure(response);
}
}
}
if (_isFunction(callback.doComplete)) {
let pubDoComplete = config.callback.doComplete;
config.callback.doComplete = () => {
if (!callback.doComplete()) {
pubDoComplete();
}
}
}
return config;
}

发送请求和上传文件

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
/**
* 请求数据
* @param requestConfig
* @private
*/
function _request(requestConfig) {
wx.request({
url: requestConfig['url'],
method: requestConfig['netMethod'],
data: requestConfig['params'],
header: requestConfig['header'],
success: (res) => {
_requestSuccess(res, requestConfig);
},
fail: (res) => {
_requestFailed(res, requestConfig)
},
complete: (res) => {
requestConfig.callback.doComplete();
}
})
}
/**
* 上传文件
* @param requestConfig 请求配置
* @param filePath 本地文件路径
* @private
*/
function _upload(requestConfig, filePath) {
wx.uploadFile({
header: headers,
method: requestConfig['netMethod'],
url: requestConfig['url'],
filePath: filePath,
name: 'file',
success: (res) => {
_requestSuccess(res, requestConfig);
},
fail: (res) => {
_requestFailed(res, requestConfig);
},
complete: () => {
requestConfig.callback.doComplete();
}
})
}

方法暴露并在需要时引用

1
2
3
4
module.exports = {
buildRequest:buildRequest
}
var netUtil=require("../../utils/netUtil.js");
使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
_requestLogin: function (code) {
let params = {
code: code,
userId: this.data.userId,
password: this.data.password
};
network_utils.buildRequest(url, params, {
doHttpSucess: (response) => {
if (constants.RetCodes.SUCCESS === response.retCode) {
console.log('[login] success.');
this._onLoginSuccess(response);
return true;
}
return false;
},
doComplete: () => {
this.setData({
loading: false
});
}
}, false).send();
},

flex布局

以下两篇阮一峰老师的文章:

Flex 布局教程:语法篇

Flex 布局教程:实例篇

登录态验证流程

微信官方给出了标准的登录流程,参照登录态维护