小程序也能跑 Rematch?| 8月更文挑战

444 阅读3分钟

这是我参与8月更文挑战的第14天,活动详情查看:8月更文挑战

Rematch 的介绍

  • Rematch 是没有模板的 Redux 最佳实践,可以理解为 Redux 的一种替代品.
  • Rematch 依赖于 Redux,解决了一些 Redux 的痛点而存在.
  • 在使用层面来说,与 Dva 相比,RematchRedux-saga 替换成了 promise/async await.

Rematch 在 Web 平台的使用

首先,对于 Rematch 本身来说,它与浏览器、Node、小程序等平台无关,只要可以运行 JS,即可使用 Rematch。 另外,Rematch 需要和 Redux 配套使用,而 Redux 也与平台无关。 最后,在与 React 配套使用时,需要使用 react-redux,而这个库是与 Web 平台绑定的。

Rematch in MiniApp

想要在小程序平台运行,首先要弃用的是 react-redux。我们需要一套类似的 react-redux 中的 connect。 需要实现将 statedispatch 传入页面,并且页面的 dispatch 操作能够触发小程序页面的更新。

带来的收益和问题

  • 收益方面

首先可以使用 Redux 社区已有的积累,因为 Rematch 也支持 Redux 的中间件等其他功能。 另外 mapStatemapDispatch 这种类似 Redux 的开发体验、以及 多Model混写 state、reducer、effects 的方式也会极大地便利页面的开发,同时由于整体与 Redux 模板相似,对于其他人的理解和维护成本相对较低。

  • 问题方面

主要是 Rematch 的上手成本,只有先熟悉 RematchReact 中的使用,才能更好的过渡到 Rematch in MiniApp

期望的 Rematch in MiniApp 使用方式

// page/index/index.js

import connect from 'path/to/rematch-in-miniapp/connect'
const mapState = (state) => ({})
const mapDispatch = (dispatch) => ({})
const pageConfig = {
  anyMethod() {
    this.dispatch.dialog.showCommonPopup()
  }
}
Page(
  connect(
    pageConfig
  )(mapState, mapDispatch)
)

// pages/index/index.wxml

<view>{{/* data in rematch */}}</view>

核心代码

module.exports = (pageConfig) => (mapState, mapDispatch) => {
  const store = createStore()
  const fromMapState = () => mapState(store.getState())
  const fromMapDispatch = mapDispatch(store.dispatch)

  return enhancedPageConfig(pageConfig, {
    ...store,
    fromMapState,
    fromMapDispatch,
  })
}

这里以此接收 pageConfigmapStatemapDispatch 三个参数,最后返回了 enhancedPageConfig

function enhancedPageConfig(pageConfig, store) {
  const {
    fromMapState,
    fromMapDispatch,
  } = store
  const originalOnLoad = pageConfig.onLoad || noop
  const injectDispatchApi = (context) => context.dispatch = fromMapDispatch
  const addSyncStoreStateListener = (context) => store.subscribe(() => {
    syncStoreState(context, fromMapState())
  })

  return {
    ...pageConfig,
    onLoad: function (...args) {
      injectDispatchApi(this)
      syncStoreState(this, fromMapState())
      addSyncStoreStateListener(this)

      return originalOnLoad.apply(this, args)
    }
  }
}

enhancedPageConfig 的实现中,

首先劫持 onLoad 方法,并将 dispatch 注入到 this 中。

最后添加 syncStoreStateListener,这里主要是通过 store.subscribe 监控到 rematchstore 的变更,然后当触发变更后再执行 syncStoreState

对于 syncStoreState 的实现:

const debounce = require('./debounce')

const syncStoreState = debounce((context, storeState) => {
  const readyToSet = {}
  const {
    data: originalData,
    setData
  } = context
  for (let key in storeState) {
    const shouldUpdate = Boolean(originalData[key] !== storeState[key])
    if (!shouldUpdate) continue

    readyToSet[key] = storeState[key]
  }
  const shouldSetData = Boolean(Object.keys(readyToSet).length)
  if (!shouldSetData) return

  console.log(`[触发 setData]`, readyToSet)
  setData.call(context, readyToSet)
}, 300)

module.exports = syncStoreState

由于每次 store 触发 reducereffects 都会触发 store.subscribe,但是可能多次触发的事件中,store 数据并未发生变化。

所以首先对最新的 store state 和原 data 进行 diff,当不同时才需要触发 data 更新。

另外关于如何动态添加来自 rematch 的变量,是通过遍历待添加的 rematch data,并逐个执行this.setData({}) 实现。

最后,最外层通过 debounce 保证在一段时间内不会触发太多次。

总结

至此,就可以实现在小程序平台上运行 rematch