接触小程序有一段时间后而且多多少少作了一些项目以后,又开始了vue的旅程,受其核心思想的影响,对数据/状态管理、组件化、跨平台等都有较高的追求,mpvue 是一个使用 Vue.js开发小程序的前端框架,由此开始了mpvue踩坑之旅,想在提升代码可读性的同时,也增长一点vue.js的开发体验。css
前端: 微信小程序、mpvue
后端:koa
数据库:mongodb 数据库可视化工具:Robo3Thtml
一个基本的商城小程序,包含了前端的首页、分类、购物车、个人(订单)四个tab页,后端的数据定义、分类、和存取。各有其色,我在下面就相应介绍一些主要功能、对比原生小程序和vue.js所踩到的坑还有后端数据库的功能应用。 想了解或者有何问题均可以去做品源码中了解哦。前端
首页: vue
举个栗子说,首页由三部分组成:头部轮播推荐+中间横向滑动推荐+纵向滚动商品list。这三部分,几乎是全部商城类app必需的功能了。头部的轮播推荐、中间的横向滑动式推荐的封装,咱们都知道,诸如此类的功能组件,在各app上基本都少不了,最初学vue最早有所体会的,即是组件代码复用性高的特色,在进行一些组件复用迁移至别的组件或页面时,可能都不须要改动代码或者改动少许代码就能够直接使用,能够说是至关方便了,对于mpvue组件内仍然支持原生小程序的swiper与scroll,二者兼容后,对于熟知小程序和vue的开发者,这项功能能够很高效率地完成。
最后主页面文件就是由一个个组件组成,可读性很强了,对于初学者来讲,模块封装的思想是首先就得具有的了。node
<template>
<div class="container" @click="clickHandle('test click', $event)">
<div class="swiperList">
<swiper :text="motto" :swiperList="swiperlist"></swiper>
</div>
<div class="navTab">
<div class="recTab">
<text> —— 为你推荐 ——</text>
</div>
</div>
<scroll></scroll>
<div class="hot">
<span> —— 热门商品 ——</span>
</div>
<hot :v-text="motto"></hot>
<div class="fixed-img">
<img :src="fixImg" alt="" class="fix-img">
</div>
</div>
</template>
复制代码
不过关于组件封装与组合的问题,因为最近有研究vue性能优化和用户体验的一些知识点,考虑了一个比较严肃的问题:
先看一下常见的vue写法:在html里放一个app组件,app组件里又引用了其余的子组件,造成一棵以app为根节点的组件树:mysql
<body>
<app></app>
</body>
复制代码
而这种作法就引起了性能问题,要初始化一个父组件,必然须要先初始化它的子组件,而子组件又有它本身的子组件。那么要初始化根标签,就须要从底层开始冒泡,将页面全部组件都初始化完。因此咱们的页面会在全部组件都初始化完才开始显示。
这个结果显然不是咱们要的,用户每次点开页面,还要面对一阵子的空白和响应,由于页面启动后不止要响应初始化页面的组件,还有包含在app里的其余组件,这样严重拖慢了页面打开的速度。
更好的结果是页面能够从上到下按顺序流式渲染,这样可能整体时间增加了,但首屏时间缩减,在用户看来,页面打开速度就更快了。网上一些办法大同小异,各有优缺点,因此...本人也在疯狂试验中,静待好消息。react
在不一样父组件中引用同一子组件时,可是各自须要接收绑定的动态样式去呈现不一样的样式,在绑定css style样式这一关上,踩了个大坑:mpvue竟然不支持用object的形式传style,起先处于样式一直上不去的抓狂当中,网上对于mpvue这方面的细节也少之又少,后来查找了许多地方,发现class和style的绑定都是不支持classObj和styleObj形式,就尝试用了字符串,果真...改代码改到怀疑人生,结果你告诉我人生起步就是错误,怎能不心痛?...git
解决:github
<template>
<div class="swiper-list">
<d-swiper :swiperList="swiperlist" :styleObject="styleobject"></d-swiper>
</div>
</template>
<script>
data() {
return {
styleobject:'width:100%;height:750rpx;position:absolute;top:0;z-index:3'
}
}
</script>
复制代码
在作vue项目的时候不免会用到循环,须要用到index索引值,可是v-for在嵌套时index没办法重复用,内循环与外循环不能共用一个index。ajax
<swiper-item v-for="(items,index) in swiperList" :key="index">
<div v-for="item in items" class="swiper-info" :key="item.id" @click="choose" >
<image :src="item.url" class="swiper-image" :style="styleObject"/>
</div>
</swiper-item>
复制代码
以上代码就会报错:
<swiper-item v-for="(items,index) in swiperList" :key="index">
<div v-for="(item,i) in items" class="swiper-info" :key="i" @click="choose" >
<image :src="item.url" class="swiper-image" :style="styleObject"/>
</div>
</swiper-item>
复制代码
这是vue文档里的原话:All lifecycle hooks are called with their 'this' context pointing to the Vue instance invoking it.
意思是:在Vue全部的生命周期钩子方法(如created,mounted, updated以及destroyed)里使用this,this指向调用它的Vue实例,即(new Vue)。 mpvue里同理。 咱们都知道,生命周期函数中的this都是指向Vue实例的,所以咱们就能够访问数据,对属性和方法进行运算。
props:{
goods:Array
},
mounted: function(options){
let category = [
{id: 0, name: '所有'},
{id: 1, name: 'JAVA'},
{id: 2, name: 'C++'},
{id: 3, name: 'PHP'},
{id: 4, name: 'VUE'},
{id: 5, name: 'CSS'},
{id: 6, name: 'HTML'},
{id: 7, name: 'JavaScript'}
]
this.categories = category
this.getGoodsList(0)
},
methods: {
getGoodsList(categoryId){
console.log(categoryId);
if(categoryId == 0){
categoryId = ''
}
wx.request({
url: 'http://localhost:3030/shop/goods/list',
data: {
categoryId: categoryId
},
method: 'POST',
success: res => {
console.log(res);
this.goods = res.data.data;
}
})
},
}
复制代码
普通函数this指向这个函数运行的上下文环境,也就是调用它的上下文,因此在这里,对于生命周期函数用普通函数仍是箭头函数其实并无影响,由于它的定义环境与运行环境是同一个,因此一样能取到vue实例中数据、属性和方法。 箭头函数中,this指向的是定义它的最外层代码块,()=>{} 等价于 function(){}.bind(this);因此this固然指向的是vue实例。起初并无考虑到this指向的问题,在wx.request({})中success用了普通函数,结果一直报错“goods is not defined”,用了箭头函数才解决,起初普通函数的this指向 getGoodsList()的上下文环境,因此一直没办法取到值。
在进行首页点击商品跳转到详情页时,onLoad()没法获取更新数据。
首先虽然onLoad: function (options) 这个是能够接受到值的,可是这个只是加载一次,不是我想要的效果,我须要在本页面(不关闭的状况下)到另一个页面在跳转进来,接收到对应商品的数据。
因此须要将代码放在onshow内部, 在每次页面加载的时候都会进行当前状态的查询,查询对应数据的子对象,更新渲染到详情页上。
onShow: function(options){
// console.log(this.styleobject)
// console.log(options)
wx.getStorage({
key: 'shopCarInfo',
success: (res) =>{
// success
console.log(`initshopCarInfo:${res.data}`)
this.shopCarInfo = res.data;
this.shopNum = res.data.shopNum
}
})
wx.request({
url: 'http://localhost:3030/shop/goods/detail',//请求detail数据表的数据
method: 'POST',
data: {
id: options.id
},
success: res =>{
// console.log(res);
const dataInfo = res.data.data.basicInfo;
this.saveShopCar = dataInfo;
this.goodsDetail.name = dataInfo.name;
this.goodsDetail.minPrice = dataInfo.minPrice;
this.goodsDetail.goodsDescribe = dataInfo.characteristic;
let goodsLabel = this.goodsLabel
goodsLabel = res.data.data;
// console.log(goodsLabel);
this.selectSizePrice = dataInfo.minPrice;
this.goodsLabel.pic = dataInfo.pic;
this.goodsLabel.name = dataInfo.name;
this.buyNumMax = dataInfo.stores;
this.buyNumMin = (dataInfo.stores > 0) ? 1 : 0;
}
})
}
复制代码
了解小程序onLoad与onShow生命周期函数:
onLoad:生命周期函数–监听小程序初始化,当小程序初始化完成时,会触发 onLoadh(全局只触发一次)。
onShow:生命周期函数–监听小程序显示,当小程序启动,或从后台进入前台显示,会触发 onShow。
在全局配置文件中: 1).引入koa并实例化
const Koa = require('koa');
const app = new Koa()
复制代码
2).app.listen(端口号):建立并返回 HTTP 服务器,将给定的参数传递给Server#listen()。
const Koa = require('koa');//引入koa框架
const app = new Koa();
app.listen(3000);
这里的app.listen()方法只是如下方法的语法糖:
const http = require('http');
const Koa = require('koa');
const app = new Koa();
http.createServer(app.callback()).listen(3000);
复制代码
这样基本的配置完毕,咱们就能够用“http://localhost3030+请求地址参数”获取到数据库的值了。
koa-router 是经常使用的 koa 的路由库。
若是依靠ctx.request.url去手动处理路由,将会写不少处理代码,这时候就须要对应的路由的中间件对路由进行控制,这里介绍一个比较好用的路由中间件koa-router。
以路由切换催动界面切换,”数据化”界面。
在构建函数库以前,先来聊聊对象的建模。
Mongoose是在node.js异步环境下对mongodb进行便捷操做的对象模型工具。该npm包封装了操做mongodb的方法。
Mongoose有两个特色:
一、经过关系型数据库的思想来设计非关系型数
二、基于mongodb驱动,简化操做
const mongoose = require('mongoose')
const db = mongoose.createConnection('mongodb://localhost/shop') //创建与shop数据库的链接(shop是我本地数据库名)
复制代码
本地数据库shop中建了分别“地址管理”、“商品详情”、“订单详情”、“商品列表”、“用户列表”五个数据表:
Schema界面定义数据模型:
Schema用于定义数据库的结构。相似建立表时的数据定义(不只仅能够定义文档的结构和属性,还能够定义文档的实例方法、静态模型方法、复合索引等),每一个Schema会映射到mongodb中的一个collection,可是Schema不具有操做数据库的能力。
数据表跟对象的映射,同时具备检查效果,检查每组数据是否知足模型中定义的条件 同时,每一个对象映射成一个数据报表,就可用该对象进行保存操做,等同操做数据表,而非mysql命令行般繁琐的操做
以“商品列表”数据表为例:
// 模型经过Schema界面定义。
var Schema = mongoose.Schema;
const listSchema = new Schema({
barCode: String,
categoryId: Number,
characteristic: String,
commission: Number,
commissionType: Number,
dateAdd: String,
dateStart: String,
id: Schema.Types.ObjectId,
logisticsId: Number,
minPrice: Number,
minScore: Number,
name: String,
numberFav: Number,
numberGoodReputation: Number,
numberOrders: Number,
originalPrice: Number,
paixu: Number,
pic: String,
pingtuan: Boolean,
pingtuanPrice: Number,
propertyIds: String,
recommendStatus: Number,
recommendStatusStr: String,
shopId: Number,
status: Number,
statusStr: String,
stores: Number,
userId: Number,
videoId: String,
views: Number,
weight: Number,
})
复制代码
定义了数据表中须要的数据项的类型,数据表传入数据后会一一对应:
const Router = require('koa-router')()//引入koa-router
const router = new Router();// 建立 router 实例对象
//注册路由
router.post('/shop/goods/list', async (ctx, next) => {
const params = ctx.request.body
//以‘listSchema’的模型去取到Goods的数据
const Goods = db.db.model('Goods', db.listSchema) //第一个‘db’是require来的自定义的,第二个‘db’是取到链接到mongodb的数据库,model代指实体数据(根据schema获取该字段下的数据,而后传给Goods))
ctx.body = await new Promise((resolve, reject) => {//ctx.body是ctx.response.body的缩写,代指响应数据
//异步,等到获取到数据以后再将body发出去
if (params.categoryId) {
Goods.find({categoryId: params.categoryId},(err, docs) => {
if (err) {
reject(err)
}
resolve({
code: 0,
errMsg: 'success',
data: docs
})
})
} else {
Goods.find((err, docs) => {
if (err) {
reject(err)
}
resolve({
code: 0,
errMsg: 'success',
data: docs
})
})
}
})
})
复制代码
全部的数据库操做都是异步的操做,因此须要封装promise来实现,由此经过POST “http://localhost3030/shop/goods/list”即可访问本地shop数据库了。 这里顺便提一下“ctx”的使用,ctx(context)上下文,咱们都知道有node.js 中有request(请求)对象和respones(响应)对象。Koa把这两个对象封装在ctx对象中。 参数ctx是由koa传入的封装了request和response的变量,咱们能够经过它访问request和response (前端经过ajax请求http获取数据) 咱们能够经过ctx请求or获取数据库中的数据。
Ctx.body 属性就是发送给用户的内容
body是http协议中的响应体,header是指响应头
ctx.body = ctx.res.body = ctx.response.body
1).为何要作数据缓存?
在这里不得不提一句数据缓存的重要性,虽然我是从本地数据库获取的数据,可是因为须要的数据量较多,再者前面说的性能优化还未完成,每次仍是有必定的请求时间,不必每次打开都去请求一遍后端,渲染页面较慢,因此须要将须要常常用到的数据作本地缓存,这样能大大提升页面渲染速度。
2).设置模型层
setGoodsList: function (saveHidden, total, allSelect, noSelect, list) {
this.saveHidden = saveHidden,
this.totalPrice = total,
this.allSelect = allSelect,
this.noSelect = noSelect,
this.goodsList = list
var shopCarInfo = {};
var tempNumber = 0;
var list = [];
shopCarInfo.shoplist = list;
for (var i = 0; i < list.length; i++) {
tempNumber = tempNumber + list[i].number
}
shopCarInfo.shopNum = tempNumber;
wx.setStorage({
key: "shopCarInfo",
data: shopCarInfo
})
},
复制代码
将须要作本地存储数据的方法封装成一个方法模型,当须要作本地存储时,直接作引用,现在vue、react中多用到的架构思想,都对模型层封装有必定的要求。
bindAllSelect() {
var list = this.goodsList;
var currentAllSelect = this.allSelect
if (currentAllSelect) {
list.forEach((item) => {
item.active = false
})
} else {
list.forEach((item) => {
item.active = true
})
}
this.setGoodsList(this.getSaveHide(), this.totalPrice(), !currentAllSelect, this.noSelect(), list);
},
复制代码
写这个项目抓狂了不少次,由于不少vue能用的但在mpvue里实现不了,致使走了不少弯路,踩了不少坑,可是程序猿成长不就是在一个个坑里掉下去又爬起来的过程当中吗?做文不易,伙伴们能打赏点就打赏点吧... 顺便附上个人项目地址:“mpvue-demo” ,不过还有不少须要完善的地方,漫漫长路一块儿走啊!