React 和 Redux 一直是前端开发中最流行和最强大的框架之一。React 用于构建 User Interface,而 Redux 则用于管理应用程序的状态。Redux 提供了一个简单且可预测的数据流方案,使得在复杂应用中管理状态变得方便。然而,Redux 的 reducer 处理方式需要程序员熟练使用语言特性和设计模式。为了解决这个问题,出现了 redux-functional-reducer 这个包。
redux-functional-reducer 是什么?
简单来说,redux-functional-reducer 是一个可视化工具,它可以帮助我们更好地管理应用程序的状态。它使用函数式编程风格处理状态管理,不再是传统的 switch 和 if/else 控制流编写,更符合现代 JS 语言特性和风格。
redux-functional-reducer 提供了四个高阶 reducer,分别是 composeReducers
, mapReducers
, whenRejected
和 whenUnhandled
,我们会在下面的示例中详细了解它们。
安装
安装 redux-functional-reducer 很简单,使用 npm 安装:
npm install redux-functional-reducer
使用
为了更好地说明如何使用 redux-functional-reducer,我们将创建一个具有如下功能的简单应用:
- 一个 todo 列表
- 可以添加、删除和完成 todo
我们将从 Redux 基础模板开始,并使用 redux-functional-reducer 优化我们的 reducer。
基础模板
我们从最基础的 Redux 模板开始,它包含了一个简单的 todo 的 actions 和 reducer:
-- -------------------- ---- ------- ----- -------- - ----------- ----- ----------- - -------------- ----- ------------ - - ------ -- -- -------- ------------------ - ------------------- ------- - ------ ------------- - ---- --------- ------ - --------- - --- ------------ - -- ----- ------------ ---------- ----- - -- ---- ------------ ------ -------------- -- ------- --- --------- - - -------- ---------- --------------- - - ---- -- -------- ------ ------ - - ------ ------- -------------
这个模板很简单,只有两个 actions 和一个 reducer,但是在复杂的应用程序中,reducer 通常是一大块 switch 和 if/else 语句。因此,我们使用 redux-functional-reducer 来优化这个 reducer。
使用 redux-functional-reducer
接下来,我们将使用 redux-functional-reducer 来编写我们的 reducer。
首先,由于我们的应用程序中有多个 reducers,我们需要使用 combineReducers 将它们组合起来,生成一个根 reducer。然后,我们使用 composeReducers
将 todosReducer
编写成函数式 reducer。
-- -------------------- ---- ------- ------ - --------------- - ---- -------- ------ - --------------- - ---- --------------------------- ----- -------- - ----------- ----- ----------- - -------------- ----- ------------ - - ------ -- -- -------- ------------------ - ------------------- ------- - ------ ------------- - ---- --------- ------ - --------- - --- ------------ - -- ----- ------------ ---------- ----- - -- ---- ------------ ------ -------------- -- ------- --- --------- - - -------- ---------- --------------- - - ---- -- -------- ------ ------ - - ----- ----------- - ----------------- ------ ---------------- ------------- -------------------- -- ----------------------- ------- ----------------- - ---
我们组合了 todosReducer 和 whenUnhandled。whenUnhandled
用于捕捉 todo actions 以外的 actions,防止我们的 reducer 抛出错误。
mapReducers
控制多个 reducer
现在我们想增加一个新的 reducer,用于控制 todo 的 visibilityFilter(过滤器)。
const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'; const visibilityFilterReducer = (state = 'SHOW_ALL', action) => { if (action.type === SET_VISIBILITY_FILTER) { return action.filter; } return state; };
在 rootReducer 中,我们需要为两个 reducer 分别提供 action。
const rootReducer = combineReducers({ visibilityFilter: visibilityFilterReducer, todos: composeReducers( todosReducer, whenUnhandled(action => console.warn(`Unhandled Action: ${action.type}`)) ) });
很显然,我们需要手动的将 action 传递给每个 reducer。此时,我们需要 mapReducers
。
-- -------------------- ---- ------- ------ - --------------- - ---- -------- ------ - ---------------- ------------ ------------- - ---- --------------------------- ----- -------- - ----------- ----- ----------- - -------------- ----- --------------------- - ------------------------ ----- ------------ - - ----------------- ----------- ------ -- -- -------- ------------------ - ------------------- ------- - ------ ------------- - ---- --------- ------ - --------- - --- ------------ - -- ----- ------------ ---------- ----- - -- ---- ------------ ------ -------------- -- ------- --- --------- - - -------- ---------- --------------- - - ---- -- -------- ------ ------ - - ----- ----------------------- - ------ - ------------------------------ ------- -- - -- ------------ --- ---------------------- - ------ -------------- - ------ ------ -- ----- ----------- - ---------------- ------------- ----------------- ------------------------ ------ ------------ --- -------------------- -- ----------------------- ------ ----- ----------------- -- ------ ------- ------------
现在,我们使用了 mapReducers
和 composeReducers
,可以将 reducers 组合在一起,并处理所有的 action。此时,我们的应用程序状态如下:
{ visibilityFilter: 'SHOW_ALL' todos: [] }
同时,我们还通过 whenUnhandled
避免了未知 action 的错误。
whenRejected
控制异步错误
在复杂的应用程序中,API 调用和异步操作是必需的。因此,我们需要处理异步错误和在 UI 中通知错误。
当我们使用 redux-thunk 或 redux-saga 等 middleware 时,抛出 error 对 reducer 不安全,redux 异步错误处理通常使用 actions 和 reducers 的结合。因此,redux-functional-reducer 提供了 whenRejected
,它是一个拦截器,负责处理 action 的 error 信息。
我们使用 React 的服务端渲染(SSR)流程来展示 whenRejected
。通常,我们需要在 UI 中通知用户错误信息。
下面是一个模拟异步 API 接口,模拟返回一篇不存在的文章:
const FAKE_ARTICLE_API = id => new Promise((resolve, reject) => { setTimeout(() => { return reject({ id, message: 'Article not found' }); }, 500); });
我们的 onError 函数可以根据返回的 error,展示错误 UI。
-- -------------------- ---- ------- ------ ----- ---- -------- ------ - -------- - ---- -------------- ------ - ---------------- ----------- - ---- -------- ------ ----- ---- -------------- ------ ----------- ---- ------------- ------ - ---------------- ------------ -------------- ------------ - ---- --------------------------- ----- -------- - ----------- ----- ----------- - -------------- ----- --------------------- - ------------------------ ----- ------------ - - ----------------- ----------- ------ -- -- -------- ------------------ - ------------------- ------- - ------ ------------- - ---- --------- ------ - --------- - --- ------------ - -- ----- ------------ ---------- ----- - -- ---- ------------ ------ -------------- -- ------- --- --------- - - -------- ---------- --------------- - - ---- -- -------- ------ ------ - - ----- ----------------------- - ------ - ------------------------------ ------- -- - -- ------------ --- ---------------------- - ------ -------------- - ------ ------ -- ----- ----------- - ---------------- ------------- ----------------- ------------------------ ------ ------------ --- ------------------ --------- --------- -- - ------------------ ------- --------- ----- ----- ----- - ---- ------- ------- --------------- ----- ------------------ ------ - ----- ------------ -------- ----- -- --- -------------------- -- ----------------------- ------ ----- ----------------- -- ------ ------- ------------
我们将 whenRejected
与 mapReducers
和 composeReducers
中的其他 reducers 结合使用,以便能够处理 actions 和捕捉未处理的 actions。
您可能已经注意到,此时我们返回一个 action { type: 'API_ERROR', payload: '...' }
,稍后可以在我们的组件中处理它。
-- -------------------- ---- ------- -------- ----- --------- -------- -- - ----- ------- --------- - ------------------- ----- ------------ - --- -- - ------------------- ------------------------- ------------- -- ----- ------------------ - -- -- - ------ ------------------------- -- ------ - ---- ---------------- -------------- ----- ------------------------ ------- --- ----- ------ ------------- ------------ ----------- -- ------------------------- -- -------- ------- ----------------------------- ------- ------- ---------------------------------- ---------------- --------- -- --- -------- ------ ----- ------------------- ------ -- -
如果我们在组件中调用不存文章的 id,则出现这个编码错误。
-- -------------------- ---- ------- ------ - ---------------- - ---- -------- ------ - ------------ - ---- ------------------------ ----- ------------ - --------------- ------ ----- ----------- - ---- -- ----- ---------- --------- -- - --- - ----- -------- - ----- ----------------------------------- ---------- ----- ------------- -------- -------- --- - ----- ------- - ----- - -------- ---------- - - ------ ------------------------ ---------------- - -------- ---------- --- ----- ------ - --
发生错误后,我们在 UI 中看到了错误信息:
总结
在本文中,我们介绍了 redux-functional-reducer,并展示了如何使用它来帮助我们更好地管理 Redux 应用程序的状态。redux-functional-reducer 的 composeReducers
, mapReducers
, whenRejected
和 whenUnhandled
提供了很多好处,帮助我们更好地组织和控制应用程序。通过使用 redux-functional-reducer,我们可以构建更健壮、更可扩展和更可维护的应用程序。
下面是完整的代码示例:
-- -------------------- ---- ------- -- ----------- ------ - --------------- - ---- -------- ------ - ---------------- ------------ -------------- ------------ - ---- --------------------------- ------ - ---------------- - ---- -------- ------ - ------------ - ---- ------------------------ ----- -------- - ----------- ----- ----------- - -------------- ----- --------------------- - ------------------------ ----- ------------ - --------------- ----- ------------ - - ----------------- ----------- ------ --- --------- ---- -- -------- ------------------ - ------------------- ------- - ------ ------------- - ---- --------- ------ - --------- - --- ------------ - -- ----- ------------ ---------- ----- - -- ---- ------------ ------ -------------- -- ------- --- --------- - - -------- ---------- --------------- - - ---- -- -------- ------ ------ - - ----- ----------------------- - ------ - ------------------------------ ------- -- - -- ------------ --- ---------------------- - ------ -------------- - ------ ------ -- ----- --------------- - ------ - ---------------------- ------- -- - -- ------------ --- ------------ - ------ --------------- - ------ ------ -- ----- ----------- - ---------------- ------------- ----------------- ------------------------ ------ ------------- --------- --------------- --- ------------------ -- - ------------------ ------- --------- ----- ----- ----- - ---- ------- ------- ---------------- ------ - ----- ------------ -------- ----- -- --- -------------------- -- ----------------------- ------- ----------------- -- ------ ------- ------------ -- ------ ------ ----- ---------------- - -- -- --- ----------------- ------- -- - ------------- -- - ------ -------- --- -------- -------- --- ------ --- -- ----- --- -- ---------- ------ ----- ------- - ------ -- - ------ - ----- --------- ---- -- -- ------ ----- ---------- - ---- -- - ------ - ----- ------------ -- -- -- ------ ----- ------------------- - -------- -- - ------ - ----- ---------------------- ------ -- -- ------ ----- ----------- - ---- -- ----- ---------- --------- -- - --- - ----- -------- - ----- ----------------------------------- ---------- ----- ------------- -------- -------- --- - ----- ------- - ----- - ------- - - ------ ------------------------ ---------------- - ------- --- ----- ------ - -- -- ------ ------ ----- ---- -------- ------ - -------- - ---- -------------- ------ - ---------------- ----------- - ---- -------- ------ ----- ---- -------------- ------ ----------- ---- ------------- ------ - ---------------- ------------ -------------- ------------ - ---- --------------------------- ------ - -------- ----------- -------------------- ----------- - ---- ------------ ------ - ------------------ ------------ - ---- ------------------------ -------- ----- -------- -- - ----- - ----------------- - - -------------------- ----- ------- --------- - ------------------- ----- ------------ - --- -- - ------------------- ------------------------- ------------- -- ----- ------------------ - -- -- - ------ ------------------------- -- ------ - ---- ---------------- -------------- ----- ------------------------ ------- --- ----- ------ ------------- ------------ ----------- -- ------------------------- -- -------- ------- ----------------------------- ------- ------- ---------------------------------- ---------------- ------------------ -- -------------------- --------- -- --- -------- ------ ----- ------------------- ------ -- - ----- ---------- - -------- ----- ----- - ------------ ------------ ------------------------------ -- -------- ------ - ------ - --------- -------------- ---- ------------------------- -- ----------- -- - ------ ------- -----
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/60055ea381e8991b448dbfe8