(但是,只在封闭系统生效!)
早些时候,我对 hooks 下使用 useReducer 或者一众状态管理库是非常方案的,带上在结合项目仔细思考之下,发现了这一方案的优点,或许结合使用才是最优解
对 useReducer 的产生误解的原因也很简单:
- 类型支持非常差(js/ts 没有很好的逆变类型支持,没有模式识别,对比 reasonml 下的 useReducer 可知)
- 无模式识别(同上)
- 无法在 useReducer 内部,利用第三方 hooks 生态(即封装状态逻辑视图的全功能第三方库)
很简单一个例子,const {data,error} = useSwr(key,fetcher)
,这段代码将请求过程,全部转化为数据驱动的 data,error 状态,同时响应式支持了请求,延迟,屏幕聚焦,轮训,防抖等等功能
开发过程中使用这样的 第三方 api 能够得到事半功倍的效果
但是,如果你在这样的逻辑上,使用了 useReducer ,那么,这个叫做 useSwr 的 api,你无法再使用了,因为 reducer 内部代码(脱离了 react 调度)
这么说来,useReducer 在 hooks 环境下,就毫无作用可言了么?
前文有提过,至少 react 所举的例子,并不能说明 useReducer 的意义:
仅仅是因为 useCallback 有变化?就需要用 useReducer dispatch?
const [todos,dispatch] = useReducer(todosReducer)
return <TodosState value={todos}>
<DeepTreeWithState/>
<TodosDispatch.Provider value={dispatch}>
<DeepTreeWithDispatch />
</TodosDispatch.Provider>
</TodosState>
甚至标准做法还应该做的这么丑陋?
拜托,这个实在误人子弟了,控制 view 永远靠 useMemo,调度逻辑也是业务逻辑的一部分,哪能就这么回避掉的?
直接返回未拆分的 jsx 本身的含义就是 —— 视图跟随所有 state,props,context 变化,逻辑即如此,反而 dispatch 虽不说不伦不类,也有点反直觉
useReducer 该用么?现在告诉大家,某些情况下的确该用,但是不是 react 文档提到的这种情况(至少绝不会像他说的那么简单)
什么情况下需要用到 useReducer ?
封闭系统下的 控熵
可以近似理解为,同一时刻,物体可能状态的数量,其中既包含状态的 数量,也包含状态的 可能性
还记得前文总结的么?React 虽然在 hooks 这一次更新,做了很大的函数式改进,但是数据驱动原则始终未变
毕竟直接由具象转化为抽象编程,跨度太大,前端又多是处在视图开发层次,状态或许比事件更加重要?
然而函数式是不应该有太多状态,无共享状态甚至是无状态的,因为函数本身是对变化的抽象,用状态模拟概念编程,补足函数式顶层设计短板的做法,其实是有很大牺牲的(比如缺乏概念编程的自解释性,类型无协变,即类型无法自顶向下等)
但是,现实是,前端必须两者兼得,必须尊重状态,因为前端开发本身就是中介,本身就是视图和一致性数据的中间件,状态绕不过去
同样,即便是事件驱动(zone)著称的 angular,也没有像 cyclejs 那样无状态流一撸到底,cyclejs 的不温不火也能反过来证明这一点
那么, 状态,以及背后代表的意义,就耐人寻味了
因为
状态就是熵
下面这段表述我相信对 react 程序员来说,都是入门经典:
没错!曼妥思糖和可乐!
曼妥思碰到了可乐,双方产生剧烈反应,一方的不确定性和状态数量会直接灌入另一方中,这就是个剧烈的熵增反应
左侧是你的应用中的状态,右侧是你收到的数据请求的状态,一旦两者想触碰,会发生什么?
一边,是确定调度,确定结构的 react 应用,另一边,是不确定数据,不确定调度的异步
将两者看做一个系统,怎么解决这个问题?
没错!麦克斯韦妖!
假设出现一个有选择能力的主体 —— 麦克斯韦妖,它选择性地将结果放入另一个结构(react 应用/store)中,就可以解决这个问题
一边是确定性(低熵)的 state,通过这个 麦克斯韦妖(dispatch)选择 (reducer),很好地保证了 store 的低熵环境
const [state,dispatch] = useReducer((state,action)=>{
if(action.name === 'xxx'){
// 麦克斯韦妖/reducer 正在进行选择
}
// 麦克斯韦妖/reducer 正在放入另一侧的 state
return {...state}
},{})
因此,前文提到的某个情景 —— 即应对非合成事件 的时候,callback 调用烧脑的问题(区分内外,区分调度源),useReducer 是个非常好的解决方案
这个时候,dispatch 的不变性,才有了很好的使用场景:
const ws = useRef()
const [state,dispatch] = useReducer((state,action)=>{
if(action.payload === 'init'){
ws.current = new WebSocker('...')
}
if(action.payload === 'read'){
ws.current.send('read')
}
// ...
},{data:'',error:null})
// 这样,应用本身的结构,可以很容易(effect,合成事件)与另一部分(非合成事件)进行交流
useEffect(()=>{
if(ready){
dispatch('init')
}
},[ready])
return <button onClick={()=>dispatch('init')}>init</button>
这样的话,不确定性会比纯粹使用 useCallback 低得多(因为依赖很难得到处理,至少很烧脑)
整个应用的熵得到了非常好的控制
useReducer,系统对接的神器
是的,当我们想要将一个封闭系统,与另一个系统对接,形成更大的封闭系统时,useReducer 就非常有用了,因为它控制住了不确定性
将 dispatch 想象成麦克斯韦妖,你就能很轻松实现多个系统之间的隔离,并且有可预知的结果
那么,除了异步以外,还有哪些地方可以用到呢?
跨模块通讯!
这部分以后可以继续分享,因为涉及一个笔者仍没有彻底弄懂的架构技术
那全用 useReducer 可以么?
本来是可以的,除非你不想要哪些全封装的第三方hooks api,以及不想要更简单明了的流
但是效率和质量是可以做权衡的,这部分看你怎么看,不过一旦你全使用 useReducer 进行开发,就是默认了,有 useReducer 处,就是个单独的模块
也就是说,要么全用 useReducer,要么只用 useReducer 做模块通讯(其他调度系统的通讯也可以看做模块通讯)
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!