歡迎繼續(xù)閱讀《Taro 小程序開發(fā)大型實(shí)戰(zhàn)》系列,前情回顧:
- 熟悉的 React,熟悉的 Hooks:我們用 React 和 Hooks 實(shí)現(xiàn)了一個(gè)非常簡(jiǎn)單的添加帖子的原型
- 多頁面跳轉(zhuǎn)和 Taro UI 組件庫(kù):我們用 Taro 自帶的路由功能實(shí)現(xiàn)了多頁面跳轉(zhuǎn),并用 Taro UI 組件庫(kù)升級(jí)了應(yīng)用界面
- 實(shí)現(xiàn)微信和支付寶多端登錄:實(shí)現(xiàn)了微信、支付寶以及普通登錄和退出登錄
- 使用 Hooks 版的 Redux 實(shí)現(xiàn)大型應(yīng)用狀態(tài)管理(上篇):使用 Hooks 版的 Redux 實(shí)現(xiàn)了
user
邏輯的狀態(tài)管理重構(gòu) - 使用 Hooks 版的 Redux 實(shí)現(xiàn)大型應(yīng)用狀態(tài)管理(下篇):使用 Hooks 版的 Redux 實(shí)現(xiàn)了
post
邏輯的狀態(tài)管理重構(gòu) - Taro 小程序開發(fā)大型實(shí)戰(zhàn)(六):嘗鮮微信小程序云(上篇):
user
邏輯接入微信小程序云
在上一篇文章中,我們將我們兩大邏輯之一 User 部分接入了 Redux 異步處理流程,接著接入了微信小程序云,使得 User 邏輯可以在云端永久保存,好不自在:),兩兄弟一個(gè)得了好處,另外一個(gè)不能干瞪眼對(duì)吧?在這一篇教程中,我們想辦法把 User 另外一個(gè)兄弟 Post 撈上來,也把 Redux 異步流程和微信小程序給它整上,這樣就齊活了。
我們首先來看一看最終的完成效果:
如果你不熟悉 Redux,推薦閱讀我們的《Redux 包教包會(huì)》系列教程:
- Redux 包教包會(huì)(一):解救 React 狀態(tài)危機(jī)
- Redux 包教包會(huì)(二):趁熱打鐵,完全重構(gòu)
- Redux 包教包會(huì)(三):各司其職,重拾初心
如果你希望直接從這一步開始,請(qǐng)運(yùn)行以下命令:
git clone -b miniprogram-start https://github.com/tuture-dev/ultra-club.git
cd ultra-club
本文所涉及的源代碼都放在了Github 上,如果您覺得我們寫得還不錯(cuò),希望您能給?這篇文章點(diǎn)贊+Github倉(cāng)庫(kù)加星?哦~
此教程屬于 React 前端工程師學(xué)習(xí)路線的一部分,歡迎來 Star 一波,鼓勵(lì)我們繼續(xù)創(chuàng)作出更好的教程,持續(xù)更新中~
“六脈神劍” 搞定 createPost 異步邏輯
不知道看到這里的讀者有沒有發(fā)現(xiàn)上篇文章其實(shí)打造了一套講解模式,即按照如下的 “六步流程” 來講解,我們也稱為 “六脈神劍” 講解法:
- 將組件中的同步邏輯重構(gòu)到異步邏輯
- 聲明和補(bǔ)充對(duì)應(yīng)需要的異步
sagas
文件 - 定義
sagas
需要的常量文件 - 定義
sagas
涉及到的前端 API 文件 - 創(chuàng)建對(duì)于的微信小程序云函數(shù),并編寫對(duì)應(yīng)的 Node.js 處理邏輯
- 定義對(duì)應(yīng)的
reducers
文件 - 如此往復(fù)
可以看到我們上面的講解順序?qū)嶋H上是按照前端數(shù)據(jù)流的流動(dòng)來進(jìn)行的,我們對(duì)標(biāo)上面的講解邏輯來看一下前端數(shù)據(jù)流是如何流動(dòng)的:
- 從組件中通過對(duì)應(yīng)的常量發(fā)起異步請(qǐng)求
-
sagas
監(jiān)聽到對(duì)應(yīng)的異步請(qǐng)求,開始處理流程 - 在
sagas
調(diào)用對(duì)應(yīng)的前端 API 文件向微信小程序云發(fā)起請(qǐng)求 - 微信小程序云函數(shù)處理對(duì)應(yīng)的 API 請(qǐng)求,返回?cái)?shù)據(jù)
-
sagas
中獲取到對(duì)應(yīng)的數(shù)據(jù),dispatch
action 到對(duì)應(yīng)的reducers
處理邏輯 -
reducers
接收數(shù)據(jù),開始更新本地 Redux Store 中的state
- 組件中重新渲染
好的,了解了講解邏輯和對(duì)應(yīng)前端數(shù)據(jù)流動(dòng)邏輯之后,我們馬上來實(shí)踐這套邏輯,把 User 邏輯的好兄弟 Post 邏輯搞定。
第一劍:PostForm 組件中發(fā)起異步請(qǐng)求
首先從創(chuàng)建帖子邏輯動(dòng)刀子,我們將創(chuàng)建帖子接入異步邏輯并接通小程序云,讓文章上云。打開 src/components/PostForm/index.jsx
,對(duì)其中的內(nèi)容作出對(duì)應(yīng)的修改如下:
import { useDispatch, useSelector } from '@tarojs/redux'
import './index.scss'
import { CREATE_POST } from '../../constants'
export default function PostForm() {
const [formTitle, setFormTitle] = useState('')
const [formContent, setFormContent] = useState('')
const userId = useSelector(state => state.user.userId)
const dispatch = useDispatch()
... }
dispatch({
type: CREATE_POST,
payload: {
postData: {
title: formTitle,
content: formContent,
},
userId,
},
})
setFormTitle('')
setFormContent('')
}
return (
可以看到,上面的內(nèi)容做了如下四處修改:
- 首先我們現(xiàn)在是接收用戶的文章輸入數(shù)據(jù)然后向小程序云發(fā)起創(chuàng)建文章的請(qǐng)求,所以我們將之前的
dispatch
SET_POSTS Action 改為 CREATE_POST Action,并且將之前的 action payload 簡(jiǎn)化為postData
和userId
,因?yàn)槲覀兛梢酝ㄟ^小程序云數(shù)據(jù)庫(kù)查詢userId
得到創(chuàng)建文章的用戶信息,所以不需要再攜帶用戶的數(shù)據(jù)。 - 接著,因?yàn)槲覀儾辉傩枰脩舻?
avatar
和nickName
數(shù)據(jù),所以我們刪掉了對(duì)應(yīng)的useSelector
語句。 - 接著,因?yàn)檎?qǐng)求是異步的,所以需要等待請(qǐng)求完成之后再設(shè)置對(duì)應(yīng)的發(fā)表文章的狀態(tài)以及發(fā)表文章彈出層狀態(tài),所以我們刪掉了對(duì)應(yīng)的
dispatch
SET_POST_FORM_IS_OPENED Action 邏輯以及Taro.atMessage
邏輯。 - 最后我們刪掉不需要的常量
SET_POSTS
和SET_POST_FORM_IS_OPENED
,然后導(dǎo)入異步創(chuàng)建文章的常量CREATE_POST
。
增加 Action 常量
我們?cè)谏弦徊街惺褂玫搅?CREATE_POST
常量,打開 src/constants/post.js
,在其中增加 CREATE_POST
常量:
export const CREATE_POST = 'CREATE_POST'
到這里,我們的 “六步流程” 講解法就走完了第一步,即從組件中發(fā)起對(duì)應(yīng)的異步請(qǐng)求,這里我們是發(fā)出的 action.type
為 CREATE_POST
的異步請(qǐng)求。
第二劍: 聲明和補(bǔ)充對(duì)應(yīng)需要的異步 sagas 文件
在 “第一劍” 中,我們從組件中 dispatch 了 action.type 為 CREATE_POST
的異步 Action,接下來我們要做的就是在對(duì)應(yīng)的 sagas
文件中補(bǔ)齊響應(yīng)這個(gè)異步 action 的 sagas。
在 src/sagas/
文件夾下面創(chuàng)建 post.js
文件,并在其中編寫如下創(chuàng)建文章的邏輯:
import Taro from '@tarojs/taro'
import { call, put, take, fork } from 'redux-saga/effects'
import { postApi } from '../api'
import {
CREATE_POST,
POST_SUCCESS,
POST_ERROR,
SET_POSTS,
SET_POST_FORM_IS_OPENED,
} from '../constants'
function* createPost(postData, userId) {
try {
const post = yield call(postApi.createPost, postData, userId)
// 其實(shí)以下三步可以合成一步,但是這里為了講解清晰,將它們拆分成獨(dú)立的單元
// 發(fā)起發(fā)帖成功的 action
yield put({ type: POST_SUCCESS })
// 關(guān)閉發(fā)帖框彈出層
yield put({ type: SET_POST_FORM_IS_OPENED, payload: { isOpened: false } })
// 更新 Redux store 數(shù)據(jù)
yield put({
type: SET_POSTS,
payload: {
posts: [post],
},
})
// 提示發(fā)帖成功
Taro.atMessage({
message: '發(fā)表文章成功',
type: 'success',
})
} catch (err) {
console.log('createPost ERR: ', err)
// 發(fā)帖失敗,發(fā)起失敗的 action
yield put({ type: POST_ERROR })
// 提示發(fā)帖失敗
Taro.atMessage({
message: '發(fā)表文章失敗',
type: 'error',
})
}
}
function* watchCreatePost() {
while (true) {
const { payload } = yield take(CREATE_POST)
console.log('payload', payload)
yield fork(createPost, payload.postData, payload.userId)
}
}
export { watchCreatePost }
可以看到,上面的改動(dòng)主要是創(chuàng)建 watcherSaga
和 handlerSaga
。
創(chuàng)建 watcherSaga
- 我們創(chuàng)建了登錄的
watcherSaga
:watchCreatePost
,它用來監(jiān)聽action.type
為CREATE_POST
的 action,并且當(dāng)監(jiān)聽到CREATE_POST
action 之后,從這個(gè) action 中獲取必要的postData
和userId
數(shù)據(jù),然后激活handlerSaga
:createPost
去處理對(duì)應(yīng)的創(chuàng)建帖子的邏輯。 - 這里的
watcherSaga
:watchCreatePost
是一個(gè)生成器函數(shù),它內(nèi)部是一個(gè)while
無限循環(huán),表示在內(nèi)部持續(xù)監(jiān)聽CREATE_POST
action。 - 在循環(huán)內(nèi)部,我們使用了
redux-saga
提供的effects helper
函數(shù):take
,它用于監(jiān)聽CREATE_POST
action,獲取 action 中攜帶的數(shù)據(jù)。 - 接著我們使用了另外一個(gè)
effects helper
函數(shù):fork
,它表示非阻塞的執(zhí)行handlerSaga
:createPost
,并將payload.postData
和payload.userId
作為參數(shù)傳給createPost
。
創(chuàng)建 handlerSaga
- 我們創(chuàng)建了創(chuàng)建帖子的
handlerSaga
:createPost
,它用來處理創(chuàng)建邏輯。 -
createPost
也是一個(gè)生成器函數(shù),在它內(nèi)部是一個(gè)try/catch
語句,用于處理創(chuàng)建帖子請(qǐng)求可能存在的錯(cuò)誤情況。 -
在
try
語句中,首先是使用了redux-saga
提供給我們的effects helper
函數(shù):call
來調(diào)用登錄的 API:postApi.createPost
,并把postData
和userId
作為參數(shù)傳給這個(gè) API。- 如果創(chuàng)建帖子成功,我們使用
redux-saga
提供的effects helpers
函數(shù):put
,put
類似之前在view
中的dispatch
操作,,來dispatch
了三個(gè) action:POST_SUCCESS
,SET_POST_FORM_IS_OPENED
,SET_POSTS
,代表更新創(chuàng)建帖子成功的狀態(tài),關(guān)閉發(fā)帖框,設(shè)置最新創(chuàng)建的帖子信息到 Redux Store 中。 - 最后我們使用了 Taro UI 提供給我們的消息框,來顯示一個(gè)
success
消息。
- 如果創(chuàng)建帖子成功,我們使用
- 如果發(fā)帖失敗,我們則使用
put
發(fā)起一個(gè)POST_ERROR
的 action 來更新創(chuàng)建帖子失敗的信息到 Redux Store,接著使用了 Taro UI 提供給我們的消息框,來顯示一個(gè)error
消息。
一些額外的工作
為了創(chuàng)建 watcherSaga
和 handlerSaga
,我們還導(dǎo)入了 postApi
,我們將在后面來創(chuàng)建這個(gè) API。
除此之外我們還導(dǎo)入了需要使用的 action 常量:
-
POST_SUCCESS
:設(shè)置處理帖子邏輯成功信息 -
POST_ERROR
:設(shè)置處理帖子邏輯失敗信息 -
SET_POSTS
:將新帖子添加到 Redux Store -
CREATE_POST
: 相應(yīng)創(chuàng)建帖子的常量 -
SET_POST_FORM_IS_OPENED
:更新發(fā)帖框的開閉邏輯
這里的 POST_SUCCESS
和 POST_ERROR
我們還沒有創(chuàng)建,我們將馬上在 “下一劍” 中創(chuàng)建它。
以及一些 redux-saga/effects
相關(guān)的 helper 函數(shù),我們已經(jīng)在之前的內(nèi)容中詳細(xì)講過了,這里就不再贅述了。
加入 saga 中心調(diào)度文件
我們像之前將 watchLogin
等加入到 sagas
中心調(diào)度文件一樣,將我們創(chuàng)建好的 watchCreatePost
也加入進(jìn)去:
// ...之前的邏輯
import { watchCreatePost } from './post'
export default function* rootSaga() {
yield all([
// ... 之前的邏輯
fork(watchCreatePost)
])
}
第三劍:定義 sagas 需要的常量文件
打開 src/constants/post.js
文件,定義我們之前創(chuàng)建的常量文件如下:
export const POST_SUCCESS = 'POST_SUCCESS'
export const POST_ERROR = 'POST_ERROR'
第四劍:定義 sagas 涉及到的前端 API 文件
在之前的 post
saga 文件里面,我們使用到了 postApi
,它里面封裝了用于向后端(這里我們是小程序云)發(fā)起和帖子有關(guān)請(qǐng)求的邏輯,讓我們馬上來實(shí)現(xiàn)它吧。
在 src/api/
文件夾下添加 post.js
文件,并在文件中編寫內(nèi)容如下:
import Taro from '@tarojs/taro'
async function createPost(postData, userId) {
const isWeapp = Taro.getEnv() === Taro.ENV_TYPE.WEAPP
const isAlipay = Taro.getEnv() === Taro.ENV_TYPE.ALIPAY
console.log('postData', postData, userId)
// 針對(duì)微信小程序使用小程序云函數(shù),其他使用小程序 RESTful API
try {
if (isWeapp) {
const { result } = await Taro.cloud.callFunction({
name: 'createPost',
data: {
postData,
userId,
},
})
return result.post
}
} catch (err) {
console.error('createPost ERR: ', err)
}
}
const postApi = {
createPost,
}
export default postApi;
在上面的代碼中,我們定義了 createPost
函數(shù),它是一個(gè) async
函數(shù),用來處理異步邏輯,在 createPost
函數(shù)中,我們對(duì)當(dāng)前的環(huán)境進(jìn)行了判斷,且只在微信小程序,即 isWeapp
的條件下執(zhí)行創(chuàng)建帖子的操作,對(duì)于支付寶小程序和 H5,我們則放在下一節(jié)使用 LeanCloud 的 Serverless 來解決。
創(chuàng)建帖子邏輯是一個(gè) try/catch
語句,用于捕捉可能存在的請(qǐng)求錯(cuò)誤,在 try
代碼塊中,我們使用了 Taro
為我們提供的微信小程序云的云函數(shù) API Taro.cloud.callFunction
來便捷的向小程序云發(fā)起云函數(shù)調(diào)用請(qǐng)求。
這里我們調(diào)用了一個(gè) createPost
云函數(shù),并將 postData
和 userId
作為參數(shù)傳給云函數(shù),用于在云函數(shù)中使用用戶 Id 和帖子數(shù)據(jù)來創(chuàng)建一個(gè)屬于此用戶的帖子并保存到數(shù)據(jù)庫(kù),我們將在下一節(jié)中實(shí)現(xiàn)這個(gè)云函數(shù)。
如果調(diào)用成功,我們可以接收返回值,用于從后端返回?cái)?shù)據(jù),這里我們返回了 result.post
數(shù)據(jù)。
如果調(diào)用失敗,則打印錯(cuò)誤。
最后我們定義了一個(gè) postApi
對(duì)象,用于存放所有和用戶邏輯有個(gè)的函數(shù),并添加 createPost
API 屬性然后將其導(dǎo)出,這樣在 post
saga 函數(shù)里面就可以導(dǎo)入 postApi
然后通過 postApi.createPost
的方式來調(diào)用 createPost
API 處理創(chuàng)建帖子的邏輯了。
在 API 默認(rèn)文件統(tǒng)一導(dǎo)出
在 src/api/index.js
文件中導(dǎo)入上面創(chuàng)建的 postApi
并進(jìn)行統(tǒng)一導(dǎo)出如下:
import postApi from './post'
export { postApi }
第五劍:創(chuàng)建對(duì)應(yīng)的微信小程序云函數(shù)
創(chuàng)建 createPost 云函數(shù)
按照和之前創(chuàng)建 login
云函數(shù)類似,我們創(chuàng)建 createPost
云函數(shù)。
創(chuàng)建成功之后,我們可以得到兩個(gè)文件,一個(gè)是 functions/createPost/package.json
文件,它和之前的類似。
{
"name": "createPost",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"wx-server-sdk": "latest"
}
}
第二個(gè)文件就是我們需要編寫創(chuàng)建帖子邏輯的 functions/createPost/index.js
文件,微信小程序開發(fā)者工具會(huì)默認(rèn)為我們生成一段樣板代碼。
我們?cè)?function/createPost
文件夾下同樣運(yùn)行 npm install
安裝對(duì)應(yīng)的云函數(shù)依賴,這樣我們才能運(yùn)行它。
編寫 createPost 云函數(shù)
打開 functions/createPost/index.js
文件,對(duì)其中的內(nèi)容作出對(duì)應(yīng)的修改如下:
// 云函數(shù)入口文件
const cloud = require('wx-server-sdk')
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV,
})
const db = cloud.database()
// 云函數(shù)入口函數(shù)
exports.main = async (event, context) => {
const { postData, userId } = event
console.log('event', event)
try {
const user = await db
.collection('user')
.doc(userId)
.get()
const { _id } = await db.collection('post').add({
data: {
...postData,
user: user.data,
createdAt: db.serverDate(),
updatedAt: db.serverDate(),
},
})
const newPost = await db
.collection('post')
.doc(_id)
.get()
return {
post: { ...newPost.data },
}
} catch (err) {
console.error(`createUser ERR: ${err}`)
}
}
可以看到上面的代碼改動(dòng)主要有以下七處:
- 首先我們給
cloud.init()
傳入了環(huán)境參數(shù),我們使用了內(nèi)置的cloud.DYNAMIC_CURRENT_ENV
,表示自動(dòng)設(shè)置為當(dāng)前的云環(huán)境,即在右鍵點(diǎn)擊小程序開發(fā)者工具里functions
文件夾時(shí)選擇的環(huán)境。 - 接著,我們通過
cloud.database()
生成了數(shù)據(jù)實(shí)例db
,用于之后在函數(shù)體中便捷的操作云數(shù)據(jù)庫(kù)。 - 接著就是
main
函數(shù)體,我們首先從event
對(duì)象中取到了在小程序的調(diào)用Taro.cloud.callFunction
傳過來的postData
和userId
數(shù)據(jù)。 - 然后,跟著取數(shù)據(jù)的是一個(gè)
try/catch
語句塊,用于捕獲錯(cuò)誤,在try
語句塊中,我們使用db
的查詢操作:db.collection('user').doc(userId).get()
,表示查詢id
為userId
的user
表數(shù)據(jù),它查出來應(yīng)該是個(gè)唯一值,如果不存在滿足where
條件的,那么是一個(gè)null
值,如果存在滿足 條件的,那么返回一個(gè)user
對(duì)象。 - 接著,我們使用的
db.collection('post').add()
添加一個(gè)post
數(shù)據(jù),然后在add
方法中傳入data
字段,這里我們不僅傳入了postData
,還將user
也一同傳入了,原因我們將在之后來講解。除此之外,這里我們額外使用了db.serverDate()
用于記錄創(chuàng)建此帖子的時(shí)間和更新此帖子的時(shí)間,方便之后做條件查詢。 - 接著,因?yàn)橄驍?shù)據(jù)庫(kù)添加一個(gè)記錄之后只會(huì)返回此記錄的
_id
,所以我們需要一個(gè)額外的操作db.collection('post').doc()
來獲取此條記錄,這個(gè)doc
用于獲取指定的記錄引用,返回的是這條數(shù)據(jù),而不是一個(gè)數(shù)組。 - 最后我們返回新創(chuàng)建的
post
。
提示
我們?cè)谏厦鎰?chuàng)建
post
的時(shí)候,將user
對(duì)象也添加到了post
數(shù)據(jù)中,這里是因?yàn)樾〕绦蛟茢?shù)據(jù)庫(kù)是 JSON 數(shù)據(jù)庫(kù),所以沒有關(guān)系數(shù)據(jù)庫(kù)的外鍵概念,導(dǎo)致建關(guān)系困難,所以為了之后查詢post
的時(shí)候方便展示user
數(shù)據(jù),我們才這樣保存的. 當(dāng)然更加科學(xué)的做法是在post
里面保存userId
,這樣能減少數(shù)據(jù)冗余,但是因?yàn)樽鼋虒W(xué)用,所以這些我們偷了一點(diǎn)懶。所以我們這里強(qiáng)烈建議,在正規(guī)的環(huán)境下,關(guān)系型數(shù)據(jù)庫(kù)應(yīng)該建外鍵,JSON 數(shù)據(jù)庫(kù)也至少應(yīng)該保存
userId
。:
第六劍: 定義對(duì)應(yīng)的 reducers 文件
我們?cè)谇懊嫣幚韯?chuàng)建帖子時(shí),在組件內(nèi)部 dispatch
了 CREATE_POST
action,在處理異步 action 的 saga 函數(shù)中,使用 put
發(fā)起了一系列更新 store 中帖子狀態(tài)的 action,現(xiàn)在我們馬上來實(shí)現(xiàn)響應(yīng)這些 action 的 reducers
,打開 src/reducers/post.js
,對(duì)其中的代碼做出對(duì)應(yīng)的修改如下:
import {
SET_POST,
SET_POSTS,
SET_POST_FORM_IS_OPENED,
POST_ERROR,
CREATE_POST,
POST_NORMAL,
POST_SUCCESS,
} from '../constants/'
import avatar from '../images/avatar.png'
const INITIAL_STATE = {
posts: [],
post: {},
isOpened: false,
isPost: false,
postStatus: POST_NORMAL,
}
export default function post(state = INITIAL_STATE, action) {
switch (action.type) {
case SET_POST: {
const { post } = action.payload
return { ...state, post }
}
case SET_POSTS: {
const { posts } = action.payload
return { ...state, posts: state.posts.concat(...posts) }
}
case SET_POST_FORM_IS_OPENED: {... return { ...state, isOpened }
}
case CREATE_POST: {
return { ...state, postStatus: CREATE_POST, isPost: true }
}
case POST_SUCCESS: {
return { ...state, postStatus: POST_SUCCESS, isPost: false }
}
case POST_ERROR: {
return { ...state, postStatus: POST_ERROR, isPost: false }
}
default:
return state
}
看一看到上面的代碼主要有三處改動(dòng):
- 首先我們導(dǎo)入了必要的 action 常量
-
接著我們給
INITIAL_STATE
增加了幾個(gè)字段:-
posts
:保存帖子列表的數(shù)據(jù),創(chuàng)建新的帖子也會(huì)保存在這里面。 -
post
:保存單個(gè)帖子,我們將在之后講解用于在獲取帖子詳情時(shí)保存數(shù)據(jù)用。
-
-
isPost
:用于標(biāo)志帖子邏輯過程中是否在執(zhí)行創(chuàng)帖邏輯,true
表示正在執(zhí)行創(chuàng)帖中,false
表示登錄邏輯執(zhí)行完畢-
postStatus
:用于標(biāo)志創(chuàng)帖過程中的狀態(tài):開始創(chuàng)帖(CREATE_POST
)、創(chuàng)帖成功(POST_SUCCESS
)、登錄失敗(POST_ERROR
)
-
- 最后就是
switch
語句中響應(yīng) action,更新相應(yīng)的狀態(tài)。
“六脈神劍” 搞定 getPosts 異步邏輯
在上一 “大” 節(jié)中,我們使用了圖雀社區(qū)不傳之術(shù):“六脈神劍” 搞定了 createPost
的異步邏輯,現(xiàn)在我們馬上趁熱打鐵來鞏固我們的武功,搞定 getPosts 異步邏輯,它對(duì)應(yīng)著我們小程序底部?jī)蓚€(gè) tab 欄的第一個(gè),也就是我們打開小程序的首屏渲染邏輯,也就是一個(gè)帖子列表。
第一劍:index 組件中發(fā)起異步請(qǐng)求
打開 src/pages/index/index.jsx
文件,對(duì)其中的內(nèi)容作出對(duì)應(yīng)的修改如下:
import { PostCard, PostForm } from '../../components'
import './index.scss'
import {
SET_POST_FORM_IS_OPENED,
SET_LOGIN_INFO,
GET_POSTS,
} from '../../constants'
export default function Index() {
const posts = useSelector(state => state.post.posts) || []... const dispatch = useDispatch()
useEffect(() => {
const WeappEnv = Taro.getEnv() === Taro.ENV_TYPE.WEAPP
if (WeappEnv) {
Taro.cloud.init()
}
async function getStorage() {
try {
const { data } = await Taro.getStorage({ key: 'userInfo' })
const { nickName, avatar, _id } = data
// 更新 Redux Store 數(shù)據(jù)
dispatch({
type: SET_LOGIN_INFO,
payload: { nickName, avatar, userId: _id },
})
} catch (err) {
console.log('getStorage ERR: ', err)
}
}
if (!isLogged) {
getStorage()
}
async function getPosts() {
try {
// 更新 Redux Store 數(shù)據(jù)
dispatch({
type: GET_POSTS,
})
} catch (err) {
console.log('getPosts ERR: ', err)
}
}
if (!posts.length) {
getPosts()
}
}, [])
function setIsOpened(isOpened) {
dispatch({ type: SET_POST_FORM_IS_OPENED, payload: { isOpened } })... return (
<View className="index">
<AtMessage />
{posts.map(post => (
<PostCard key={post._id} postId={post._id} post={post} isList />
))}
<AtFloatLayout
isOpened={isOpened}
可以看到,上面的內(nèi)容做了如下四處修改:
- 首先我們對(duì)當(dāng)前的開發(fā)環(huán)境做了判斷,如果是微信小程序環(huán)境,我們就使用
Taro.cloud.init()
進(jìn)行小程序環(huán)境的初始化。 - 接著,我們?cè)?
useEffects
Hooks 里面定義了getPosts
函數(shù),它是一個(gè)異步函數(shù),用于dispatch
GET_POSTS 的異步請(qǐng)求,并且我們進(jìn)行了判斷,當(dāng)此時(shí) Redux Store 內(nèi)部沒有文章時(shí),才進(jìn)行數(shù)據(jù)的獲取。 - 接著,我們改進(jìn)了
getStorage
獲取緩存的函數(shù),將其移動(dòng)到useEffects
Hooks 里面,并額外增加了_id
屬性,它被賦值給userId
一起設(shè)置 Redux Store 中關(guān)于用戶的屬性,這樣做的目的主要是為了之后發(fā)帖標(biāo)志用戶,或者獲取用戶的個(gè)人信息用。并且,加了一層if
判斷,只有當(dāng)沒有登錄時(shí),即isLogged
為 false 的時(shí)候,才進(jìn)行獲取緩存操作。 - 最后我們導(dǎo)入了必要的
GET_POSTS
常量,并且將return
語句里的PostCard
接收的key
和postId
屬性變成了真實(shí)的帖子_id
。這樣我們?cè)谔釉斍闀r(shí)可以直接拿postId
向小程序云發(fā)起異步請(qǐng)求。
注意
在上一篇教程中,有同學(xué)提到?jīng)]有使用
Taro.cloud.init()
初始化的問題,是因?yàn)榉殖闪藘善恼拢谶@篇文章才初始化。要使用小程序云,初始化環(huán)境是必要的。
第二劍:聲明和補(bǔ)充對(duì)應(yīng)需要的異步 sagas 文件
在 “第一劍” 中,我們從組件中 dispatch 了 action.type 為 GET_POSTS
的異步 Action,接下來我們要做的就是在對(duì)應(yīng)的 sagas
文件中補(bǔ)齊響應(yīng)這個(gè)異步 action 的 sagas。
打開 src/sagas/post.js
文件,在其中定義 getPosts
sagas 邏輯如下:
import {
GET_POSTS,
} from '../constants'
function* getPosts() {
try {
const posts = yield call(postApi.getPosts)
// 其實(shí)以下三步可以合成一步,但是這里為了講解清晰,將它們拆分成獨(dú)立的單元
// 發(fā)起獲取帖子成功的 action
yield put({ type: POST_SUCCESS })
// 更新 Redux store 數(shù)據(jù)
yield put({
type: SET_POSTS,
payload: {
posts,
},
})
} catch (err) {
console.log('getPosts ERR: ', err)
// 獲取帖子失敗,發(fā)起失敗的 action
yield put({ type: POST_ERROR })
}
}
function* watchGetPosts() {
while (true) {
yield take(GET_POSTS)
yield fork(getPosts)
}
}
export { watchGetPosts }
可以看到,上面的改動(dòng)主要是創(chuàng)建 watcherSaga
和 handlerSaga
。
創(chuàng)建 watcherSaga
- 我們創(chuàng)建了登錄的
watcherSaga
:watchGetPosts
,它用來監(jiān)聽action.type
為GET_POSTS
的 action,并且當(dāng)監(jiān)聽到GET_POSTS
action 之后,然后激活handlerSaga
:getPosts
去處理對(duì)應(yīng)的獲取帖子列表的邏輯。 - 這里的
watcherSaga
:watchGetPosts
是一個(gè)生成器函數(shù),它內(nèi)部是一個(gè)while
無限循環(huán),表示在內(nèi)部持續(xù)監(jiān)聽GET_POSTS
action。 - 在循環(huán)內(nèi)部,我們使用了
redux-saga
提供的effects helper
函數(shù):take
,它用于監(jiān)聽GET_POSTS
action,獲取 action 中攜帶的數(shù)據(jù)。 - 接著我們使用了另外一個(gè)
effects helper
函數(shù):fork
,它表示非阻塞的執(zhí)行handlerSaga
:getPosts
,因?yàn)檫@里獲取帖子列表不需要傳數(shù)據(jù),所以這里沒有額外的數(shù)據(jù)傳遞邏輯。
創(chuàng)建 handlerSaga
- 我們創(chuàng)建了創(chuàng)建帖子的
handlerSaga
:getPosts
,它用來處理創(chuàng)建邏輯。 -
getPosts
也是一個(gè)生成器函數(shù),在它內(nèi)部是一個(gè)try/catch
語句,用于處理獲取帖子列表請(qǐng)求可能存在的錯(cuò)誤情況。 -
在
try
語句中,首先是使用了redux-saga
提供給我們的effects helper
函數(shù):call
來調(diào)用登錄的 API:postApi. getPosts
。- 如果獲取帖子列表成功,我們使用
redux-saga
提供的effects helpers
函數(shù):put
,put
類似之前在view
中的dispatch
操作,,來dispatch
了兩個(gè) action:POST_SUCCESS
,SET_POSTS
,代表更新獲取帖子列表成功的狀態(tài),設(shè)置最新獲取的帖子列表到 Redux Store 中。
- 如果獲取帖子列表成功,我們使用
- 如果獲取帖子列表失敗,我們則使用
put
發(fā)起一個(gè)POST_ERROR
的 action 來更新獲取帖子列表失敗的信息到 Redux Store
一些額外的工作
為了創(chuàng)建 watcherSaga
和 handlerSaga
,我們還導(dǎo)入了 postApi. getPosts
,我們將在后面來創(chuàng)建這個(gè) API。
除此之外我們還導(dǎo)入了需要使用的 action 常量:
-
GET_POSTS
:響應(yīng)獲取帖子列表的 ACTION 常量,我們將在 “第三劍” 中創(chuàng)建它。
加入 saga 中心調(diào)度文件
我們像之前將 watchCreatePost
等加入到 sagas
中心調(diào)度文件一樣,將我們創(chuàng)建好的 watchGetPosts
也加入進(jìn)去:
// ...之前的邏輯
import { watchGetPosts } from './post'
export default function* rootSaga() {
yield all([
// ... 之前的邏輯
fork(watchGetPosts)
])
}
第三劍:定義 sagas 需要的常量文件
打開 src/constants/post.js
文件,定義我們之前創(chuàng)建的常量文件如下:
export const GET_POSTS = 'GET_POSTS'
第四劍:定義 sagas 涉及到的前端 API 文件
在之前的 post
saga 文件里面,我們使用到了 postApi.getPosts
,它里面封裝了用于向后端(這里我們是小程序云)發(fā)起和獲取帖子列表有關(guān)請(qǐng)求的邏輯,讓我們馬上來實(shí)現(xiàn)它吧。
打開 src/api/post.js
文件,并在其中編寫內(nèi)容如下:
// ... 其余邏輯一樣
async function getPosts() {
const isWeapp = Taro.getEnv() === Taro.ENV_TYPE.WEAPP
const isAlipay = Taro.getEnv() === Taro.ENV_TYPE.ALIPAY
// 針對(duì)微信小程序使用小程序云函數(shù),其他使用小程序 RESTful API
try {
if (isWeapp) {
const { result } = await Taro.cloud.callFunction({
name: 'getPosts',
})
return result.posts
}
} catch (err) {
console.error('getPosts ERR: ', err)
}
}
const postApi = {
// ... 之前的 API
getPosts,
}
// ... 其余邏輯一樣
在上面的代碼中,我們定義了 getPosts
函數(shù),它是一個(gè) async
函數(shù),用來處理異步邏輯,在 getPosts
函數(shù)中,我們對(duì)當(dāng)前的環(huán)境進(jìn)行了判斷,且只在微信小程序,即 isWeapp
的條件下執(zhí)行獲取帖子列表的操作,對(duì)于支付寶小程序和 H5,我們則放在下一節(jié)使用 LeanCloud 的 Serverless 來解決。
創(chuàng)建帖子邏輯是一個(gè) try/catch
語句,用于捕捉可能存在的請(qǐng)求錯(cuò)誤,在 try
代碼塊中,我們使用了 Taro
為我們提供的微信小程序云的云函數(shù) API Taro.cloud.callFunction
來便捷的向小程序云發(fā)起云函數(shù)調(diào)用請(qǐng)求。
這里我們調(diào)用了一個(gè) getPosts
云函數(shù),我們將在下一節(jié)中實(shí)現(xiàn)這個(gè)云函數(shù)。
如果調(diào)用成功,我們可以接收返回值,用于從后端返回?cái)?shù)據(jù),這里我們返回了 result.posts
數(shù)據(jù),即從小程序云返回的帖子列表。
如果調(diào)用失敗,則打印錯(cuò)誤。
最后我們?cè)谝呀?jīng)定義好的 postApi
對(duì)象里,添加 getPosts
API 屬性然后將其導(dǎo)出,這樣在 post
saga 函數(shù)里面就可以導(dǎo)入 postApi
然后通過 postApi. getPosts
的方式來調(diào)用 getPosts
API 處理獲取帖子列表的邏輯了。
第五劍:創(chuàng)建對(duì)應(yīng)的微信小程序云函數(shù)
創(chuàng)建 getPosts 云函數(shù)
按照和之前創(chuàng)建 createPost
云函數(shù)類似,我們創(chuàng)建 getPosts
云函數(shù)。
創(chuàng)建成功之后,我們可以得到兩個(gè)文件,一個(gè)是 functions/getPosts/package.json
文件,它和之前的類似。
{
"name": "getPosts",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"wx-server-sdk": "latest"
}
}
第二個(gè)文件就是我們需要編寫創(chuàng)建帖子邏輯的 functions/getPosts/index.js
文件,微信小程序開發(fā)者工具會(huì)默認(rèn)為我們生成一段樣板代碼。
我們?cè)?function/getPosts
文件夾下同樣運(yùn)行 npm install
安裝對(duì)應(yīng)的云函數(shù)依賴,這樣我們才能運(yùn)行它。
編寫 getPosts 云函數(shù)
打開 functions/getPosts/index.js
文件,對(duì)其中的內(nèi)容作出對(duì)應(yīng)的修改如下:
// 云函數(shù)入口文件
const cloud = require('wx-server-sdk')
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV,
})
const db = cloud.database()
const _ = db.command
// 云函數(shù)入口函數(shù)
exports.main = async (event, context) => {
try {
const { data } = await db.collection('post').get()
return {
posts: data,
}
} catch (e) {
console.error(`getPosts ERR: ${e}`)
}
}
可以看到上面的代碼改動(dòng)主要有以下處:
- 首先我們給
cloud.init()
傳入了環(huán)境參數(shù),我們使用了內(nèi)置的cloud.DYNAMIC_CURRENT_ENV
,表示自動(dòng)設(shè)置為當(dāng)前的云環(huán)境,即在右鍵點(diǎn)擊小程序開發(fā)者工具里functions
文件夾時(shí)選擇的環(huán)境。 - 接著,我們通過
cloud.database()
生成了數(shù)據(jù)實(shí)例db
,用于之后在函數(shù)體中便捷的操作云數(shù)據(jù)庫(kù)。 - 接著就是
main
函數(shù)體,里面是一個(gè)try/catch
語句塊,用于捕獲錯(cuò)誤,在try
語句塊中,我們使用db
的查詢操作:db.collection('post').get()
,表示查詢所有的post
數(shù)據(jù)。 - 最后我們返回查詢到的
posts
數(shù)據(jù)。
第六劍: 定義對(duì)應(yīng)的 reducers 文件
因?yàn)檫@里 SET_POSTS
的 Action 我們?cè)谏弦?“大” 節(jié)中創(chuàng)建帖子時(shí)已經(jīng)定義了,所有在 “這一劍” 中我們無需添加額外的代碼,復(fù)用之前的邏輯就好。
“六脈神劍” 搞定 getPost 異步邏輯
在上面兩 “大” 節(jié)中,我們連續(xù)用了兩次 “六脈神劍”,相信跟到這里的同學(xué)應(yīng)該對(duì)我們接下來要做的事情已經(jīng)輕車熟路了吧。
接下來,我們將收尾 Post 邏輯的最后一公里,即帖子詳情的異步邏輯 “getPost” 接入,話不多說就是干!
第一劍:post 組件中發(fā)起異步請(qǐng)求
打開 src/pages/post/post.jsx
文件,對(duì)其中的內(nèi)容作出對(duì)應(yīng)的修改如下:
import Taro, { useRouter, useEffect } from '@tarojs/taro'
import { View } from '@tarojs/components'
import { useDispatch, useSelector } from '@tarojs/redux'
import { PostCard } from '../../components'
import './post.scss'
import { GET_POST, SET_POST } from '../../constants'
export default function Post() {
const router = useRouter()
const { postId } = router.params
const dispatch = useDispatch()
const post = useSelector(state => state.post.post)
useEffect(() => {
dispatch({
type: GET_POST,
payload: {
postId,
},
})
return () => {
dispatch({ type: SET_POST, payload: { post: {} } })
}
}, [])
return (
<View className="post">
可以看到,上面的內(nèi)容做了如下四處修改:
- 首先我們使用
useDispatch
Hooks 獲取到了dispatch
函數(shù)。 - 接著,在
useEffects
Hooks 里面定義了 dispatch 了 action.type 為 GET_POST 的 action,它是一個(gè)異步 Action,并且我們?cè)?Hooks 最后返回了一個(gè)函數(shù),其中的內(nèi)容為將post
設(shè)置為空對(duì)象,這里用到的SET_POST
常量我們將在后面定義它。這個(gè)返回函數(shù)主要用于post
組件卸載之后,Redux Store 數(shù)據(jù)的重置,避免下次打開帖子詳情還會(huì)渲染之前獲取到的帖子數(shù)據(jù)。 - 接著,我們使用
useSelector
Hooks 來獲取異步請(qǐng)求到的post
數(shù)據(jù),并用于return
語句中的數(shù)據(jù)渲染。 - 最后我們刪除了不必要的獲取
posts
數(shù)據(jù)的useSelector
Hooks,以及刪掉了不必要的調(diào)試console.log
語句。
第二劍: 聲明和補(bǔ)充對(duì)應(yīng)需要的異步 sagas 文件
在 “第一劍” 中,我們從組件中 dispatch 了 action.type 為 GET_POST
的異步 Action,接下來我們要做的就是在對(duì)應(yīng)的 sagas
文件中補(bǔ)齊響應(yīng)這個(gè)異步 action 的 sagas。
打開 src/sagas/post.js
文件,在其中定義 getPosts
sagas 邏輯如下:
// ... 和之前的邏輯一樣
import {
// ... 和之前的邏輯一樣
SET_POST,
} from '../constants';
// ... 和之前的邏輯一樣
function* getPost(postId) {
try {
const post = yield call(postApi.getPost, postId)
// 其實(shí)以下三步可以合成一步,但是這里為了講解清晰,將它們拆分成獨(dú)立的單元
// 發(fā)起獲取帖子成功的 action
yield put({ type: POST_SUCCESS })
// 更新 Redux store 數(shù)據(jù)
yield put({
type: SET_POST,
payload: {
post,
},
})
} catch (err) {
console.log('getPost ERR: ', err)
// 獲取帖子失敗,發(fā)起失敗的 action
yield put({ type: POST_ERROR })
}
}
function* watchGetPost() {
while (true) {
const { payload } = yield take(GET_POST)
yield fork(getPost, payload.postId)
}
}
export { watchGetPost }
可以看到,上面的改動(dòng)主要是創(chuàng)建 watcherSaga
和 handlerSaga
。
創(chuàng)建 watcherSaga
- 我們創(chuàng)建了登錄的
watcherSaga
:watchGetPost
,它用來監(jiān)聽action.type
為GET_POST
的 action,并且當(dāng)監(jiān)聽到GET_POST
action 之后,然后激活handlerSaga
:getPost
去處理對(duì)應(yīng)的獲取單個(gè)帖子的邏輯。 - 這里的
watcherSaga
:watchGetPost
是一個(gè)生成器函數(shù),它內(nèi)部是一個(gè)while
無限循環(huán),表示在內(nèi)部持續(xù)監(jiān)聽GET_POST
action。 - 在循環(huán)內(nèi)部,我們使用了
redux-saga
提供的effects helper
函數(shù):take
,它用于監(jiān)聽GET_POST
action,獲取 action 中攜帶的數(shù)據(jù),這里我們拿到了傳過來的payload
數(shù)據(jù)。 - 接著我們使用了另外一個(gè)
effects helper
函數(shù):fork
,它表示非阻塞的執(zhí)行handlerSaga
:getPost
,并傳入了獲取到payload.postId
參數(shù)。
創(chuàng)建 handlerSaga
- 我們創(chuàng)建了獲取單個(gè)帖子的
handlerSaga
:getPost
,它用來處理獲取帖子邏輯。 -
getPost
也是一個(gè)生成器函數(shù),在它內(nèi)部是一個(gè)try/catch
語句,用于處理獲取單個(gè)帖子請(qǐng)求可能存在的錯(cuò)誤情況。 -
在
try
語句中,首先是使用了redux-saga
提供給我們的effects helper
函數(shù):call
來調(diào)用登錄的 API:postApi. getPost
。- 如果獲取單個(gè)帖子成功,我們使用
redux-saga
提供的effects helpers
函數(shù):put
,put
類似之前在view
中的dispatch
操作,,來dispatch
了兩個(gè) action:POST_SUCCESS
,SET_POSTS
,代表更新獲取單個(gè)帖子成功的狀態(tài),設(shè)置最新獲取的帖子到 Redux Store 中。
- 如果獲取單個(gè)帖子成功,我們使用
- 如果獲取單個(gè)帖子失敗,我們則使用
put
發(fā)起一個(gè)POST_ERROR
的 action 來更新獲取單個(gè)帖子失敗的信息到 Redux Store
一些額外的工作
為了創(chuàng)建 watcherSaga
和 handlerSaga
,我們還導(dǎo)入了 postApi.getPost
,我們將在后面來創(chuàng)建這個(gè) API。
除此之外我們還導(dǎo)入了需要使用的 action 常量:
-
SET_POST
:響應(yīng)獲取帖子列表的 ACTION 常量,我們將在 “第三劍” 中創(chuàng)建它
加入 saga 中心調(diào)度文件
我們像之前將 watchGetPosts
等加入到 sagas
中心調(diào)度文件一樣,將我們創(chuàng)建好的 watchGetPost
也加入進(jìn)去:
打開 src/sagas/index.js
文件,對(duì)其中的內(nèi)容作出如下的修改:
import { fork, all } from 'redux-saga/effects'
import { watchLogin } from './user'
import { watchCreatePost, watchGetPosts, watchGetPost } from './post'
export default function* rootSaga() {
yield all([
fork(watchLogin),
fork(watchCreatePost),
fork(watchGetPosts),
fork(watchGetPost),
])
}
第三劍:定義 sagas 需要的常量文件
打開 src/constants/post.js
文件,定義我們之前創(chuàng)建的常量文件 GET_POST
:
export const SET_POST = 'SET_POST'
第四劍:定義 sagas 涉及到的前端 API 文件
在之前的 post
saga 文件里面,我們使用到了 postApi.getPost
,它里面封裝了用于向后端(這里我們是小程序云)發(fā)起和獲取單個(gè)帖子有關(guān)請(qǐng)求的邏輯,讓我們馬上來實(shí)現(xiàn)它吧。
打開 src/api/post.js
文件,并在其中編寫內(nèi)容如下:
// ... 其他內(nèi)容和之前一致
async function getPost(postId) {
const isWeapp = Taro.getEnv() === Taro.ENV_TYPE.WEAPP
const isAlipay = Taro.getEnv() === Taro.ENV_TYPE.ALIPAY
// 針對(duì)微信小程序使用小程序云函數(shù),其他使用小程序 RESTful API
try {
if (isWeapp) {
const { result } = await Taro.cloud.callFunction({
name: 'getPost',
data: {
postId,
},
})
return result.post
}
} catch (err) {
console.error('getPost ERR: ', err)
}
}
const postApi = {
getPost,
}
export default postApi
可以看到上面的代碼有如下六處改動(dòng):
- 在上面的代碼中,我們定義了
getPost
函數(shù),它是一個(gè)async
函數(shù),用來處理異步邏輯,在getPost
函數(shù)中,我們對(duì)當(dāng)前的環(huán)境進(jìn)行了判斷,且只在微信小程序,即isWeapp
的條件下執(zhí)行獲取單個(gè)帖子的操作,對(duì)于支付寶小程序和 H5,我們則放在下一節(jié)使用 LeanCloud 的 Serverless 來解決。 - 創(chuàng)建帖子邏輯是一個(gè)
try/catch
語句,用于捕捉可能存在的請(qǐng)求錯(cuò)誤,在try
代碼塊中,我們使用了Taro
為我們提供的微信小程序云的云函數(shù) APITaro.cloud.callFunction
來便捷的向小程序云發(fā)起云函數(shù)調(diào)用請(qǐng)求。 - 這里我們調(diào)用了一個(gè)
getPost
云函數(shù),并給它傳遞了對(duì)應(yīng)要獲取的帖子的postId
我們將在下一節(jié)中實(shí)現(xiàn)這個(gè)云函數(shù)。 - 如果調(diào)用成功,我們可以接收返回值,用于從后端返回?cái)?shù)據(jù),這里我們返回了
result.post
數(shù)據(jù),即從小程序云返回的單個(gè)帖子。 - 如果調(diào)用失敗,則打印錯(cuò)誤。
- 最后我們?cè)谝呀?jīng)定義好的
postApi
對(duì)象里,添加getPost
API 屬性然后將其導(dǎo)出,這樣在post
saga 函數(shù)里面就可以導(dǎo)入postApi
然后通過postApi. getPost
的方式來調(diào)用getPost
API 處理獲取單個(gè)帖子的邏輯了。
第五劍:創(chuàng)建對(duì)應(yīng)的微信小程序云函數(shù)
創(chuàng)建 getPost 云函數(shù)
按照和之前創(chuàng)建 getPosts
云函數(shù)類似,我們創(chuàng)建 getPost
云函數(shù)。
創(chuàng)建成功之后,我們可以得到兩個(gè)文件,一個(gè)是 functions/getPost/package.json
文件,它和之前的類似。
{
"name": "getPost",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"wx-server-sdk": "latest"
}
}
第二個(gè)文件就是我們需要編寫創(chuàng)建帖子邏輯的 functions/getPost/index.js
文件,微信小程序開發(fā)者工具會(huì)默認(rèn)為我們生成一段樣板代碼。
我們?cè)?function/getPost
文件夾下同樣運(yùn)行 npm install
安裝對(duì)應(yīng)的云函數(shù)依賴,這樣我們才能運(yùn)行它。
編寫 getPost 云函數(shù)
打開 functions/getPost/index.js
文件,對(duì)其中的內(nèi)容作出對(duì)應(yīng)的修改如下:
// 云函數(shù)入口文件
const cloud = require('wx-server-sdk')
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV,
})
const db = cloud.database()
// 云函數(shù)入口函數(shù)
exports.main = async (event, context) => {
const { postId } = event
try {
const { data } = await db
.collection('post')
.doc(postId)
.get()
return {
post: data,
}
} catch (e) {
console.error(`getPost ERR: ${e}`)
}
}
可以看到上面的代碼改動(dòng)主要有以下處:
- 首先我們給
cloud.init()
傳入了環(huán)境參數(shù),我們使用了內(nèi)置的cloud.DYNAMIC_CURRENT_ENV
,表示自動(dòng)設(shè)置為當(dāng)前的云環(huán)境,即在右鍵點(diǎn)擊小程序開發(fā)者工具里functions
文件夾時(shí)選擇的環(huán)境。 - 接著,我們通過
cloud.database()
生成了數(shù)據(jù)實(shí)例db
,用于之后在函數(shù)體中便捷的操作云數(shù)據(jù)庫(kù)。 - 接著就是
main
函數(shù)體,里面是一個(gè)try/catch
語句塊,用于捕獲錯(cuò)誤,在try
語句塊中,我們首先從event
對(duì)象里面獲取到了postId
,接著我們使用db
的查詢操作:db.collection('post').doc(postId).get()
,表示查詢所有的對(duì)應(yīng)_id
為postId
的單個(gè)帖子數(shù)據(jù) - 最后我們返回查詢到的
post
數(shù)據(jù)。
第六劍: 定義對(duì)應(yīng)的 reducers 文件
因?yàn)檫@里 SET_POST
的 Action 我們?cè)谏仙?“大” 節(jié)中創(chuàng)建帖子時(shí)已經(jīng)定義了,所有在 “這一劍” 中我們無需添加額外的代碼,復(fù)用之前的邏輯就好。
小結(jié)
在這篇教程中,我們連續(xù)使用了三次 “六脈神劍” 講完了我們的 Post 邏輯的異步流程,讓我們?cè)賮韽?fù)習(xí)一下我們開頭提到的 “六脈神劍”:
- 將組件中的同步邏輯重構(gòu)到異步邏輯
- 聲明和補(bǔ)充對(duì)應(yīng)需要的異步
sagas
文件 - 定義
sagas
需要的常量文件 - 定義
sagas
涉及到的前端 API 文件 - 創(chuàng)建對(duì)于的微信小程序云函數(shù),并編寫對(duì)應(yīng)的 Node.js 處理邏輯
- 定義對(duì)應(yīng)的
reducers
文件
這是一套講解模式,也是一套寫代碼的最佳實(shí)踐方式之一,希望你能受用。
一點(diǎn)遺憾
這兩篇講解微信小程序云的文章有一個(gè)小小的遺憾,我們也在之前的文章中提到過了,就是微信小程序云僅限于微信小程序內(nèi)的使用,如果我們想做多端應(yīng)用,比如支付寶小程序云,H5 網(wǎng)站,那么單單使用微信小程序就顯得無能為力了,我們將在下一篇文章中引入 LeanCloud Serverless 服務(wù),并使用它來補(bǔ)齊我們跨端小程序開發(fā)的短板,敬請(qǐng)期待!?
想要學(xué)習(xí)更多精彩的實(shí)戰(zhàn)技術(shù)教程?來圖雀社區(qū)逛逛吧。
本文所涉及的源代碼都放在了 Github 上,如果您覺得我們寫得還不錯(cuò),希望您能給?這篇文章點(diǎn)贊+Github倉(cāng)庫(kù)加星?哦
作者:一只圖雀文章來源:http://www.zghlxwxcb.cn/news/detail-838284.html
原文地址:https://segmentfault.com/a/1190000022296526文章來源地址http://www.zghlxwxcb.cn/news/detail-838284.html
到了這里,關(guān)于Taro 小程序開發(fā)大型實(shí)戰(zhàn)(七):嘗鮮微信小程序云(下篇)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!