微信原生小程式中引入redux,支援async, await
微信原生小程式官方提供了全域性變數globalData,進行頁面元件之間的通訊,這在小型應用中足以,然而在複雜的應用中,管理全域性狀態變得無力。
redux在react專案中使用廣泛,我們希望引入到微信小程式中。
實現思路
要實現引入redux到微信小程式中,我們需要完成三個函式
- setStore(store) 儲存redux store,方便小程式呼叫
- connect(mapStateToPage, mapDispatchToPage)(Page) 用於連結state到微信小程式頁面元件中
- connect(mapStateToComponent, mapDispatchToComponent)(Component) 用於連結到微信自定義元件中
實現setStore方法
setStore的實現相當的簡單,我們只需要儲存傳遞過來的store物件即可,詳細定義如下:
let _store function setStore(store) { // 這裡僅僅是驗證傳遞的store是否符合redux store規則 assert( isReduxStore(store), 'the store you provider not a standrand redux store', ) _store = store }
實現connect方法
要實現conenct方法,我們需要利用微信小程式頁面元件的生命週期函式onLoad以及onUnLoad,思路如下:
- 在微信小程式頁面onLoad時,通過store.getState()獲取全域性state狀態
- 傳遞狀態值到使用者註冊的mapStateToPage函式中,塞選出需要連結到頁面的狀態值
- 呼叫微信小程式this.setData合併上一步獲取的狀態值到頁面data屬性中
- 利用store.subscribe監聽store中狀態改變,當資料改變時利用mapStateToPage獲取需要連結的資料,淺對比上一次值,如果改變則重新合併到data中,未改變則放棄合併
- 在微信小程式onShow時,判斷onLoad是否執行完成,且,監聽器不存在,重新建立監聽器
- 呼叫mapDispatchToPage函式,傳遞store.dispatch,合併到頁面元件自定中,即可通過this呼叫
- 當微信小程式頁面onHide時,取消store的監聽器
- 當微信小程式頁面onUnload時,取消store的監聽事件
首先我們看一下整體函式定義
function connect(mapStateToPage, mapDispatchToPage) { const shouldMapStateToPage = isFunction(mapStateToPage) const shouldMapDispatchToPage = isFunction(mapDispatchToPage) return function(pageConfig) { // defaultMapToPage是一個空函式 const mapState = shouldMapStateToPage ? mapStateToPage : defaultMapToPage const mapDispatch = shouldMapDispatchToPage ? mapDispatchToPage : defaultMapToPage let unsubscribe = null let ready = false function subscribe() {} function onLoad() {} function onShow() {} function onUnload() {} function onHide() {} // 這裡返回一個重新組裝後的pageCofing物件,用於註冊到Page() return { ...pageConfig, ...mapDispatch(_store.dispatch), onLoad, onUnLoad } } }
subscribe函式定義
function subscribe(options) { if (!isFunction(unsubscribe)) return null // 獲取該頁面需要連結的state const mappedState = mapState(_store.getState(), options) // 進行淺比較,一致則不改變 if (isShallowInclude(this.data, mappedState)) return null // 呼叫微信方法更新頁面data this.setData(mappedState) }
onLoad函式定義
function onLoad(options) { assert(_store !== null, 'we should call setStore before the connect') if (shouldMapStateToPage) { // 建立store的狀態改變監聽事件 unsubscribe = _store.subscribe(subscribe.bind(this, options)) // 初始化繫結頁面的資料 subscribe.call(this, options) } // 檢測使用者是否定義了onLoad函式 // 如果使用者定義了該函式,觸發該函式 if (isFunction(pageConfig.onLoad)) { config.onLoad.call(this, options) } ready = true }
onShow函式定義
function onShow() { // 這裡之所以需要檢測ready及unsubscribe // 主要是防止重複監聽改變,重複繫結 if (ready && !isFunction(unsubscribe) && shouldMapStateToPage) { unsubscribe = _store.subscribe(subscribe.bind(this)) subscribe.call(this) } if (isFunction(config.onShow)) { config.onShow.call(this) } }
onHide函式定義
function onHide() { if (isFunction(config.onHide)) { config.onHide.call(this) } if (isFunction(unsubscribe)) { unsubscribe() unsubscribe = null } }
onUnload函式定義
function onUnload() { // 檢測使用者是否定義了onUnload函式 // 如果定義,觸發該函式 if (isFunction(pageConfig.onUnload)) { pageConfig.onUnload.call() } // 取消store的變化監聽 if (isFunction(unsubscribe)) { unsubscribe() unsubscribe = null } }
這樣一個完整的connect方法定義已經完成,檢視完整程式碼請移步 ofollow,noindex">weapp-redux
實現connectComponent
有了connect的實現,要實現connenctComponent,原理就是一致的,利用微信小程式自定義元件生命週期函式,attached, detached, pageLifetimes.show, pageLifetimes.hide實現
注意:在低版本微信中不支援pageLifetimes,因此會造成頁面隱藏時,監聽器還在執行的浪費
完整程式碼這裡不再列出了,請移步 createConnectComponent
引入redux到微信小程式中
上面我們僅僅是實現了連結redux到page,component中,還記得setStore函式麼?它的引數需要傳遞一個redux store,我們可以選擇使用redux原生建立store,移步 redux 官方文件 ,也可以利用一些基於redux封裝的redux框架庫,簡化redux使用
這裡我們選擇使用第三方redux框架 zoro ,快速搭建我們的redux應用
初始化我們的專案
第一步,通過微信開發者工具,建立一個快速啟動模版
第二步,我們拷貝 weapp-redux.js , zoro.js ,到專案目錄utils中
第三步,引入zoro和redux到app中,開啟app.js
import zoro from './utils/zoro' import { setStore } from './utils/weapp-redux' const app = zoro() const store = app.start(false) setStore(store) App({ onLaunch() { app.setup() ... } })
至此專案的初始化完成了,開啟除錯工具預覽(需開啟es6轉換)
一個簡單的hello world專案,在debug工具中,我麼看到如下報錯
Store does not have a valid reducer. Make sure the argument passed to combineReducers is an object whose values are reducers.
這是因為我們並沒有建立任何的reducer,我們檢視 API.md" target="_blank" rel="nofollow,noindex">zoro使用文件 ,修改微信hello world模版,通過redux實現
在專案根目錄下建立models資料夾
建立models/user.js
export default { namespace: 'user', state: {}, }
引入model
import zoro from './utils/zoro' import { setStore } from './utils/weapp-redux' // 新增引入user model import user from './models/user' const app = zoro() // 新增引入user model app.model(user) const store = app.start(false) setStore(store) App({ onLaunch() { app.setup() ... } })
接下來我們分析微信空白模版功能,首先改造登入
// models/user.js檔案中 // 由於該檔案中需要使用到async, await,因此需要引入regeneratorRuntime import { regeneratorRuntime } from '../utils/zoro' import { promise } from '../utils/util' // Promise化微信登入介面 const wxLogin = promise(wx.login) export default { namespace: 'user', state: {}, effects: { async login() { // 阻塞呼叫微信登入 const { code } = await wxLogin() // 傳送code到後臺伺服器中獲取openId, sessionKey, unionId } } }
// app.js檔案中 // 新增匯出dispatcher,用於觸發redux action import zoro, { dispatcher } from './utils/zoro' App({ onLaunch() { app.setup() // 觸發微信登入 dispatcher.user.login() /* 刪除原有程式碼邏輯 wx.login({ success: res => { // 傳送 res.code 到後臺換取 openId, sessionKey, unionId } }) */ // 省略其他程式碼 }, })
接下來我們改造微信獲取使用者資訊
// models/user.js檔案中 import { regeneratorRuntime } from '../utils/zoro' import { promise } from '../utils/util' // 首先對於需要使用的介面進行promise化 const wxGetSetting = promise(wx.getSetting) const wxGetUserInfo = promise(wx.getUserInfo) export default { namespace: 'user', state: { userInfo: {}, // 給使用者資訊一個預設值 canGetUserInfo: false, // 標記使用者是否已經授權 }, effects: { async getUserInfo() { const { authSetting } = await wxGetSetting() if (authSetting['scope.userInfo']) { const { userInfo } = await wxGetUserInfo() // 獲取到了使用者資訊,我們需要儲存在redux的state中 // 請看下面reducers.update的定義 put({ type: 'update', payload: { userInfo, canGetUserInfo: true } }) } else { put({ type: 'update', payload: { canGetUserInfo: false } }) } }, }, reducers: { update({ payload }, state) { return { ...state, ...payload } }, }, }
// app.js檔案中 import zoro, { dispatcher } from './utils/zoro' App({ onLaunch() { app.setup() // 觸發微信登入 dispatcher.user.login() // 觸發獲取使用者資訊 dispatcher.user.getUserInfo() /* 刪除原有程式碼邏輯 wx.getSetting({ success: res => { if (res.authSetting['scope.userInfo']) { // 已經授權,可以直接呼叫 getUserInfo 獲取頭像暱稱,不會彈框 wx.getUserInfo({ success: res => { // 可以將 res 傳送給後臺解碼出 unionId this.globalData.userInfo = res.userInfo // 由於 getUserInfo 是網路請求,可能會在 Page.onLoad 之後才返回 // 所以此處加入 callback 以防止這種情況 if (this.userInfoReadyCallback) { this.userInfoReadyCallback(res) } } }) } } }) */ // 省略其他程式碼 }, /* 刪除原有邏輯 globalData: { userInfo: null } */ })
成功拿到了使用者資訊,我們還需要改造index頁面,以保證使用者資料可以連結到頁面中
// pages/index/index.js import { dispatcher } from '../../utils/zoro' import { connect } from '../../utils/weapp-redux' // 連結state到頁面,返回值用於註冊到微信Page中 const config = connect(state => ({ userInfo: state.user.userInfo, hasUserInfo: state.user.hasUserInfo, }))({ data: { motto: 'Hello World', canIUse: wx.canIUse('button.open-type.getUserInfo'), }, bindViewTap: function() { wx.navigateTo({ url: '../logs/logs' }) }, getUserInfo: function(e) { // 更新使用者資料 dispatcher.user.update({ userInfo: e.detail.userInfo, hasUserInfo: true }) } }) Page(config)
整個頁面是不是變得非常簡潔,conenctComponent使用與conenct一致這裡不在討論
如果你需要在頁面或者元件中使用async, await,請在檔案頭部引入如下程式碼
// 路徑請根據實際情況而定 import { regeneratorRuntime } from '../utils/zoro'
該專案程式碼託管於github, weapp-combine-redux
最後,混口飯吃,支付寶掃碼領個紅包吧

支付寶掃碼領紅包.PNG