本文将手把手教你如何写出迷你版微博的一行行代码,迷你版微博包含如下功能: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相关的技术故事/技术实战经验想要跟你们分享,欢迎留言联系咱们哦~比心!