React Hook 是 React 16.8 版本中引入的新特性,它可以让我们在函数组件中使用 React 的 state、生命周期等功能。在所有 Hook 中,useReducer 是最强大的一个,它为我们提供了一种更优雅、更可预测以及更易于测试的方式来管理复杂的状态逻辑。本文将会深入探讨 useReducer 在 React 中的应用场景以及如何实现一个自定义 Hook。
useReducer 简介
在探讨 useReducer 的应用场景之前,我们需要先了解一下它是如何工作的。useReducer 的签名如下:
const [state, dispatch] = useReducer(reducer, initialArg, init);
state
:表示状态的值dispatch
:一个函数,用来将 action 分发给 reducer 函数reducer
:一个函数,接收当前状态和分发的 action,返回新的状态值initialArg
:初始化状态的值init
:一个可选的函数,可以将复杂的状态进行初始化
当我们使用 useReducer 时,相当于我们创建了一个可控制的状态容器,状态容器中存储的数据是由我们自己定义的 reducer 函数决定的。一个简单的例子是:
-- -------------------- ---- ------- ----- ------------ - ------- --- -------- -------------- ------- - ------ ------------- - ---- ------------ ------ ------- ----------- - --- ---- ------------ ------ ------- ----------- - --- -------- ----- --- -------- - - -------- --------- - ----- ------- --------- - ------------------- -------------- ------ - -- --------- ----------------- ------- ----------- -- --------------- ------------------------- ------- ----------- -- --------------- ------------------------- --- -- -
在上面的例子中,我们创建了一个初始状态为 {count: 0}
的状态容器,并创建了一个 reducer 函数。在组件中,我们调用了 useReducer 函数,将容器和 reducer 组合在了一起,同时创建了两个按钮,分别在我们点击时向容器中分发了 increment
和 decrement
的 action。每次分发 action 时,容器中的状态都会按照我们在 reducer 函数中定义的逻辑进行更新,并在 React 中重新渲染视图,从而实现了计数器的增减功能。
在这个例子中,我们看到了 useReducer 函数的核心作用,它创建了一个容器来存储我们的状态,并通过 dispatch 函数向容器中分发 action,最终由 reducer 函数来更新容器中的状态值。
useReducer 的应用场景
上面的例子展示了 useReducer 的最基本的用法,但实际上 useReducer 还有很多应用场景。接下来,我们将探讨一些常见的场景。
状态过于复杂
当我们的状态过于复杂、嵌套层次过深时,我们可以选择用 useReducer 来管理状态。一个例子是一个日历应用,我们需要展示不同的事件数据、一个当前的视图模式以及选中的日期。
-- -------------------- ---- ------- ----- ------------ - - ----- --- ------- --------- ------- ------- --- --------- ----- -- -------- -------------- ------- - ------ ------------- - ---- -------------- ------ ---------- ----- ---------------- ---- -------------- ------ ---------- --------- ---------------- ---- --------------- ------ ---------- --------- ---------------- -------- ----- --- -------- - - -------- ---------- - ----- ------- --------- - ------------------- -------------- -- --- -
在上面的例子中,我们可以看到这个日历应用的状态相当复杂,有多个字段、嵌套层次较深。我们使用 useReducer 可以很方便地管理这种复杂的状态变化,同时也使得我们的代码更加易于维护和测试。
表示异步操作的状态
有些状态既表示同步数据的状态,又表示异步操作的状态。例如,在一个带有搜索功能的应用中,我们可能需要表示两种状态:请求状态和搜索结果状态。
-- -------------------- ---- ------- ----- ------------ - -------- ------- ----- ---- -------- -------------- ------- - ------ ------------- - ---- ---------- ------ -------- ---------- ----- ---- ---- ---------- ------ -------- ------- ----- ---------------- ---- ---------- ------ -------- ------- ----- ---- -------- ----- --- -------- - - -------- -------- - ----- ------- --------- - ------------------- -------------- -- --- -
在上面的例子中,我们使用 useReducer 来表示搜索的状态,在请求开始时状态变为 loading,在请求成功时状态变为 idle,并且将搜索结果保存在 data 中。如果请求失败,则将状态也设置为 idle,同时清空搜索结果。
在上面的例子中,我们使用 useReducer 来管理状态,而不是仅仅使用 useState,这是因为 state 变量需要同时表示同步和异步数据,而 useReducer 可以更好地处理状态的复杂性。
可撤回和重做的操作
假设我们有一个可撤回和重做的编辑器应用,我们可能需要记录每一个操作的历史记录,从而能够撤销和重做操作。
-- -------------------- ---- ------- ----- ------------ - ------ --- -------- --------------------- ------- ---- -------- -------------- ------- - ----- ------ -------- ------- - ------ ------ ------------- - ---- ------------------ ------ ------ --------- --------- -------- --------------- ------- ---- ---- ------- ----- ------------ ----------- - ----- ------ ------ -------- -------- ----------- ------- --------- ------------ ---- ------- ----- ----------- ----------- - ------- ------ ------ --------- -------- ------------ -------- ---------- ------- ---- -------- ----- --- -------- - - -------- -------- - ----- ------- --------- - ------------------- -------------- -- --- -
在上面的例子中,我们使用 useReducer 来管理整个应用的状态,包括过去、当前、和未来的状态。当我们分发 UPDATE_DOCUMENT
的 action 时,将当前状态添加到过去状态中,然后将新的状态设置为当前状态。当我们分发 UNDO
的 action 时,将当前状态添加到未来状态中,然后将过去的状态设置为新的当前状态。REDO
操作类似。
实现一个自定义 Hook
使用 useReducer 比使用 useState 更为强大。实际上,大多数使用 useState 的场景都可以使用 useReducer 来实现。我们可以将复杂的状态转换逻辑分解到 reducer 函数中,从而将组件本身变得更为简单。
在本节中,我们将实现一个自定义 Hook,它将使用 useReducer 来管理一个异步请求的状态。在实现之前,我们需要了解一下 useState 和 useReducer 之间的区别。useState 会在每次组件渲染之后重新创建一个新的状态变量。而 useReducer 则在每次渲染时使用相同的状态变量,只是根据传递给它的 action 进行了更新。
我们的自定义 Hook 将使用 useReducer 来存储一些关于请求的状态信息:
-- -------------------- ---- ------- ----- ------------ - - ------- ------- ----- ----- ------ ----- -- -------- -------------- ------- - ------ ------------- - ---- ---------------- ------ ---------- ------- ----------- ----- ----- ------ ------ ---- ------------------ ------ ---------- ------- ---------- ----- ---------------- ---- ------------------ ------ ---------- ------- ---------- ----- ----- ------ ---------------- -------- ----- --- -------- - - -------- -------------------- - ----- ------- --------- - ------------------- -------------- ----- --------- - ----------------- -- -- - --- - --------------- ------------------ ----- -------- - ----- ----------- ----- ---- - ----- ---------------- --------------- ------------------ -------- ------- - ----- ------- - --------------- ------------------ -------- -------- - -- ------- ------ ------- ----------- -
在上面的例子中,我们定义了一个 useAsyncRequest
(命名不规范)自定义 Hook,它接收一个 URL 作为输入。使用 useReducer 和几个 action,我们管理了一个包含了请求状态、响应数据和错误信息的状态对象。当我们向 fetchData
函数分发 REQUEST_START
的 action 时,将状态设置为 FETCHING
,数据和错误信息都设置为 null。当请求成功时,我们将数据添加到状态中,同时将状态设置为 SUCCESS
。当请求失败时,我们将错误信息添加到状态中,同时将状态设置为 FAILURE
。
使用自定义 Hook 的代码如下:
-- -------------------- ---- ------- -------- ----- - ----- ------- ---------- - ------------------------------------------------ ------ - ----- ------------- --- ------ -- ------- ------------------------- -------------- ------------- --- ---------- -- ---------------------- ------------- --- --------- -- --------- -- ------------- ------------- --- --------- -- -------------- ---- ------------ ------ -- -
在上面的例子中,我们使用自定义 Hook useAsyncRequest
从后端获取数据,并根据响应状态展示不同的状态信息。
结论
useReducer 可以帮助我们管理复杂的状态,同时也能使我们的代码更加可维护。通过将状态容器分解为独立的 reducer 函数,我们可以将状态的逻辑分解到不同的函数中,从而使代码变得更加模块化和可读性更高。在我们的应用中,可以通过 useReducer 来表示复杂的状态结构,管理异步数据的状态、记录撤消和重做操作等。
总之,使用 useReducer 和自定义 Hook 可以使我们的 React 应用更加强大和易于维护。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/67492e79a1ce006354455dbf