微信小程序受權登錄方案以及在Taro下利用Decorator修飾器實現

選用Taro作技術框架的緣由:最近公司須要開發一款新的小程序,主要是作付費知識相關的產品,涉及到了虛擬商品支付,對於IOS的對於虛擬商品支付的種種限制,加上相似小程序的相關調研,決定IOS支付的方式走h5公總號支付繞開限制,因此在框架選型上面須要一套代碼加一點兼容代碼,就能夠生成小程序和H5版本的庫,考慮到自己技術棧以react爲主,因此最後老大選擇了Taro進行開發javascript

對於Taro的簡單介紹以及提供能力能夠瀏覽 Taro初探 html

需求場景

在微信小程序裏面,須要作助力、拼團等邏輯的時候,有些須要鑑權的接口等,要再用戶受權登陸完畢以後,在請求的header帶上用戶的accessToken,因此要確保這些接口在用戶登陸完成以後再開始進行請求java

之因此要用戶受權登陸而不用小程序的靜態登陸方式,是由於在兼容H5的時候,登錄流程是經過公衆號登陸的,在不想產生多餘的數據下,使用用戶的union_id做爲惟一依據,用wx.login這種形式拿用戶的code登陸只能拿到open_id,與咱們的需求不符合react

UnionID機制說明 · 小程序git

咱們這邊與後端約定是先經過用戶受權wx.getUserInfo,拿到用戶信息發送給後端進行註冊或者登錄,後端返回一個accessToken做爲用戶的憑證,調用其餘接口的時候在header帶着這個accessToken,後端就能在須要的時候根據accessToken獲取到當前用戶信息github

小程序的登陸流程以下

1272272b-3f6c-416b-9e8a-24ae3b9c2f37.jpg

因爲小程序的生命週期機制,生命週期是異步執行的,生命週期之間是沒法阻塞執行,若是在onLaunch的時候進行用戶登陸的邏輯,在弱網的狀況下,會出現一種狀況就是用戶登陸沒完成的狀況下,還沒拿到accessToken就開始了page裏面的請求接口,這樣會致使接口報錯redux

解決思路

利用修飾器Decorator、React的高階組件HOC以及async/await,劫持當前頁面調用接口的聲明週期,等待封裝好的用戶登陸邏輯執行完之後,再進行當前聲明週期裏面其餘調用的執行。小程序

舉個例子

在分享助力的場景下,新用戶點擊分享用戶的卡片進來小程序,須要彈出一個受權彈框等用戶受權登錄成功之後,才能進行助力接口的調用。後端

要注意的是,劫持的是當前聲明週期的方法,並不會阻塞到其餘生命週期,例如劫持willMount的時候,didShowdidMount等週期依然會照樣按順序執行,並不會等待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; 複製代碼
注意
  1. 若是在繼承的時候使用了redux去connect了數據,使用以後已自動爲組件的props附帶上connect的數據,被修飾的組件不須要再connect去拿這一個數據, 否則可能會出現報錯 Setting data field "xxx" to undefined is invalid.

利用修飾器這個特性,咱們還能夠對小程序作一層瀏覽打點,分享封裝等操做

暫未解決的問題

因爲小程序編譯的緣由,小程序上面不能劫持render, 因此在受權登陸的時候想彈出自定義彈窗引導用戶受權的話,須要經過redux來控制是否顯示彈框以及在頁面組件引入自定義彈窗的組件

問題參考 反向繼承組件的時候 render()劫持失敗

相關文章
相關標籤/搜索