文章目录
  1. 1. 前言
  2. 2. 1.发布订阅处理复杂逻辑
  3. 3. 2.Storage
  4. 4. 3.filter 计算属性
  5. 5. 4.flex Style
  6. 6. 5.async await
  7. 7. 6.addKey Api
  8. 8. 7. 组件化复用开发实践
    1. 8.1. (1) 第一步,查看目录结构划分,主要为 Carousel 组件 在 index 页面的使用
    2. 8.2. (2) 第二步我们分析看如何使用,设计 组件需要的 props
    3. 8.3. (3).业务代码编写
  9. 9. 8. 自定义 headerBar
    1. 9.1. 参考
    2. 9.2. 生态圈

前言

原生开发小程序有了两个项目,在原生开发小程序经验技巧方面有一些自己的总结,此篇文章做原创分享!

本文适合老手查看,新手请参阅官方文档,同步至github

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
var Event = (function() {
var clientList = {},
pub,
sub,
remove;

var cached = {};

sub = function(key, fn) {
if (!clientList[key]) {
clientList[key] = [];
}
// 使用缓存执行的订阅不用多次调用执行
cached[key + "time"] == undefined ? clientList[key].push(fn) : "";
if (cached[key] instanceof Array && cached[key].length > 0) {
//说明有缓存的 可以执行
fn.apply(null, cached[key]);
cached[key + "time"] = 1;
}
};
pub = function() {
var key = Array.prototype.shift.call(arguments),
fns = clientList[key];
if (!fns || fns.length === 0) {
//初始默认缓存
cached[key] = Array.prototype.slice.call(arguments, 0);
return false;
}

for (var i = 0, fn; (fn = fns[i++]); ) {
// 再次发布更新缓存中的 data 参数
cached[key + "time"] != undefined
? (cached[key] = Array.prototype.slice.call(arguments, 0))
: "";
fn.apply(this, arguments);
}
};
remove = function(key, fn) {
var fns = clientList[key];
// 缓存订阅一并删除
var cachedFn = cached[key];
if (!fns && !cachedFn) {
return false;
}
if (!fn) {
fns && (fns.length = 0);
cachedFn && (cachedFn.length = 0);
} else {
if (cachedFn) {
for (var m = cachedFn.length - 1; m >= 0; m--) {
var _fn_temp = cachedFn[m];
if (_fn_temp === fn) {
cachedFn.splice(m, 1);
}
}
}
for (var n = fns.length - 1; n >= 0; n--) {
var _fn = fns[n];
if (_fn === fn) {
fns.splice(n, 1);
}
}
}
};
return {
pub: pub,
sub: sub,
remove: remove
};
})();
  • 全局挂载使用
1
2
3
4
5
6
7
8
9
// app.js
App({
onLaunch: function(e) {
// 注册 storage,这是第二条
wx.Storage = Storage;
// 注册发布订阅模式
wx.yue = Event;
}
});
  • 使用实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 添加收货地址页面订阅
onLoad: function (options) {
wx.yue.sub("addAddress", function (data) {
y.setData({
addAddress: data
})
})
}
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
// 取消多余的事件订阅
wx.Storage.removeItem("addAddress");
},
onUnload: function () {
// 取消多余的事件订阅
wx.yue.remove("addAddress");
}
1
2
3
// 传递地址页面获取好数据传递
wx.yue.pub("addAddress", data.info);
// 补充跳转返回

注意:使用完成数据后要注意卸载,在页面被关闭时操作

2.Storage

storage 管理封装,用法和上面的一致,挂载在全局对象上调用,使用介绍就不列了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const Storage = {
setItem: function(key, obj, callback) {
wx.setStorage({
key: key,
data: obj,
success: callback || function() {}
});
},
getItem: function(key) {
return wx.getStorageSync(key);
},
removeItem: function(key) {
wx.removeStorage({
key: key
});
}
};

3.filter 计算属性

小程序也有计算属性,你知道吗?

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
// 文件名称为 :filter.wxs
// 不支持es6,Date,Number
function filterOrderTitleName(status) {
switch (status) {
case "1":
return "待支付";
case "2":
return "待配送";
case "3":
return "配送中";
case "4":
return "已完成";
}
}
function filterPrice(str) {
// 四舍五入 格式化数字
// toFix(8440.55,1) => 8440.6
var times = Math.pow(10, 2);
var roundNum = Math.round(str * times) / times;
return roundNum.toFixed(2);
}

module.exports = {
filterOrderTitleName: filterOrderTitleName,
filterPrice: filterPrice
};
  • 使用实例,过滤处理打折后的金额小数位数
1
2
3
4
5
6
7
8
// 当前文件名:shoppingCart.wxml
// wxs 文件顶部导入
<wxs src="../../filter/filter.wxs" module="filter"></wxs>
<view class='offerPrice nowrap'>¥{{filter.filterPrice(item.plus*100*item.price/1000)}}
<image class='youhuiBox' src="../../assets/youhuiBox.png">
<view class='youhuiText'>会员{{item.dazhe}}折</view>
</image>
</view>

4.flex Style

