这是我参与8月更文挑战的第14天,活动详情查看:8月更文挑战
Rematch 的介绍
Rematch是没有模板的Redux最佳实践,可以理解为Redux的一种替代品.Rematch依赖于Redux,解决了一些Redux的痛点而存在.- 在使用层面来说,与
Dva相比,Rematch将Redux-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。
需要实现将 state、dispatch 传入页面,并且页面的 dispatch 操作能够触发小程序页面的更新。
带来的收益和问题
- 收益方面
首先可以使用 Redux 社区已有的积累,因为 Rematch 也支持 Redux 的中间件等其他功能。
另外 mapState、mapDispatch 这种类似 Redux 的开发体验、以及 多Model、混写 state、reducer、effects 的方式也会极大地便利页面的开发,同时由于整体与 Redux 模板相似,对于其他人的理解和维护成本相对较低。
- 问题方面
主要是 Rematch 的上手成本,只有先熟悉 Rematch 在 React 中的使用,才能更好的过渡到 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,
})
}
这里以此接收 pageConfig、mapState、mapDispatch 三个参数,最后返回了 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 监控到 rematch 中 store 的变更,然后当触发变更后再执行 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 触发 reducer 或 effects 都会触发 store.subscribe,但是可能多次触发的事件中,store 数据并未发生变化。
所以首先对最新的 store state 和原 data 进行 diff,当不同时才需要触发 data 更新。
另外关于如何动态添加来自 rematch 的变量,是通过遍历待添加的 rematch data,并逐个执行this.setData({}) 实现。
最后,最外层通过 debounce 保证在一段时间内不会触发太多次。
总结
至此,就可以实现在小程序平台上运行 rematch。