選用Taro做技術框架的原因:最近公司需要開發一款新的小程式,主要是做付費知識相關的產品,涉及到了虛擬商品支付,對於IOS的對於虛擬商品支付的種種限制,加上類似小程式的相關調研,決定IOS支付的方式走h5公總號支付繞開限制,所以在框架選型上面需要一套程式碼加一點相容程式碼,就可以生成小程式和H5版本的庫,考慮到本身技術棧以react為主,所以最後老大選擇了Taro進行開發
對於Taro的簡單介紹以及提供能力可以瀏覽 Taro初探
需求場景
在微信小程式裡面,需要做助力、拼團等邏輯的時候,有些需要鑑權的介面等,要再使用者授權登入完畢之後,在請求的header
帶上使用者的accessToken
,所以要確保這些介面在使用者登入完成之後再開始進行請求
之所以要使用者授權登入而不用小程式的靜態登入方式,是因為在相容H5的時候,登陸流程是通過公眾號登入的,在不想產生多餘的資料下,使用使用者的union_id
作為唯一依據,用wx.login
這種形式拿使用者的code
登入只能拿到open_id
,與我們的需求不符合
我們這邊與後端約定是先通過使用者授權wx.getUserInfo
,拿到使用者資訊傳送給後端進行註冊或者登陸,後端返回一個accessToken
作為使用者的憑證,呼叫其他介面的時候在header
帶著這個accessToken
,後端就能在需要的時候根據accessToken
獲取到當前使用者資訊
小程式的登入流程如下
由於小程式的生命週期機制,生命週期是非同步執行的,生命週期之間是無法阻塞執行,如果在
onLaunch
的時候進行使用者登入的邏輯,在弱網的情況下,會出現一種情況就是使用者登入沒完成的情況下,還沒拿到accessToken
就開始了page裡面的請求介面,這樣會導致介面報錯
解決思路
利用修飾器Decorator
、React的高階元件HOC
以及async/await
,劫持當前頁面呼叫介面的宣告週期,等待封裝好的使用者登入邏輯執行完以後,再進行當前宣告週期裡面其他呼叫的執行。
舉個例子
在分享助力的場景下,新使用者點選分享使用者的卡片進來小程式,需要彈出一個授權彈框等使用者授權登陸成功以後,才能進行助力介面的呼叫。
要注意的是,劫持的是當前宣告週期的方法,並不會阻塞到其他生命週期,例如劫持
willMount
的時候,didShow
、didMount
等週期依然會照樣按順序執行,並不會等待willMount
結束後再進行
程式碼分享
主要分享修飾器的使用以及作用,登陸邏輯主要參考流程圖即可,程式碼暫不做分享
寫一個能劫持傳入元件生命週期的修飾器
由於Taro暫時不支援無狀態元件,所以只能使用HOC的反向劫持能力,繼承傳入的元件,這個時候就可以通過等待登入邏輯完成,再執行劫持的生命週期
withLogin.js
const LIFE_CYCLE_MAP = ['willMount', 'didMount', 'didShow'];
/**
*
* 登入鑑權
*
* @param {string} [lifecycle] 需要等待的鑑權完再執行的生命週期 willMount didMount didShow
* @returns 包裝後的Component
*
*/
function withLogin(lifecycle = 'willMount') {
// 異常規避提醒
if (LIFE_CYCLE_MAP.indexOf(lifecycle) < 0) {
console.warn(
`傳入的生命週期不存在, 鑑權判斷異常 ===========> $_{lifecycle}`
);
return Component => Component;
}
return function withLoginComponent(Component) {
// 避免H5相容異常
if (tool.isH5()) {
return Component;
}
// 這裡還可以通過redux來獲取本地使用者資訊,在使用者一次登入之後,其他需要鑑權的頁面可以用判斷跳過流程
// @connect(({ user }) => ({
// userInfo: user.userInfo,
// }))
return 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;
複製程式碼
注意
使用的元件內必須有對應定義的生命週期,而且
不能使用箭頭函式式
,例如componentWillMount(){}
不能寫成componentWillMount = () => {}
,會劫持失敗
需要登入鑑權頁面的使用方式
pages/xxx/xxx.js
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呼叫的介面等
}
componentDidMount(){
console.log('Index didMount')
}
render() {
console.log('Index render');
return <View />;
}
}
export default Index;
複製程式碼
注意
- 如果在繼承的時候使用了redux去connect了資料,使用之後已自動為元件的props附帶上connect的資料,被修飾的元件不需要再connect去拿這一個資料, 不然可能會出現報錯
Setting data field "xxx" to undefined is invalid
.
利用修飾器這個特性,我們還可以對小程式做一層瀏覽打點,分享封裝等操作
暫未解決的問題
由於小程式編譯的原因,小程式上面不能劫持render
, 所以在授權登入的時候想彈出自定義彈窗
引導使用者授權的話,需要通過redux
來控制是否顯示彈框以及在頁面元件引入自定義彈窗
的元件