分享我常使用的自定义的一套 flex 样式,快速实现布局

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
/* -------------------------------------------------------------flex------------------------------------------------------- */

.center {
display: flex;
align-items: center;
justify-content: center;
}

/* 单行水平垂直 */

.oneLineCenter {
display: flex;
display: -webkit-flex;
justify-content: center;
align-items: center;
}

/* 单行垂直居中,水平向左 */

.oneLineStart {
display: flex;
display: -webkit-flex;
justify-content: flex-start;
align-items: center;
}

/* 单行垂直居中,水平向右 */

.oneLineEnd {
display: flex;
display: -webkit-flex;
justify-content: flex-end;
align-items: center;
}

/* 单行垂直居中,水平保持间距 */

.oneLineAround {
display: flex;
display: -webkit-flex;
justify-content: space-around;
align-items: center;
}

/* 单行垂直居中,两端对齐 */

.oneLineBetween {
display: flex;
display: -webkit-flex;
justify-content: space-between;
align-items: center;
}

/* 超过单行设置的最大宽度,允许换行显示 */

.f-wrap {
flex-wrap: wrap;
}

/* 多轴线方向,一般配合 wrap 使用 */

/* 宽度不足换行后,垂直方向靠上排列 */

.mulitLineStart {
display: flex;
display: -webkit-flex;
flex-wrap: wrap;
align-content: flex-start;
}

/* 宽度不足换行后,垂直方向居中排列 */

.mulitLineCenter {
display: flex;
display: -webkit-flex;
flex-wrap: wrap;
align-content: center;
}

/* 宽度不足换行后,垂直方向靠下排列 */

.mulitLineEnd {
display: flex;
display: -webkit-flex;
flex-wrap: wrap;
align-content: flex-end;
}

/* 宽度不足换行后,垂直方向上保持间隔排列 */

.mulitLineAround {
display: flex;
display: -webkit-flex;
flex-wrap: wrap;
align-content: space-around;
}

/* 宽度不足换行后,垂直方向上靠两侧最顶开始间隔排列 */

.mulitLineBetween {
display: flex;
display: -webkit-flex;
flex-wrap: wrap;
align-content: space-between;
}

/* 纵轴变主轴,垂直靠上,水平居中 */

.columnStart {
display: flex;
display: -webkit-flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
}

/* 纵轴变主轴,垂直靠下,水平居中 */

.columnEnd {
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: center;
}

/* 纵轴变主轴,垂直居中,水平居中 */

