我搞了一个 Elm 风格的 useEffectReducer hook

GitHub: bolasblack/react-components/tree/develop/packages/useEffectReducer

什么是 "Elm 风格"?

Elm 有一个 Model-View-Update 模式,其中每次更新都会返回下一个 model 和要执行的命令:

update : Msg -> Model -> ( Model, Cmd Msg )

这种设计将所有关于 "事件 X 发生时会发生什么" 的逻辑集中在 一个地方 —— update 函数 —— 而不是将副作用分散在各种 useEffect 中。

它还保持了 reducer 的纯洁性,同时让 副作用何时发生、发生什么 变得明确,并可被运行时解释执行。

React 官方文档也呼应了这一理念:effect 应该是一个 逃生舱,而不是默认选择 —— 参见 You Might Not Need an Effect

如果你想了解这种建模方式如何让 UI 逻辑变得优雅且易于维护,可以看看 David Khourshid 的经典文章 "No, disabling a button is not app logic."

为什么 造一个 Elm 风格的 reducer?

因为没有完全满足我的需求的版本:

  • 有些已经归档了。 例如,useEffectReducerreact-use-bireducer 现在都是只读状态。

  • 保持精简。 只需一个文件,几乎没有依赖 —— 随时可以复制、修改或删除。

  • Effect 作为普通对象 + 独立的解释器。 返回可序列化的 effect descriptors,然后在一个专门的地方实现实际的 effect 逻辑。这样更容易测试 reducer(只需断言描述符),而无需真正执行副作用。

  • 降低门槛。 目标是让 Elm 风格的方法变得易于上手,而不需要深入了解 Elm 的 Cmd Msg 系统,也不需要学习像 XState 这样的完整状态机库。

来点代码

我们用 useEffectReducer 重新实现一下 David Khourshid 文章 "No, disabling a button is not app logic." 中的示例。

两种实现都可以在 GitHub 上找到:

其他人的工作

Elm 风格的 "状态 + 副作用" reducer 引入 React 的优秀现有实现:

好文章