前提
復雜場景中有不少數據需要在多個不同頁面間來回使用和修改。但是小程序頁面直接的數據通信方式十分的簡單。通常情況需要自己維護一個全局的對象來存放共有數據。但是,簡單的維護一個共有數據實體,會隨著業務邏輯的不斷復雜化而變的過分龐大,并且數據的修改往往無法很好的溯源。加之公共數據實體中數據的修改和頁面的UI之間沒有太好的同步手段,往往需要在頁面和對應的數據實體中同時都維護一份相同的數據,操作十分的不方便。
之前使用過Taro以react+redux的結構來開發微信小程序,依托redux整體上可以解決上述的問題。但是Taro本身也有著一些讓人無法接受的潛在問題。本著能用原生就絕不使用第三方二次封裝的庫的原則。一直想嘗試一下在原生微信小程序開發中接入redux。
需要解決的問題
1、redux庫的接入
2、頁面UI與redux數據的綁定
redux庫的引入
1、redux的安裝,使用 npm與yarn 都可以。
具體到redux中文官網如下:https://www.reduxjs.cn/introduction/getting-started/
2、微信小程序引入外部npm包。
使用微信小程序IDEA,tools 中的 Build npm,生成miniprogram_npm。
3、redux庫ReferenceError: process is not defined報錯的解決。
因為微信小程序Build npm工具,構建時不會引入nodeprocess環境變量,但是redux對不同env做了對應的優化。所以導致構建出來的包缺失process變量。最便捷的解決方法是在構建完成的包中自己注入需要的process。
這樣基本可以解決所有第三方庫遇到的process參數缺失的問題。如果每次運行Build npm工具后都需要手動修改。如果有多個第三方庫需要手動修改,那就很麻煩。所以很有必要通過腳本,使用ast樹等工具完整動態修改,節省人力成本(這個后續介紹)
綜上,redux的引入就完成了。
在項目中添加redux
1、store的創建
使用combineReducers合并不同的實體,使用createStore創建store實體,并導出。為了數據的統一性,redux的原則是一個項目只初始化一個store,所以后續任何的操作都是在當前生成的store中進行。
合并數據實體:
const { combineReducers } = require("redux"); const testItem = require("./testItem/index"); const testItem2 = require("./testItem2/index"); const user = require("./user/index"); const reducer = combineReducers({ testItem: testItem.testItem, testItem2, user }); module.exports = { reducer }
導出store:
const { createStore, applyMiddleware } = require("redux"); const { reducer } = require("./reducers"); const { logger } = require("redux-logger"); const store = createStore( reducer, applyMiddleware(logger) ) module.exports = { store }
2、全局維護store
這里和react中的使用方法不同。微信小程序沒有對應的控件來全局維護store,所以我的做法是直接在,app.js的globalData中維護,這樣每個頁面都可以直接獲取到store
app.js:
const { store } = require("./redux/index"); //app.js App({ globalData: { $store: store, getState: ()=> store.getState(), } })
模擬connect方法
在react中,connect方法是通過高階組件的方式實現的,但是這個方法并不適用微信小程序。好在redux有提供subscribe方法來監聽store中數據的變化。所以初步設計:
1、每當頁面計入或顯示的時候,添加監聽,頁面隱藏或銷毀時銷毀監聽
2、添加完監聽后,模擬 mapState 方法,把對應 redux 中的數據注入到頁面的data中
3、當監聽到redux中數據變化時,更新頁面data,從而實現頁面UI刷新
4、模擬mapDispatch方法,為頁面提供修改store數據的方法
pageW.js:
const { store } = require("../redux/index"); const initPage = (params = {}, connect = []) => { const { onLoad = ()=>{}, onShow = ()=>{}, onHide = ()=>{}, onUnload = ()=>{}, data = {} } = params; const newPage = { ...params, // ---------------- OnLoad(...p) { onLoad.bind(this)(...p); }, OnShow(...p) { onShow.bind(this)(...p); }, OnHide(...p) { onHide.bind(this)(...p); }, OnUnload(...p) { onUnload.bind(this)(...p); }, // ---------------- // 清空監聽 clearStoreSubscribe() { if (this.storeSubscribe) { this.storeSubscribe(); this.storeSubscribe = undefined; } }, // 獲取redux 中 data getNewData() { const newItems = {}; const state = this.$store.getState(); if (connect) { if ( Array.isArray(connect) ) { connect.forEach((key) => { const value = state[key]; if (value && this.data[key] !== value) { newItems[key] = value } }) } else if (typeof connect === "function") { const list = connect(state) || {}; Object.keys(list).forEach((key) => { const value = list[key]; if (value && this.data[key] !== value) { newItems[key] = value } }) } } return newItems; }, // 監聽 redux 變化 handleReduxChange() { this.setData({ ...this.getNewData(), }); }, // ---------------- data: { ...data }, onLoad(...p) { const app = getApp() this.$store = app.globalData.$store; this.setData({ ...this.getNewData(), }); this.OnLoad(...p); this._isOnLoad = true; }, onShow (...p) { if (!this.storeSubscribe) { this.storeSubscribe = this.$store.subscribe(()=>this.handleReduxChange()); } if (!this._isOnLoad) { this.setData({ ...this.getNewData(), }); } this.OnShow(...p); this._isOnLoad = false; }, onHide(...p) { this.OnHide(...p); this.clearStoreSubscribe(); }, onUnload(...p) { this.OnUnload(...p); this.clearStoreSubscribe(); }, // ---------------- dispatch(...p) { if (this.$store) { return this.$store.dispatch(...p); } } } return newPage; } const PageW = (params = {}, mapState = [], mapDispatch = ()=>{}) => { const page = initPage({...params}, mapState); const dispatchList = mapDispatch(store) || {}; page.mapDispatch = { ...dispatchList }; return Page(page); } module.exports = PageW;
PageW 中主要考慮和不足 如下問題:
1、為了保持微信小程序原有生命周名稱不變,所以事先劫持了傳入頁面的生命周期,然后用bind重新在對應生命周期完成后觸發。
2、因為redux更新數據,都會生成一個新的數據對象,所以每當監聽到數據變化,新數據和老數據會進行對比,每次setData,只放入確實發生變化的數據
3、頁面中的data,既維護了默認頁面創建的data數據,又加入了redux connect 后的數據,但是目前沒有對這個兩個數據的命名進行安全的區分,所以頁面原生data中的數據名稱必須與 connect 注入的數據不同。
測試頁面:
導入了testItem, testItem2兩個數據,導入了add2一個方法
const PageW = require("../../pageW/index"); const { ActionsFun } = require("../../redux/testItem/actions"); const page = { data: { wwj: 4 }, onLoad() { console.log("sub onLoad"); }, onShow() { }, toTest() { console.log("toTest"); wx.navigateTo({ url: "/pages/test/index" }) }, button1() { console.log("button1"); this.mapDispatch.add2(); }, button2() { const { wwj } = this.data; this.setData({ wwj: wwj + 2 }); }, } const mapState = [ "testItem", "testItem2" ]; const mapDispatch = ({dispatch}) => { return { add2: (params) => dispatch(ActionsFun.add(params)) } } PageW(page, mapState, mapDispatch);
到此這篇關于原生微信小程序開發中 redux 的使用詳解的文章就介紹到這了,更多相關小程序 redux使用內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://juejin.cn/post/6929862191806054407