.columnCenter {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

/* 纵轴变主轴,垂直间隔排列,水平居中 */

.columnAround {
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
}

/* 纵轴变主轴,垂直上下两侧按间隔排列,水平居中 */

.columnBetween {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
}
/* 纵轴变主轴,垂直上下两侧按间隔排列,水平靠左 */

.columnBetweenStart {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
}
/* 纵轴变主轴,垂直上下两侧按间隔排列,水平靠右 */

.columnBetweenEnd {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-end;
}

5.async await

使用runtime.js,使小程序支持 async await,拷贝文件至项目目录下。

  • 实例用法
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
const regeneratorRuntime = require("../../utils/runtime.js");
Page({
shopCartInit() {
var y = this;
// 拿到商铺位置信息再去渲染购物计算当前的address符合不符合规定
var showCartList = function() {
// 显示全局的地址信息
var globalAddress = wx.Storage.getItem("globalAddress");
if (globalAddress) {
y.setData({
globalAddress: globalAddress,
addr_id: globalAddress.id
});
y.calculateDistance(
qqmapsdk,
globalAddress.latitude,
globalAddress.longitude
);
} else {
y.setData({
globalAddress: {}
});
}
};
// await 等待获取商铺位置信息
async function getShopPosTionMsg() {
await util.promiseRequest(api.merchant_addr, {}).then(res => {
var data = res.data.response_data.lists[0];
y.setData({
shop_lat: data.latitude, // 商铺纬度
shop_lng: data.longitude, // 商铺经度
peiSongFanWei: data.scope // 配送范围
});
});
}

async function initData() {
await getShopPosTionMsg();
await showCartList();
util.closeLoading();
y.setData({
loading: false
});
}
// 开始执行
initData();
}
});

6.addKey Api

使用自定义属性的方法辅助完成业务逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 为数组添加新的自定义键值以及过滤每个子项的方法
*
* @param {*} arr
* @param {*} obj { isShow:false,isStar:false}
* @param {*} filterFn
* @returns
*/
function addKey(arr, obj, filterFn) {
if (!Array.isArray(arr)) {
throw new Error("第一个参数必须为数组类型");
}
let temp = arr.forEach((v, index, arr) => {
typeof filterFn === "function" ? filterFn(v, index) : "";
for (var key in obj) {
v[key] = obj[key];
}
});
return temp;
}
  • 使用实例
1
2
3
4
5
6
util.addKey(data, { y_isCheck: false }, function(v) {
v.dazhe = Number(v.plus);
});
this.setData({
cartList: data
});

7. 组件化复用开发实践

组件化解构项目元件,提高开发效率,可参照官方介绍起步 !

这里介绍一个自定义的跑马灯的轮播图组件实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

├───component
│ └───Carousel
│ Carousel.js
│ Carousel.json
│ Carousel.wxml
│ Carousel.wxss

├───filter
│ filter.wxs

├───pages
│ └───index
│ index.js
│ index.json
│ index.wxml
│ index.wxss

└───utils
api.js
runtime.js
util.js

(2) 第二步我们分析看如何使用,设计 组件需要的 props

  • 数据项,必须 bannerList
  • 轮播图的固定高度 swiperHeight
  • 自定义轮播按钮小点,宽与高一致,圆形 dotWidthAndHeight
  • 轮播按钮盒子距离顶部的高度 dotTop

最终在 index.wxml 的实际使用

1
2
3
4
5
6
7
8
<view class="Carousel">
<Carousel
swiperHeight="260rpx"
bannerList="{{bannerList}}"
dotWidthAndHeight="12rpx"
dotTop="-20rpx"
></Carousel>
</view>

(3).业务代码编写

  • Carousel.json
    开启自定义组件模式
1
2
3
{
"component": true
}
  • Carousel.wxml
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
<view class="Carousel_wrap">
<!-- 图片区 -->
<swiper
current="{{swiperCurrentIndex}}"
circular="true"
bindchange="swiperChange"
indicator-dots="{{indicatorDots}}"
autoplay="true"
interval="5000"
duration="1000"
style="height:{{swiperHeight}}"
>
<swiper-item
wx:for="{{bannerList}}"
bindtap="toHref"
wx:key="{{index}}"
bindtap="toHref"
data-type="{{item.type}}"
data-id="{{item.goods_id}}"
data-content="{{item.content}}"
data-link="{{item.link}}"
>
<image src="{{item.img_url}}" class="slide-image" />
</swiper-item>
</swiper>
<!-- 关联按钮 -->
<view class="boxCell" style="top:{{dotTop}}">
<block
wx:for="{{bannerList.length > 1 ? bannerList:[]}}"
wx:for-index="index"
wx:key="{{item.banner}}"
>
<view
id="{{index}}"
class="dot {{index === swiperCurrentIndex ? 'dot_active':''}}"
style="width:{{dotWidthAndHeight}},height:{{dotWidthAndHeight}}"
bindtap="selectCarouselByIndex"
/>
</block>
</view>
</view>
  • Carousel.js
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
Component({
/**
* 组件的属性列表 必须
*/
properties: {
bannerList: {
// 属性名
type: Array, // 类型(必填),目前接受的类型包括:String, Number, Boolean, Object, Array, null(表示任意类型)
value: [], // 属性初始值(可选),如果未指定则会根据类型选择一个
observer: function(newVal, oldVal) {} // 属性被改变时执行的函数(可选),也可以写成在methods段中定义的方法名字符串, 如:'_propertyChange'
},
dotWidthAndHeight: String,
swiperHeight: String, // swiper 高度
dotTop: String // 小点距离顶部高度
},

/**
* 组件的初始数据
*/
data: {
swiperCurrentIndex: 0,
indicatorDots: false // 自定义轮播按钮
},
/**
* 组件的方法列表
*/
methods: {
swiperChange: function(e) {
var source = e.detail.source;
if (source === "autoplay" || source === "touch") {
this.setData({
swiperCurrentIndex: e.detail.current
});
}
},
selectCarouselByIndex: function(e) {
this.setData({
swiperCurrentIndex: Number(e.currentTarget.id)
});
},
// 轮播图跳转至内部页面
toHref(e) {
const data = e.currentTarget.dataset;
// type = 2,根据 goods_id 展示商品详情
// type = 3, 展示富文本的活动详情页面
if (data.type === "2") {
wx.navigateTo({
url: `../sort_detail/sort_detail?id=${data.id}`
});
} else if (data.type === "3") {
wx.yue.pub("renderData", data.content);
wx.navigateTo({
url: `../activity_detail/activity_detail`
});
}
}
}
});

8. 自定义 headerBar

后续分享…

参考

生态圈

  • ColorUI 鲜亮的高饱和色彩,专注视觉的小程序组件库
  • taro 多端统一开发框架,支持用 React 的开发方式编写一次代码,生成能运行在微信小程序、H5 、 React Native 等的应用
  • uni-app 使用 Vue.js 开发跨平台应用的前端框架
  • 微信小程序开发资源汇总
文章目录
  1. 1. 前言
  2. 2. 1.发布订阅处理复杂逻辑
  3. 3. 2.Storage
  4. 4. 3.filter 计算属性
  5. 5. 4.flex Style
  6. 6. 5.async await
  7. 7. 6.addKey Api
  8. 8. 7. 组件化复用开发实践
    1. 8.1. (1) 第一步,查看目录结构划分,主要为 Carousel 组件 在 index 页面的使用
    2. 8.2. (2) 第二步我们分析看如何使用,设计 组件需要的 props
    3. 8.3. (3).业务代码编写
  9. 9. 8. 自定义 headerBar
    1. 9.1. 参考
    2. 9.2. 生态圈