使用 hooks 替换 this.setState()
自从React hooks* 发布以来已经有一段时间了,我很喜欢这个特性。这个 hooks 把我勾上了!
Hooks允许我们创建更小,可组合,可重用,更易管理的 React 组件。
您可能正在使用 Hooks 的一个用例是:使用 useState 或 useReducer 管理表单状态。
让我们考虑一个场景,您必须管理具有多个输入的复杂表单状态,这些表单输入可以是几种不同的类型,如文本,数字,日期输入。表单状态甚至可以具有嵌套信息,例如用户的地址信息,它具有子字段,例如 address.addressLine1,address.addressLine2 等。
也许您还必须根据当前状态更新表单状态,例如toggle 切换按钮。
现在,如果您对每个单独的表单字段使用 useState,那么您可以 根据当前状态计算新状态。
但是,如果你有太多单独的表单字段,比如 100+,那么这种方法并不友好。
脑补一下 …
编写单独的 useStates,然后为每个字段使用单独的更新函数是不切实际的。我们的另一个选择是 hook,useReducer。
我们来看一个例子。
呃,不好。您不可能为 reducer 中的 n 个表单字段编写每个用例。
但是,useReducer 中使用的 reducer 函数只是一个返回更新状态对象的普通函数。所以,我们可以做得更好。
这样看起来,reducer 简洁干净多了。
但是,现在 reducer更新参数中如果有回调函数,则不能基于当前状态计算新状态,因为当前 state 没有传递给回调函数作为参数。就像我们在 useState 一样:
useState 中的更新函数可以基于 prev 参数计算新状态
另外,如何更新嵌套状态如 address.addressLine1,address.pinCode。
我们通过使用不那么理想的方法进行了很多关于管理复杂表单状态的讨论。让我告诉你解决方案。
因此,这是处理复杂表单场景的完整源代码。
我将稍微解释一下 reducer(enhancedReducer)函数。
reducer 函数接收两个参数,第一个参数是更新前的当前状态 。当您调用 updateState / dispatch 函数来更新 reducer 状态时, 将自动提供此参数。reducer 函数的第二个参数是用于更新 state。它不一定是采用 {type:’something’,payload:’something’} 形式的典型 redux 动作对象。它甚至可以是任何东西,数字,字符串,对象或函数。
这就是我们的做法。如果 updateArg 是一个函数,我们用当前状态调用它来计算新函数。无论我们从这个函数返回什么对象都成为我们的新状态。
如果 updateArg 是一个普通的旧 Javascript 对象,那么有两种情况。
1:该对象没有_path 和_value 属性,因此是一个普通的更新对象,就可以像使用 this.setState 一样。因此,您可以使用包含要更新的状态片段的新对象调用 updateState,并将其与旧状态合并并返回新状态。
2:对象具有_path 和_value 属性 – 当使用具有这两个属性的对象作为参数,调用更新回调函数时。我们将此视为一种特殊情况,其中_path 表示嵌套的字段路径。在字符串形式中,例如:‘address.pinCode’ 或表示路径[‘address’,’pinCode’] 的数组。
我们如何使用此类路径表示来更新对象中的嵌套字段?我们将使用 lodash 的 set 方法。它接受路径表单作为更新和对象的有效输入。
但是,set 方法就地改变对象并且不返回新副本,但在 React 世界中,更改检测取决于 Immutability(不可变)。需要一个全新的数据副本,在内存中有一个新位置来触发渲染。
为了绕过这个,我们使用 immer,来轻松地处理 Javascript 对象的不变性。
immer 中的 produce 函数 将对象作为其 第一个参数 进行处理,在我们的例子中是 当前状态 ,它的第二个参数是一个函数,
它接收对象的草稿副本以进行 mutate,无论你在这个函数内修改了什么草稿状态,是在副本上完成的,而不是实际的输入对象状态。 然后,它会自动返回包含更新数据的新对象。
这就是我们的增强版 reducer。
安装一下依赖,就可以跑起来了。
PS:在 enhancedReducer 中可以处理更多边缘情况,动态字段映射也可以缩短一些代码,减少代码重复和其他一些事情。
参考资源:
Lodash 文档: https://lodash.com/docs/4.17….
immerjs/immer: https://github.com/immerjs/im…
还可以关注头条号:「前端知否」