这是我参与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
。