用户
 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,登录网站

小程序社区 首页 教程 实战教程 查看内容

原生微信小程序开发中 redux 的使用

Rolan 2021-2-19 10:01

本着能用原生就绝不使用第三方二次封装的库的原则。一直想尝试一下在原生微信小程序开发中接入redux。

前提

复杂场景中有不少数据需要在多个不同页面间来回使用和修改。但是小程序页面直接的数据通信方式十分的简单。通常情况需要自己维护一个全局的对象来存放共有数据。但是,简单的维护一个共有数据实体,会随着业务逻辑的不断复杂化而变的过分庞大,并且数据的修改往往无法很好的溯源。加之公共数据实体中数据的修改和页面的UI之间没有太好的同步手段,往往需要在页面和对应的数据实体中同时都维护一份相同的数据,操作十分的不方便。

之前使用过Taro以react+redux的结构来开发微信小程序,依托redux整体上可以解决上述的问题。但是Taro本身也有着一些让人无法接受的潜在问题。本着能用原生就绝不使用第三方二次封装的库的原则。一直想尝试一下在原生微信小程序开发中接入redux。

需要解决的问题

1、redux库的接入
2、页面UI与redux数据的绑定
复制代码
redux库的引入

1、redux的安装,使用 npm与yarn 都可以。

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.jsglobalData中维护,这样每个页面都可以直接获取到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);
鲜花
鲜花
鸡蛋
鸡蛋
分享至 : QQ空间
收藏