微信小程序授權(quán)登錄方案以及在Taro下利用Decorator修飾器實現(xiàn)
選用Taro做技術(shù)框架的原因:最近公司需要開發(fā)一款新的小程序,主要是做付費知識相關(guān)的產(chǎn)品,涉及到了虛擬商品支付,對于IOS的對于虛擬商品支付的種種限制,加上類似小程序的相關(guān)調(diào)研,決定IOS支付的方式走h5公總號支付繞開限制,所以在框架選型上面需要一套代碼加一點兼容代碼,就可以生成小程序和H5版本的庫,考慮到本身技術(shù)棧以react為主,所以最后老大選擇了Taro進行開發(fā)
對于Taro的簡單介紹以及提供能力可以瀏覽 Taro初探
需求場景在微信小程序里面,需要做助力、拼團等邏輯的時候,有些需要鑒權(quán)的接口等,要再用戶授權(quán)登錄完畢之后,在請求的 header 帶上用戶的 accessToken ,所以要確保這些接口在用戶登錄完成之后再開始進行請求
之所以要用戶授權(quán)登錄而不用小程序的靜態(tài)登錄方式,是因為在兼容H5的時候,登陸流程是通過公眾號登錄的,在不想產(chǎn)生多余的數(shù)據(jù)下,使用用戶的 union_id 作為唯一依據(jù),用 wx.login這種形式拿用戶的 code 登錄只能拿到 open_id ,與我們的需求不符合
UnionID機制說明 · 小程序
我們這邊與后端約定是先通過用戶授權(quán) wx.getUserInfo ,拿到用戶信息發(fā)送給后端進行注冊或者登陸,后端返回一個 accessToken 作為用戶的憑證,調(diào)用其他接口的時候在 header 帶著這個 accessToken ,后端就能在需要的時候根據(jù) accessToken 獲取到當(dāng)前用戶信息
小程序的登錄流程如下由于小程序的生命周期機制,生命周期是異步執(zhí)行的,生命周期之間是無法阻塞執(zhí)行,如果在 onLaunch 的時候進行用戶登錄的邏輯,在弱網(wǎng)的情況下,會出現(xiàn)一種情況就是用戶登錄沒完成的情況下,還沒拿到 accessToken 就開始了page里面的請求接口,這樣會導(dǎo)致接口報錯
解決思路利用修飾器 Decorator 、React的高階組件 HOC 以及 async/await ,劫持當(dāng)前頁面調(diào)用接口的聲明周期,等待封裝好的用戶登錄邏輯執(zhí)行完以后,再進行當(dāng)前聲明周期里面其他調(diào)用的執(zhí)行。
舉個例子在分享助力的場景下,新用戶點擊分享用戶的卡片進來小程序,需要彈出一個授權(quán)彈框等用戶授權(quán)登陸成功以后,才能進行助力接口的調(diào)用。
要注意的是,劫持的是當(dāng)前聲明周期的方法,并不會阻塞到其他生命周期,例如劫持 willMount 的時候, didShow 、 didMount 等周期依然會照樣按順序執(zhí)行,并不會等待 willMount 結(jié)束后再進行
代碼分享主要分享修飾器的使用以及作用,登陸邏輯主要參考流程圖即可,代碼暫不做分享
寫一個能劫持傳入組件生命周期的修飾器由于Taro暫時不支持無狀態(tài)組件,所以只能使用HOC的反向劫持能力,繼承傳入的組件,這個時候就可以通過等待登錄邏輯完成,再執(zhí)行劫持的生命周期
withLogin.jsconst LIFE_CYCLE_MAP = ['willMount', 'didMount', 'didShow']; /** * * 登錄鑒權(quán) * * @param {string} [lifecycle] 需要等待的鑒權(quán)完再執(zhí)行的生命周期 willMount didMount didShow * @returns 包裝后的Component * */ function withLogin(lifecycle = 'willMount') { // 異常規(guī)避提醒 if (LIFE_CYCLE_MAP.indexOf(lifecycle) < 0) { console.warn( `傳入的生命周期不存在, 鑒權(quán)判斷異常 ===========> $_{lifecycle}` ); return Component => Component; } return function withLoginComponent(Component) { // 避免H5兼容異常 if (tool.isH5()) { return Component; } // 這里還可以通過redux來獲取本地用戶信息,在用戶一次登錄之后,其他需要鑒權(quán)的頁面可以用判斷跳過流程 // @connect(({ user }) => ({ // userInfo: user.userInfo, // })) class WithLogin extends Component { constructor(props) { super(props); } async componentWillMount() { if (super.componentWillMount) { if (lifecycle === LIFE_CYCLE_MAP[0]) { const res = await this.$_autoLogin(); if (!res) return; } super.componentWillMount(); } } async componentDidMount() { if (super.componentDidMount) { if (lifecycle === LIFE_CYCLE_MAP[1]) { const res = await this.$_autoLogin(); if (!res) return; } super.componentDidMount(); } } async componentDidShow() { if (super.componentDidShow) { if (lifecycle === LIFE_CYCLE_MAP[2]) { const res = await this.$_autoLogin(); if (!res) return; } super.componentDidShow(); } } } $_autoLogin = () => { // ...這里是登錄邏輯 } } } export default withLogin; 復(fù)制代碼
使用的組件內(nèi)必須有對應(yīng)定義的生命周期,而且 不能使用箭頭函數(shù)式 ,例如 componentWillMount(){} 不能寫成 componentWillMount = () => {} ,會劫持失敗
import Taro, { Component } from '@tarojs/taro'; import { View } from '@tarojs/components'; import withLogin from './withLogin' @withLogin() class Index extends Component { componentWillMount(){ console.log('Index willMount') // 需要帶accessToken調(diào)用的接口等 } componentDidMount(){ console.log('Index didMount') } render() { console.log('Index render'); return <View />; } } export default Index; 復(fù)制代碼
- 如果在繼承的時候使用了redux去connect了數(shù)據(jù),使用之后已自動為組件的props附帶上connect的數(shù)據(jù),被修飾的組件不需要再connect去拿這一個數(shù)據(jù), 不然可能會出現(xiàn)報錯 Setting data field "xxx" to undefined is invalid .
利用修飾器這個特性,我們還可以對小程序做一層瀏覽打點,分享封裝等操作
第二部分:如何開通一個小商店