小程序雲開發發佈有一段時間了,最近着手作了一個基於雲開發的小程序項目--仿《微博鮮知》,來自新浪的這款全新風格的小程序雖然界面很是簡約清新,可是內部仍是內藏了不少玄機,在實現的路上趕上了很多坎坷,在這裏分享給你們。但願給你們提供一些思路。html
先展現一下最終結果:更多圖片資源在這裏前端
開發一個完整的小程序時,咱們應該先分析其內部的結構。重複的結構抽離出來做爲組件,組件很是的靈活,能夠嵌入一個頁面或多個頁面。
node
在上面的gif圖中咱們能夠看到首頁的內容是一個個的新聞塊。
雖然這個新聞塊只在首頁中使用到,可是我仍是把它抽離成了一個組件。這樣作的好處是頁面結構將會更加的清晰,而且耦合度下降,好比想換個主界面風格時,你能夠直接換另外一個組件添加進來。
git
還有新聞內部頁面中,有多個小標題,每一個小標題裏面嵌入了不等數量的新聞。若是不是採用組件化的話,到時候inner頁面的wxml結構就會亂成一鍋粥。因此這裏的建議是儘可能組件化分離開來。
對於組件很陌生能夠先看個人以前的這篇文章 組件化開發tabbar
下面是項目的頁面與組件目錄:github
既然是「全棧」,後端確定要搞搞。後端的核心就是數據。那麼咱們就先把數據庫分析一下。這裏我是這樣分析的,數據庫
這裏我構建了5個集合小程序
fresh-mainNews 主頁新聞集合
subNews字段是一個數列,存儲着fresh-subNews Doc的_id,這樣就將這兩個集合綁定了起來,在後面咱們會講到在雲函數中把這兩個集合融合起來返回一個新的數據變得完整一些的集合。
有人可能會問,雲數據庫不是noSQL嗎,爲何不把全部數據所有整合到一個所有的JSON,那樣就能夠只調用一次JSON。
後端
個人理解是:
咱們查詢只是須要查詢咱們想要的數據,不須要的數據能夠等須要的時候再根據關聯去請求。
好比這個項目中的首頁新聞塊,每個新聞塊內部都關聯着大量的子新聞,第一次加載就所有把這個小程序須要的全部數據都加載出來就有點瘋狂了。api
講到這裏就該說頁面的構建了。頁面能夠想象成一個架子,一個承載數據的容器。頁面通上數據,就變得活起來。MVVM,數據驅動視圖。交互靠數據,組件間的通訊,組件與頁面間的通訊都是數據。
{{}} -> 就像是流浪法師大招神奇的傳送門。後面會將給出一個精彩的組件通訊例子(點擊目錄如何實現標題欄置頂)。
雲開發三大核心:
雲函數:通俗的理解就是你寫的函數在雲端運行,能夠把複雜的業務邏輯放在雲函數裏
數據庫:一個既可在小程序前端操做,也能在雲函數中讀寫的 JSON 數據庫
存儲:在小程序前端直接上傳/下載雲端文件,在雲開發控制檯可視化管理,能夠上傳照片下載照片,或者一些其餘文件。
在這裏詳細介紹一下操做雲函數提取數據庫的流程,
這裏咱們以獲取首頁數據爲例:
我這裏用的是vsCode+node+yarn環境。
open in terminal(在終端中打開),yarn一下,添加依賴。
或者參考雲函數官方文檔
// 雲函數入口文件 const cloud = require('wx-server-sdk') // 雲函數初始化 cloud.init() //獲取數據庫句柄 const db = cloud.database() // 雲函數入口函數 exports.main = async () => { const mainNewsList = []; //向fresh-mainNews集合中得到所有數據、由於數據庫裏面如今存的數據很少, //若是多的話能夠設置一個limit以及skip來獲取特定數量的數據 const mainNews = await db.collection("fresh-mainNews").get(); for(let i = 0; i < mainNews.data.length; i++) { const mainNew = mainNews.data[i]; let user_id = mainNew.setMan; //條件查詢 獲取特定id的docments const user = await db.collection('fresh-users').where({ _id: user_id }).get(); //限定條件若是有多條,只添加一條進去 if (user.data.length > 0) { mainNew.setMan = user.data[0] } //這個循環是集合的拼接 for (let i = 0; i < mainNew.subNews.length; i++) { const subNews = await db.collection("fresh-subNews").where({ _id: mainNew.subNews[i] }).get(); if (subNews.data.length > 0) { mainNew.subNews[i] = subNews.data[0] } } //把拼好的docments挨個放進mainNewsList裏面也就是造成了一個全新的 //融合的數據更爲完整的JSON數組 mainNewsList.push(mainNew); } return mainNewsList; }
var that = this; wx.cloud.callFunction({ // 聲明調用的函數名 name: 'mainNewsGet', // data裏面存放的數據能夠傳遞給雲函數的event 效果:event.a = 1 data: { a: 1 } }).then(res => { //res.result的值是雲函數的return的值 //這裏將查詢的結果放入mainNewsList中,而後就能夠在wxml中調用數據 that.setData({ mainNewsList: res.result }) //打印一下結果看看有沒有成功獲取數據 console.log(this.data.mainNewsList) }).catch(err => { console.log(err) })
獲取的數據:
咱們能夠看到本來的subNews裏面原本存放的是_id的數值,融合後變成_id對應的整個doc
變化:
[_id1.value,_id2.value] ---> [{_id1:value,key1:value1,key2:value2},~]
雲函數的調用,數據庫的查詢。就是這麼簡單的四步,雲開發的門檻很低,功能也很強大,只要你去嘗試,很輕鬆的就可以實現。
const formatTime = date => { var dateNow = new Date(); var date = new Date(date); const hour = date.getHours() const minute = date.getMinutes() var times = (dateNow - date) / 1000; let tip = ''; if (times <= 0) { tip = '剛剛' return tip; } else if (Math.floor(times / 60) <= 0) { tip = '剛剛' return tip; } else if (times < 3600) { tip = Math.floor(times / 60) + '分鐘前' return tip; } else if (times >= 3600 && (times <= 86400)) { tip = Math.floor(times / 3600) + '小時前' return tip; } else if (times / 86400 <= 1) { tip = Math.ceil(times / 86400) + '昨天' } else if (times / 86400 <= 31 && times / 86400 > 1) { tip = Math.ceil(times / 86400) + '天前' } else if (times / 86400 >= 31) { tip = '好幾光年前~~' } else tip = null; return tip + [hour, minute].map(formatNumber).join(':') } const formatNumber = n => { n = n.toString() return n[1] ? n : '0' + n } //將這個接口暴露 module.exports = { formatTime: formatTime, }
import { formatTime } from '../../utils/api.js';
let mainNewsList = that.data.mainNewsList for(let i =0; i < mainNewsList.length;i++) { let time = formatTime(mainNewsList[i].time) //這是setData()的數組用法,會常常用到 var str = 'mainNewsList['+i+'].time' that.setData({ [str]:time }) }
wx.previewImage({ current: imgUrl, // 當前顯示圖片的http連接 urls: imagePack // 須要預覽的圖片http連接列表 })
wx.pageScrollTo({ scrollTop: 一個數值(自帶px單位), //滾動到數值所在的位置 duration: 50 //執行滾動所花的時間 })
var that = this let catalogIndex = that.data.catalogIndex; query.selectAll('類名').boundingClientRect(function (rects) { rects.forEach(function (rect) { rect.top // 節點的上邊界座標st, //還有一些別的屬性,這個查詢節點是後面講到的目錄跳轉關鍵API }) }) }).exec() },
//給數組設置值 還能夠有var xx = 'xx['+idx+'].key'的形式 var doneList = 'doneList['+idx+']' that.setData({ [doneList]: true, })
有時候咱們還能夠先改變某個數的值再去setData()它,這是setData()的一個很好用的技巧,不過須要去運用一下才好理解
如:
dataPack.likeNum = (supLikeNum===-1 ? dataPack.likeNum: supLikeNum); this.setData({ comment: dataPack, })
這個效果在別的小程序裏面都沒有見過,應該是微博鮮知首創的,在這裏先對原做者表達一下敬意。內部的構造也是很是巧妙,不一樣於咱們常見的外賣的錨點定位。
咱們先來分析一波:
mvvm,視圖是由數據驅動的,咱們要透過現象看本質,去思考底層的數據,這樣咱們很快就會有思路:
<font color=red size=4>show the code:</font>
1.catalog/index.wxml
<block wx:for="{{subNews}}" wx:for-item="subNewsItem" wx:for-index="idx" wx:key="index"> <view class="subTitle-item" bind:tap="scrollFind" //關鍵1:綁定item索引 data-hi="{{idx}}"> <text>{{subNewsItem.title}}</text> </view> </block>
catalog/index.js
scrollFind: function(e) { //點擊後 實現inner頁面特定新聞小標題置頂 let curIndex = e.currentTarget.dataset.hi // 關鍵2: 與inner頁面取得聯繫 var myEventDetail = {index: curIndex} // detail對象,提供給事件監聽函數 var myEventOption = {} // 觸發事件的選項 this.triggerEvent('catalog', myEventDetail) }
onCatalog: function(e) { e.detail // 自定義組件觸發事件時提供的detail對象 console.log(e.detail.index) //關鍵:3 把索引存儲到data this.setData({ catalogIndex : e.detail.index }) //關鍵4: 頁面能夠經過組件的id取得其頁面引用組件的方法 // this.subNews=this.selectComponent("#subNews") this.subNews.goTop(); },
<subNews ~省略~ catalogIndex="{{catalogIndex}}" id="subNews"></subNews>
//subNews/index.wxml //一個看不見的圖片,來自瀑布流的靈感,可以產生主動觸發的事件 <view style="display:none"> <image src="{{mainImg}}" bindload="onImageLoad"></image> </view>
//subNews/index.js onImageLoad: function () { var that = this let offsetList = that.data.offsetList; const query = wx.createSelectorQuery().in(this) //以前講到過的API獲取節點信息,咱們把它存儲到offsetList偏移量數組,他存儲着每個節點在屏幕的位置, //配合wx.pageScrollTo能夠達到新聞欄置頂的效果 query.selectAll('.subNews-wrapper').boundingClientRect(function (rects) { rects.forEach(function (rect) { rect.top // 節點的上邊界座標 offsetList.push(rect.top) that.setData({ offsetList, }) }) }).exec() },
goTop: function (e) { var that = this let catalogIndex = that.data.catalogIndex; //這裏offsetList是一個data裏面的數據,來保存全部的節點的上邊距座標 let offsetList = that.data.offsetList; wx.pageScrollTo({ scrollTop: offsetList[catalogIndex], //滾動到具體數值所在的位置 duration: 50 //執行滾動所花的時間 }) }
至此,你就實現了這個看似簡單卻很是巧妙的功能,組件->頁面->組件,秀得眼花繚亂。若是仍是有些不理解的話,等下能夠下載個人代碼去看。
至於爲何要弄一個圖片的加載而後觸發那個事件呢,這是由於若是你把獲取offsetList偏移量數組的函數放在goTop裏的話,進入頁面第一次的點擊會無效,這樣產生的體驗確定是很是不舒服的。
先展現一下效果:
先說一下優化的是什麼:點贊效果的延遲極大下降
由於點讚的變化是由用戶產生的一個交互,傳統的觀點就是用戶點贊->後端更新數據->前端拉取數據->數據驅動視圖的變化。
真實的體驗就是,很是的慢,慢到點擊後2秒才能看到點讚的效果,這種差勁的交互簡直就是一場災難。
for(let i = 0; i< that.data.comments.length; i++) { //當點擊該個評論時,只更新這一條數據 if (i == idx) { var str = 'comments['+idx+'].likeNum' that.setData({ [str]:res.result.data.likeNum, }) console.log(likeNumList[idx]) } }
data: { doneList: [], //是否按下 likeNumList: [], //模擬點贊數數組 likeAdd: 10, //點贊每次增長數,根據你的設置來,你後端每次加1這裏就寫1 }, var doneList = 'doneList['+idx+']' likeNumList[idx] = (that.data.comments[idx].likeNum + that.data.likeAdd); that.setData({ likeNumList, [doneList]: true, likeAdd: that.data.likeAdd+10 })
<text class="dianzanNum">{{likeNumList[idx]?likeNumList[idx]:item.likeNum}}</text>
優化思路是怎麼樣的呢?
用一個數組來存放/模擬更新的數據,若是數字的索引位置被賦值,則頁面直接顯示這個更新的數字,也是殊途同歸之妙。由於用戶關心的是數據的變化,咱們能夠先把數據的變化產生,至於數據後端的變化讓他異步慢慢的去作。
從這裏發散思想,是否是評論功能也可以用這樣的思路一樣去達到極致的速度與交互體驗呢。
點讚的延遲幾乎爲無,體驗到<font color=red size=3>點贊</font>的極致快感,讓人幾乎停不下來~~(暗示一波)
篇幅所限,文章到這裏就差很少了。
項目地址:https://github.com/HappyBirdwe/newsDance/tree/master/weiboFresh奉上
精心寫的項目,細節很不錯喲,歡迎你們☆☆☆ star ☆☆☆☆
結語:
學習的道路上免不了坎坷,但願文章的分享可以爲你們提供一些思路,學習的過程減小一點彎路,這就是這篇文章最大的價值,歡迎你們提問及指正。
最後在這裏感謝一下:
騰訊雲提供的技術支持
新浪團隊的微博鮮知做者
掘金這個優秀的平臺
點贊動做超帥的你
微博鮮知小程序官方傳送門:
體驗真的很不錯哦,界面很是簡約,你們能夠體驗一波