接觸小程序有一段時間後而且多多少少作了一些項目以後,又開始了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」 ,不過還有不少須要完善的地方,漫漫長路一塊兒走啊!