使用React hooks处理复杂表单状态数据

2,780 阅读4分钟

使用hooks替换this.setState()
使用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文档: lodash.com/docs/4.17.1…

immerjs/immer: github.com/immerjs/imm…

还可以关注头条号:「前端知否」