微信小程式授權登陸方案以及在Taro下利用Decorator修飾器實現

MasonChow發表於2019-03-04

選用Taro做技術框架的原因:最近公司需要開發一款新的小程式,主要是做付費知識相關的產品,涉及到了虛擬商品支付,對於IOS的對於虛擬商品支付的種種限制,加上類似小程式的相關調研,決定IOS支付的方式走h5公總號支付繞開限制,所以在框架選型上面需要一套程式碼加一點相容程式碼,就可以生成小程式和H5版本的庫,考慮到本身技術棧以react為主,所以最後老大選擇了Taro進行開發

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

需求場景

在微信小程式裡面,需要做助力、拼團等邏輯的時候,有些需要鑑權的介面等,要再使用者授權登入完畢之後,在請求的header帶上使用者的accessToken,所以要確保這些介面在使用者登入完成之後再開始進行請求

之所以要使用者授權登入而不用小程式的靜態登入方式,是因為在相容H5的時候,登陸流程是通過公眾號登入的,在不想產生多餘的資料下,使用使用者的union_id作為唯一依據,用wx.login這種形式拿使用者的code登入只能拿到open_id,與我們的需求不符合

UnionID機制說明 · 小程式

我們這邊與後端約定是先通過使用者授權wx.getUserInfo,拿到使用者資訊傳送給後端進行註冊或者登陸,後端返回一個accessToken作為使用者的憑證,呼叫其他介面的時候在header帶著這個accessToken,後端就能在需要的時候根據accessToken獲取到當前使用者資訊

小程式的登入流程如下

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

由於小程式的生命週期機制,生命週期是非同步執行的,生命週期之間是無法阻塞執行,如果在onLaunch的時候進行使用者登入的邏輯,在弱網的情況下,會出現一種情況就是使用者登入沒完成的情況下,還沒拿到accessToken就開始了page裡面的請求介面,這樣會導致介面報錯

解決思路

利用修飾器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()劫持失敗

相關文章