本文將手把手教你如何寫出迷你版微博的一行行代碼,迷你版微博包含如下功能:css
使用到的雲開發能力:html
沒錯,幾乎是全部的雲開發能力。也就是說,讀完這篇實戰,你就至關於徹底入門了雲開發!ios
咳咳,固然,實際上這裏只是介紹核心邏輯和重點代碼片斷,完整代碼建議下載查看。git
做爲一個社交平臺,首先要作的確定是通過用戶受權,獲取用戶信息,小程序提供了很方便的接口:github
<button open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 進入小圈圈 </button>
這個 button
有個 open-type
屬性,這個屬性是專門用來使用小程序的開放能力的,而 getUserInfo
則表示 獲取用戶信息,能夠從bindgetuserinfo
回調中獲取到用戶信息。數據庫
因而咱們能夠在 wxml 裏放入這個 button
後,在相應的 js 裏寫以下代碼:json
Page({ ... getUserInfo: function(e) { wx.navigateTo({ url: "/pages/circle/circle" }) }, ... })
這樣在成功獲取到用戶信息後,咱們就能跳轉到迷你微博頁面了。小程序
須要注意,不能使用 wx.authorize({scope: "scope.userInfo"})
來獲取讀取用戶信息的權限,由於它不會跳出受權彈窗。目前只能使用上面所述的方式實現。api
社交平臺的主頁大同小異,主要由三個部分組成:數組
那麼很容易就能想到這樣的佈局(注意新建一個 Page 哦,路徑:pages/circle/circle.wxml
):
<view class="circle-container"> <view style="display:{{currentPage === 'main' ? 'block' : 'none'}}" class="main-area" > </view> <view style="display:{{currentPage === 'msg' ? 'flex' : 'none'}}" class="msg-area" > </view> <view style="display:{{currentPage === 'me' ? 'flex' : 'none'}}" class="me-area" > </view> <view class="footer"> <view class="footer-item"> <button class="footer-btn" bindtap="onPageMainTap" style="background: {{currentPage === 'main' ? '#111' : 'rgba(0,0,0,0)'}}; color: {{currentPage === 'main' ? '#fff' : '#000'}}" > 首頁 </button> </view> <view class="footer-item"> <button class="footer-btn" bindtap="onPageMsgTap" style="background: {{currentPage === 'msg' ? '#111' : 'rgba(0,0,0,0)'}}; color: {{currentPage === 'msg' ? '#fff' : '#000'}}" > 消息 </button> </view> <view class="footer-item"> <button class="footer-btn" bindtap="onPageMeTap" style="background: {{currentPage === 'me' ? '#111' : 'rgba(0,0,0,0)'}}; color: {{currentPage === 'me' ? '#fff' : '#000'}}" > 我的 </button> </view> </view> </view>
很好理解,畫面主要被分爲上下兩個部分:上面的部分是主要內容,下面的部分是三個 Tab 組成的 Footer。重點 WXSS 實現(完整的 WXSS 能夠下載源碼查看):
.footer { box-shadow: 0 0 15rpx #ccc; display: flex; position: fixed; height: 120rpx; bottom: 0; width: 100%; flex-direction: row; justify-content: center; z-index: 100; background: #fff; } .footer-item { display: flex; justify-content: center; align-items: center; height: 100%; width: 33.33%; color: #333; } .footer-item:nth-child(2) { border-left: 3rpx solid #aaa; border-right: 3rpx solid #aaa; flex-grow: 1; } .footer-btn { width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; border-radius: 0; font-size: 30rpx; }
核心邏輯是經過 position: fixed
來讓 Footer 一直在下方。
讀者會發現有一個 currentPage
的 data ,這個 data 的做用其實很直觀:經過判斷它的值是 main
/msg
/me
中的哪個來決定主要內容。同時,爲了讓首次使用的用戶知道本身在哪一個 Tab,Footer 中相應的 button
也會從白底黑字黑底白字,與另外兩個 Tab 造成對比。
如今咱們來看看 main
部分的代碼(在上面代碼的基礎上擴充):
... <view class="main-header" style="display:{{currentPage === 'main' ? 'flex' : 'none'}};max-height:{{mainHeaderMaxHeight}}" > <view class="group-picker-wrapper"> <picker bindchange="bindGroupPickerChange" value="{{groupArrayIndex}}" range="{{groupArray}}" class="group-picker" > <button class="group-picker-inner"> {{groupArray[groupArrayIndex]}} </button> </picker> </view> <view class="search-btn-wrapper"> <button class="search-btn" bindtap="onSearchTap">搜索用戶</button> </view> </view> <view class="main-area" style="display:{{currentPage === 'main' ? 'block' : 'none'}};height: {{mainAreaHeight}};margin-top:{{mainAreaMarginTop}}" > <scroll-view scroll-y class="main-area-scroll" bindscroll="onMainPageScroll"> <block wx:for="{{pageMainData}}" wx:for-index="idx" wx:for-item="itemName" wx:key="_id" > <post-item is="post-item" data="{{itemName}}" class="post-item-wrapper" /> </block> <view wx:if="{{pageMainData.length === 0}}" class="item-placeholder" >無數據</view > </scroll-view> <button class="add-poster-btn" bindtap="onAddPosterTap" hover-class="add-poster-btn-hover" style="bottom:{{addPosterBtnBottom}}" > + </button> </view> ...
這裏用到了 列表渲染 和 條件渲染,還不清楚的能夠點擊進去學習一下。
能夠看到,相比以前的代碼,我添加一個 header,同時 main-area
的內部也新增了一個 scroll-view
(用於展現 Feed 流) 和一個 button
(用於編輯新迷你微博)。header 的功能很簡單:左側區域是一個 picker
,能夠選擇查看的動態類型(目前有 關注動態 和 全部動態 兩種);右側區域是一個按鈕,點擊後能夠跳轉到搜索頁面,這兩個功能咱們先放一下,先繼續看 main-area
的新增內容。
main-area
裏的 scroll-view
是一個可監聽滾動事件的列表,其中監聽事件的實現:
data: { ... addPosterBtnBottom: "190rpx", mainHeaderMaxHeight: "80rpx", mainAreaHeight: "calc(100vh - 200rpx)", mainAreaMarginTop: "80rpx", }, onMainPageScroll: function(e) { if (e.detail.deltaY < 0) { this.setData({ addPosterBtnBottom: "-190rpx", mainHeaderMaxHeight: "0", mainAreaHeight: "calc(100vh - 120rpx)", mainAreaMarginTop: "0rpx" }) } else { this.setData({ addPosterBtnBottom: "190rpx", mainHeaderMaxHeight: "80rpx", mainAreaHeight: "calc(100vh - 200rpx)", mainAreaMarginTop: "80rpx" }) } }, ...
結合 wxml 能夠知道,當頁面向下滑動 (deltaY < 0) 時,header 和 button
會 「忽然消失」,反之它們則會 「忽然出現」。爲了視覺上有更好地過渡,咱們能夠在 WXSS 中使用 transition
:
... .main-area { position: relative; flex-grow: 1; overflow: auto; z-index: 1; transition: height 0.3s, margin-top 0.3s; } .main-header { position: fixed; width: 100%; height: 80rpx; background: #fff; top: 0; left: 0; display: flex; justify-content: space-around; align-items: center; z-index: 100; border-bottom: 3rpx solid #aaa; transition: max-height 0.3s; overflow: hidden; } .add-poster-btn { position: fixed; right: 60rpx; box-shadow: 5rpx 5rpx 10rpx #aaa; display: flex; justify-content: center; align-items: center; color: #333; padding-bottom: 10rpx; text-align: center; border-radius: 50%; font-size: 60rpx; width: 100rpx; height: 100rpx; transition: bottom 0.3s; background: #fff; z-index: 1; } ...
前面提到,scroll-view
的內容是 Feed 流,那麼首先就要想到使用 列表渲染。並且,爲了方便在我的主頁複用,列表渲染中的每個 item 都要抽象出來。這時就要使用小程序中的 Custom-Component 功能了。
新建一個名爲 post-item
的 Component
,其中 wxml 的實現(路徑:pages/circle/component/post-item/post-item.js
):
<view class="post-item" hover-class="post-item-hover" bindlongpress="onItemLongTap" bindtap="onItemTap" > <view class="post-title"> <view class="author" hover-class="author-hover" catchtap="onAuthorTap" >{{data.author}}</view > <view class="date">{{data.formatDate}}</view> </view> <view class="msg-wrapper"> <text class="msg">{{data.msg}}</text> </view> <view class="image-outer" wx:if="{{data.photoId !== ''}}" catchtap="onImgTap"> <image-wrapper is="image-wrapper" src="{{data.photoId}}" /> </view> </view>
可見,一個 poster-item
最主要有如下信息:
其中,圖片內容由於是可選的,因此使用了 條件渲染,這會在沒有圖片信息時不讓圖片顯示區域佔用屏幕空間。另外,圖片內容主要是由 image-wrapper
組成,它也是一個 Custom-Component
,主要功能是:
具體代碼這裏就不展現了,比較簡單,讀者能夠在 component/image-wrapper
裏找到。
回過頭看 main-area
的其餘新增部分,細心的讀者會發現有這麼一句:
<view wx:if="{{pageMainData.length === 0}}" class="item-placeholder" >無數據</view >
這會在 Feed 流暫時沒有獲取到數據時給用戶一個提示。
展現 Feed 流的部分已經編寫完畢,如今就差實際數據了。根據上一小節 poster-item
的主要信息,咱們能夠初步推斷出一條迷你微博在 雲數據庫 的 collection poster
裏是這樣存儲的:
{ "username": "Tester", "date": "2019-07-22 12:00:00", "text": "Ceshiwenben", "photo": "xxx" }
先來看 username
。因爲社交平臺通常不會限制用戶的暱稱,因此若是每條迷你微博都存儲暱稱,那未來每次用戶修改一次暱稱,就要遍歷數據庫把全部迷你微博項都改一遍,至關耗費時間,因此咱們不如存儲一個 userId
,並另外把 id 和 暱稱 的對應關係存在另外一個叫 poster_users
的 collection 裏。
{ "userId": "xxx", "name": "Tester", ...(其餘用戶信息) }
userId
從哪裏拿呢?固然是經過以前已經受權的獲取用戶信息接口拿到了,詳細操做以後會說到。
接下來是 date
,這裏最好是服務器時間(由於客戶端傳過來的時間可能會有偏差),而云開發文檔裏也有提供相應的接口:serverDate。這個數據能夠直接被 new Date()
使用,能夠理解爲一個 UTC 時間。
text
即文本信息,直接存儲便可。
photo
則表示附圖數據,可是限於小程序 image
元素的實現,想要顯示一張圖片,要麼提供該圖片的 url,要麼提供該圖片在 雲存儲 的 id,因此這裏最佳的實踐是:先把圖片上傳到雲存儲裏,而後把回調裏的文件 id 做爲數據存儲。
綜上所述,最後 poster
每一項的數據結構以下:
{ "authorId": "xxx", "date": "utc-format-date", "text": "Ceshiwenben", "photoId": "yyy" }
肯定數據結構後,咱們就能夠開始往 collection 添加數據了。可是,在此以前,咱們還缺乏一個重要步驟。
沒錯,咱們尚未在 poster_users
裏添加一條新用戶的信息。這個步驟通常在 pages/circle/circle
頁面首次加載時判斷便可:
getUserId: function(cb) { let that = this var value = this.data.userId || wx.getStorageSync("userId") if (value) { if (cb) { cb(value) } return value } wx.getSetting({ success(res) { if (res.authSetting["scope.userInfo"]) { wx.getUserInfo({ withCredentials: true, success: function(userData) { wx.setStorageSync("userId", userData.signature) that.setData({ userId: userData.signature }) db.collection("poster_users") .where({ userId: userData.signature }) .get() .then(searchResult => { if (searchResult.data.length === 0) { wx.showToast({ title: "新用戶錄入中" }) db.collection("poster_users") .add({ data: { userId: userData.signature, date: db.serverDate(), name: userData.userInfo.nickName, gender: userData.userInfo.gender } }) .then(res => { console.log(res) if (res.errMsg === "collection.add:ok") { wx.showToast({ title: "錄入完成" }) if (cb) cb() } }) .catch(err => { wx.showToast({ title: "錄入失敗,請稍後重試", image: "/images/error.png" }) wx.navigateTo({ url: "/pages/index/index" }) }) } else { if (cb) cb() } }) } }) } else { wx.showToast({ title: "登錄失效,請從新受權登錄", image: "/images/error.png" }) wx.navigateTo({ url: "/pages/index/index" }) } } }) }
代碼實現比較複雜,總體思路是這樣的:
userId
,若是有直接返回並調用回調函數,若是沒有繼續 2wx.getSetting
獲取當前設置信息res.authSetting["scope.userInfo"]
說明已經受權讀取用戶信息,繼續 3,沒有受權的話就跳轉回首頁從新受權wx.getUserInfo
獲取用戶信息,成功後提取出 signature
(這是每一個微信用戶的惟一簽名),並調用 wx.setStorageSync
將其緩存db.collection().where().get()
,判斷返回的數據是不是空數組,若是不是說明該用戶已經錄入(注意 where()
中的篩選條件),若是是說明該用戶是新用戶,繼續 5db.collection().add()
來添加用戶信息,最後經過回調判斷是否錄入成功,並提示用戶不知不覺咱們就使用了雲開發中的 雲數據庫 功能,緊接着咱們就要開始使用 雲存儲 和 雲函數了!
發送新的迷你微博,須要一個編輯新迷你微博的界面,路徑我定爲 pages/circle/add-poster/add-poster
:
<view class="app-poster-container"> <view class="body"> <view class="text-area-wrapper"> <textarea bindinput="bindTextInput" placeholder="在此填寫" value="{{text}}" auto-focus="true" /> <view class="text-area-footer"> <text>{{remainLen}}/140</text> </view> </view> <view bindtap="onImageTap" class="image-area"> <view class="image-outer"> <image-wrapper is="image-wrapper" src="{{imageSrc}}" placeholder="選擇圖片上傳" /> </view> </view> </view> <view class="footer"> <button class="footer-btn" bindtap="onSendTap">發送</button> </view> </view>
wxml 的代碼很好理解:textarea
顯示編輯文本,image-wrapper
顯示須要上傳的圖片,最下面是一個發送的 button
。其中,圖片編輯區域的 bindtap
事件實現:
onImageTap: function() { let that = this wx.chooseImage({ count: 1, success: function(res) { const tempFilePaths = res.tempFilePaths that.setData({ imageSrc: tempFilePaths[0] }) } }) }
直接經過 wx.chooseImage
官方 API 獲取本地圖片的臨時路徑便可。而當發送按鈕點擊後,會有以下代碼被執行:
onSendTap: function() { if (this.data.text === "" && this.data.imageSrc === "") { wx.showModal({ title: "錯誤", content: "不能發送空內容", showCancel: false, confirmText: "好的" }) return } const that = this wx.showLoading({ title: "發送中", mask: true }) const imageSrc = this.data.imageSrc if (imageSrc !== "") { const finalPath = imageSrc.replace("//", "/").replace(":", "") wx.cloud .uploadFile({ cloudPath: finalPath, filePath: imageSrc // 文件路徑 }) .then(res => { that.sendToDb(res.fileID) }) .catch(error => { that.onSendFail() }) } else { that.sendToDb() } }, sendToDb: function(fileId = "") { const that = this const posterData = { authorId: that.data.userId, msg: that.data.text, photoId: fileId, date: db.serverDate() } db.collection("poster") .add({ data: { ...posterData } }) .then(res => { wx.showToast({ title: "發送成功" }) wx.navigateBack({ delta: 1 }) }) .catch(error => { that.onSendFail() }) .finally(wx.hideLoading()) }
db.collection().add()
,發送成功後退回上一頁(即首頁),若是失敗則執行 onSendFail
函數,後者見源碼,邏輯較簡單這裏不贅述因而,咱們就這樣建立了第一條迷你微博。接下來就讓它在 Feed 流中顯示吧!
這個函數的主要做用如前所述,就是經過處理雲數據庫中的數據,將最終數據返回給客戶端,後者將數據可視化給用戶。咱們先作一個初步版本,由於如今 poster_users
中只有一條數據,因此僅先展現本身的迷你微博。getMainPageData
雲函數代碼以下:
// 雲函數入口文件 const cloud = require("wx-server-sdk") cloud.init() const db = cloud.database() // 雲函數入口函數 exports.main = async (event, context, cb) => { // 經過 event 獲取入參 const userId = event.userId let followingResult let users // idNameMap 負責存儲 userId 和 name 的映射關係 let idNameMap = {} let followingIds = [] // 獲取用戶信息 followingResult = await db .collection("poster_users") .where({ userId: userId }) .get() users = followingResult.data followingIds = users.map(u => { return u.userId }) users.map(u => { idNameMap[u.userId] = u.name }) // 獲取動態 const postResult = await db .collection("poster") .orderBy("date", "desc") .where({ // 經過高級篩選功能篩選出符合條件的 userId authorId: db.command.in(followingIds) }) .get() const postData = postResult.data // 向返回的數據添加 存儲用戶暱稱的 author 屬性、存儲格式化後的時間的 formatDate 屬性 postData.map(p => { p.author = idNameMap[p.authorId] p.formatDate = new Date(p.date).toLocaleDateString("zh-Hans", options) }) return postData }
最後在 pages/circle/circle.js
裏補充雲調用:
getMainPageData: function(userId) { const that = this wx.cloud .callFunction({ name: "getMainPageData", data: { userId: userId, isEveryOne: that.data.groupArrayIndex === 0 ? false : true } }) .then(res => { that.setData({ pageMainData: res.result, pageMainLoaded: true }) }) .catch(err => { wx.showToast({ title: "獲取動態失敗", image: "/images/error.png" }) wx.hideLoading() }) }
便可展現 Feed 流數據給用戶。
以後,getMainPageData
還會根據使用場景的不一樣,新增了查詢全部用戶動態、查詢關注用戶動態的功能,可是原理是同樣的,看源碼能夠輕易理解,後續就再也不說明。
上一節中咱們一口氣把雲開發中的大部分主要功能:雲數據庫、雲存儲、雲函數、雲調用都用了一遍,接下來其餘功能的實現也基本都依賴它們。
首先咱們須要建一個新的 collection poster_user_follows
,其中的每一項數據的數據結構以下:
{ "followerId": "xxx", "followingId": "xxx" }
很簡單,followerId
表示關注人,followingId
表示被關注人。
關注或者取消關注須要進入他人的我的主頁操做,咱們在 pages/circle/user-data/user-data.wxml
中放一個 user-info
的自定義組件,而後新建該組件編輯:
<view class="user-info"> <view class="info-item" hover-class="info-item-hover">用戶名: {{userName}}</view> <view class="info-item" hover-class="info-item-hover" bindtap="onPosterCountTap">動態數: {{posterCount}}</view> <view class="info-item" hover-class="info-item-hover" bindtap="onFollowingCountTap">關注數: {{followingCount}}</view> <view class="info-item" hover-class="info-item-hover" bindtap="onFollowerCountTap">粉絲數: {{followerCount}}</view> <view class="info-item" hover-class="info-item-hover" wx:if="{{originId && originId !== '' && originId !== userId}}"><button bindtap="onFollowTap">{{followText}}</button></view> </view>
這裏注意條件渲染的 button
:若是當前訪問我的主頁的用戶 id (originId) 和 被訪問的用戶 id (userId)的值是相等的話,這個按鈕就不會被渲染(本身不能關注/取消關注本身)。
咱們重點看下 onFollowTap
的實現:
onFollowTap: function() { const that = this // 判斷當前關注狀態 if (this.data.isFollow) { wx.showLoading({ title: "操做中", mask: true }) wx.cloud .callFunction({ name: "cancelFollowing", data: { followerId: this.properties.originId, followingId: this.properties.userId } }) .then(res => { wx.showToast({ title: "取消關注成功" }) that.setData({ isFollow: false, followText: "關注" }) }) .catch(error => { wx.showToast({ title: "取消關注失敗", image: "/images/error.png" }) }) .finally(wx.hideLoading()) } else if (this.data.isFollow !== undefined) { wx.showLoading({ title: "操做中", mask: true }) const data = { followerId: this.properties.originId, followingId: this.properties.userId } db.collection("poster_user_follows") .add({ data: { ...data } }) .then(res => { wx.showToast({ title: "關注成功" }) that.setData({ isFollow: true, followText: "取消關注" }) }) .catch(error => { wx.showToast({ title: "關注失敗", image: "/images/error.png" }) }) .finally(wx.hideLoading()) } } }
這裏讀者可能會有疑問:爲何關注的時候直接調用 db.collection().add()
便可,而取消關注卻要調用雲函數呢?這裏涉及到雲數據庫的設計問題:刪除多個數據的操做,或者說刪除使用 where
篩選的數據,只能在服務端執行。若是確實想在客戶端刪除,則在查詢用戶關係時,將惟一標識數據的 _id
用 setData
存下來,以後再使用 db.collection().doc(_id).delete()
刪除便可。這兩種實現方式讀者可自行選擇。固然,還有一種實現是不實際刪除數據,只是加個 isDelete
字段標記一下。
查詢用戶關係的實現很簡單,雲函數的實現方式以下:
// 雲函數入口文件 const cloud = require('wx-server-sdk') cloud.init() const db = cloud.database() // 雲函數入口函數 exports.main = async(event, context) => { const followingResult = await db.collection("poster_user_follows") .where({ followingId: event.followingId, followerId: event.followerId }).get() return followingResult }
客戶端只要檢查返回的數據長度是否大於 0 便可。
另外附上 user-data
頁面其餘數據的獲取雲函數實現:
// 雲函數入口文件 const cloud = require("wx-server-sdk") cloud.init() const db = cloud.database() async function getPosterCount(userId) { return { value: (await db.collection("poster").where({ authorId: userId }).count()).total, key: "posterCount" } } async function getFollowingCount(userId) { return { value: (await db.collection("poster_user_follows").where({ followerId: userId }).count()).total, key: "followingCount" } } async function getFollowerCount(userId) { return { value: (await db.collection("poster_user_follows").where({ followingId: userId }).count()).total, key: "followerCount" } } async function getUserName(userId) { return { value: (await db.collection("poster_users").where({ userId: userId }).get()).data[0].name, key: "userName" } } // 雲函數入口函數 exports.main = async (event, context) => { const userId = event.userId const tasks = [] tasks.push(getPosterCount(userId)) tasks.push(getFollowerCount(userId)) tasks.push(getFollowingCount(userId)) tasks.push(getUserName(userId)) const allData = await Promise.all(tasks) const finalData = {} allData.map(d => { finalData[d.key] = d.value }) return finalData }
很好理解,客戶端獲取返回後直接使用便可。
這部分其實很好實現。關鍵的搜索函數實現以下:
// 雲函數入口文件 const cloud = require('wx-server-sdk') cloud.init() const db = cloud.database() const MAX_LIMIT = 100 async function getDbData(dbName, whereObj) { const totalCountsData = await db.collection(dbName).where(whereObj).count() const total = totalCountsData.total const batch = Math.ceil(total / 100) const tasks = [] for (let i = 0; i < batch; i++) { const promise = db .collection(dbName) .where(whereObj) .skip(i * MAX_LIMIT) .limit(MAX_LIMIT) .get() tasks.push(promise) } const rrr = await Promise.all(tasks) if (rrr.length !== 0) { return rrr.reduce((acc, cur) => { return { data: acc.data.concat(cur.data), errMsg: acc.errMsg } }) } else { return { data: [], errMsg: "empty" } } } // 雲函數入口函數 exports.main = async (event, context) => { const text = event.text const data = await getDbData("poster_users", { name: { $regex: text } }) return data }
這裏參考了官網所推薦的分頁檢索數據庫數據的實現(由於搜索結果可能有不少),篩選條件則是正則模糊匹配關鍵字。
搜索頁面的源碼路徑是 pages/circle/search-user/search-user
,實現了點擊搜索結果項跳轉到對應項的用戶的 user-data
頁面,建議直接閱讀源碼理解。
因爲轉發、評論、點讚的原理基本相同,因此這裏只介紹點贊功能如何編寫,另外兩個功能讀者能夠自行實現。
毫無疑問咱們須要新建一個 collection poster_likes
,其中每一項的數據結構以下:
{ "posterId": "xxx", "likeId": "xxx" }
這裏的 posterId
就是 poster
collection 裏每條記錄的 _id
值,likeId
就是 poster_users
裏的 userId
了。
而後咱們擴展一下 poster-item
的實現:
<view class="post-item" hover-class="post-item-hover" bindlongpress="onItemLongTap" bindtap="onItemTap"> ... <view class="interact-area"> <view class="interact-item"> <button class="interact-btn" catchtap="onLikeTap" style="color:{{liked ? '#55aaff' : '#000'}}">贊 {{likeCount}}</button> </view> </view> </view>
即,新增一個 interact-area
,其中 onLikeTap
實現以下:
onLikeTap: function() { if (!this.properties.originId) return const that = this if (this.data.liked) { wx.showLoading({ title: "操做中", mask: true }) wx.cloud .callFunction({ name: "cancelLiked", data: { posterId: this.properties.data._id, likeId: this.properties.originId } }) .then(res => { wx.showToast({ title: "取消成功" }) that.refreshLike() that.triggerEvent('likeEvent'); }) .catch(error => { wx.showToast({ title: "取消失敗", image: "/images/error.png" }) }) .finally(wx.hideLoading()) } else { wx.showLoading({ title: "操做中", mask: true }) db.collection("poster_likes").add({ data: { posterId: this.properties.data._id, likeId: this.properties.originId } }).then(res => { wx.showToast({ title: "已贊" }) that.refreshLike() that.triggerEvent('likeEvent'); }) .catch(error => { wx.showToast({ title: "贊失敗", image: "/images/error.png" }) }) .finally(wx.hideLoading()) } }
細心的讀者會發現這和關注功能原理幾乎是同樣的。
咱們可使用不少方式讓主頁面刷新數據:
onShow: function() { wx.showLoading({ title: "加載中", mask: true }) const that = this function cb(userId) { that.refreshMainPageData(userId) that.refreshMePageData(userId) } this.getUserId(cb) }
第一種是利用 onShow
方法:它會在頁面每次從後臺轉到前臺展現時調用,這個時候咱們就能刷新頁面數據(包括 Feed 流和我的信息)。可是這個時候用戶信息可能會丟失,因此咱們須要在 getUserId
裏判斷,並將刷新數據的函數們整合起來,做爲回調函數。
第二種是讓用戶手動刷新:
onPageMainTap: function() { if (this.data.currentPage === "main") { this.refreshMainPageData() } this.setData({ currentPage: "main" }) }
如圖所示,當目前頁面是 Feed 流時,若是再次點擊 首頁 Tab,就會強制刷新數據。
第三種是關聯數據變動觸發刷新,好比動態類型選擇、刪除了一條動態之後觸發數據的刷新。這種能夠直接看源碼學習。
當用戶第一次進入主頁面時,咱們若是想在 Feed 流和我的信息都加載好了再容許用戶操做,應該如何實現?
若是是相似 Vue 或者 React 的框架,咱們很容易就能想到屬性監控,如 watch
、useEffect
等等,可是小程序目前 Page
並無提供屬性監控功能,怎麼辦?
除了本身實現,還有一個方法就是利用 Component
的 observers
,它和上面提到的屬性監控功能差很少。雖然官網文檔對其說明比較少,但摸索了一番仍是能用來監控的。
首先咱們來新建一個 Component
叫 abstract-load
,具體實現以下:
// pages/circle/component/abstract-load.js Component({ properties: { pageMainLoaded: { type: Boolean, value: false }, pageMeLoaded: { type: Boolean, value: false } }, observers: { "pageMainLoaded, pageMeLoaded": function (pageMainLoaded, pageMeLoaded) { if (pageMainLoaded && pageMeLoaded) { this.triggerEvent("allLoadEvent") } } } })
而後在 pages/circle/circle.wxml
中添加一行:
<abstract-load is="abstract-load" pageMainLoaded="{{pageMainLoaded}}" pageMeLoaded="{{pageMeLoaded}}" bind:allLoadEvent="onAllLoad" />
最後實現 onAllLoad
函數便可。
另外,像這種沒有實際展現數據的 Component
,建議在項目中都用 abstract
開頭來命名。
若是讀者使用 iOS 系統調試這個小程序,可能會發現 Feed 流比較短的時候,滾動 scroll-view
header 和 button
會有鬼畜的上下抖動現象,這是由於 iOS 本身實現的 WebView 對於滾動視圖有回彈的效果,而該效果也會觸發滾動事件。
對於這個 bug,官方人員也表示暫時沒法修復,只能先忍一忍了。
讀者可能會疑惑我爲何沒有講解消息 Tab 以及消息提醒的實現。首先是由於源碼沒有這個實現,其次是我以爲目前雲開發所提供的能力實現主動提醒比較麻煩(除了輪詢想不到其餘辦法)。
但願將來雲開發能夠提供 數據庫長鏈接監控 的功能,這樣經過訂閱者模式能夠很輕鬆地獲取到數據更新的狀態,主動提醒也就更容易實現了。到那時我可能會再更新相關源碼。
讀者可能會發現我有一個叫 benchmark
的雲函數,這個函數只是作了個查詢數據庫的操做,目的在於計算查詢耗時。
詭異的是,我前天在調試的時候,發現查詢一次須要1秒鐘,而寫這篇文章時卻不到100ms。建議在一些須要屢次操做數據庫的函數配置裏,把超時時間設置長一點吧。目前雲函數的性能不太穩定。
那麼關於迷你版微博開發實戰介紹就到此爲止了,更多資料能夠直接下載源碼查看哦。
https://github.com/TencentCloudBase/Good-practice-tutorial-recommended
若是你有關於使用雲開發CloudBase相關的技術故事/技術實戰經驗想要跟你們分享,歡迎留言聯繫咱們哦~比心!