当 React state 是一个数组时,如何添加项目到数组中并更新状态可能并不明显,特别是当尝试使用 React Hooks 时。
这个问题可能会出现在状态是一个项目列表时。在本文中,我将探讨用户最近搜索的查询的示例。
更复杂的例子是将 JavaScript 对象列表存储为 React state 中的数组,例如学生记录。
问题是如何将项目添加到 React State 中的数组中?
创建 React state 作为数组的典型代码示例使用类似于 useState([])
的调用来初始化 React state。
使用 <a class="af oe" href="https://reactjs.org/docs/hooks-overview.html" rel="noopener ugc nofollow" target="_blank">useState</a>
Hook,这被称为“使用 Hook”,在函数组件中调用它以向其添加一些本地状态。React 将在重新渲染之间保留此状态。
这是将数组作为 <a class="af oe" href="https://daveceddia.com/usestate-hook-examples/" rel="noopener ugc nofollow" target="_blank">useState()</a>
的有效参数的示例。
在接下来的部分中,我尝试使用各种方法向 React state 中的数组添加内容,这些内容可以通过 searches
访问,并通过 setSearches
进行更改。
我首先想到的是使用 push()
,这是一种将内容添加到数组末尾的常见 JavaScript 方法。
.push()
方法在 Array.prototype 上定义,因此可以使用点引用运算符在数组上调用,如下所示:
我想向 React State 中的数组添加内容,因此似乎可以尝试将新项目 .push()
到列表末尾,从而更新状态。
不幸的是,searches.push()
代码在 React 中不起作用。
当我尝试以这种方式将搜索词保存到 React State 时,什么都不会发生 - 没有错误,没有任何提示。发生了什么?
Photo by Kelly Sikkema on Unsplash
我忘记使用 React Hooks 的 setter 了
无法正常工作的原因是我尝试直接修改 React 状态,虽然 .push()
确实修改了变量 searches
中的数组,但这种修改不会“挂钩”到 React 上以在重新渲染时更新。
React Hooks 需要特定的 setter 函数,这是最初从 useState
调用返回的第二部分,也就是我称之为 setSearches
的部分。
“
useState
返回一对:当前状态值和一个让你更新它的函数。” —— React 文档
换句话说,React 中应该将状态视为不可变的,因此不应该直接更改(或变异)它。
这就是为什么 React 状态本身(searches
变量和 setSearches
hook)被定义为使用 const
的常量 —— 作为提醒。
这是设计上的,这意味着使用 React Hooks 更新状态涉及与 useState()
返回的相关 setter 函数。
Photo by Daria Nepriakhina on Unsplash
setSearches(searches.push(query))
不起作用
即使将 .push()
包装在 React Hooks 的 setter 函数中,它也不起作用,尽管它似乎应该。
实际上,setSearches(searches.push(query))
会崩溃并显示一个 <a class="af oe" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError" rel="noopener ugc nofollow" target="_blank">TypeError</a>
:
在这种情况下,应用程序已经损坏了,我可能正在恐慌!
幸运的是,工作解决方案与上面非常相似,只是用 .concat()
代替了 .push()
函数调用。
但为什么 searches.push()
会失败,而 .concat()
会成功呢?
Photo by Roman Synkevych on Unsplash
为什么 .push()
与 React Hooks 失败?
前面的代码示例无法工作,因为 .push()
返回修改后数组的长度,而不是数组本身。
React Hooks 的 setter 函数 setSearches()
,也称为reducer,将当前状态更新为传入的值。
在上面的示例中,状态已从数组更新为数字 —— 这就是为什么 TypeError
是 searches.map is not a function
。
React 状态 searches
被从其状态 []
更改为 .push
后数组的长度 —— .length
变成了 1,因此搜索现在是 1
。
虽然 []<a class="af oe" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map" rel="noopener ugc nofollow" target="_blank">.map()</a>
可以工作,但 1<a class="af oe" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map" rel="noopener ugc nofollow" target="_blank">.map()</a>
在 JavaScript 中是无意义的。
与其直接更改旧状态,我需要直接将更新后的状态传递给 React Hooks 状态 setter(reducer 函数)setSearches
。
Photo by Lukas Blazek on Unsplash
解决方案是 .concat()
解决方案是 <a class="af oe" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat" rel="noopener ugc nofollow" target="_blank">Array.prototype.concat()</a>
方法,简称为 concatenate,它类似于 .push()
,但有一个扭曲。```
.concat()
方法之所以可以用来更新状态,是因为它创建了一个新数组,保留了旧数组,并返回更改后的数组。
另一方面,.push()
方法会在原地修改旧数组,但返回的是修改后的数组长度而不是数组本身。
因此,使用.push()
方法意味着状态会被新数组的长度覆盖——这是由于不了解.push()
返回值所导致的简单错误。
下面的代码可以工作,因为.concat()
返回一个新的、更新后的数组:
在尊重 React 中状态的不可变性的同时,我使用.concat()
方法来实现向 React 状态中添加项目的结果。
如果我想在列表前面添加项目,只需要反转操作顺序,如下所示:
但是,有一个重要的步骤我忽略了——在频繁更新的 React 组件中使用“包装函数”可以防止出现错误。
我将在下一节中解释如何使用 ES6 特性 …
(扩展运算符)以及包装函数的潜在好处。
照片来自 Mike Kenneally 在 Unsplash
最佳解决方案:扩展运算符 …
和包装函数
JavaScript 扩展运算符(…
)有助于复制和组合数组,因此也可以用于向 React 状态中添加项目。
[...searches, query]
在数组末尾添加项目[query, ...searches]
在数组开头添加项目
请注意,在代码示例中,我还使用了一个包装函数——而不是传递状态,我传递了一个回调函数,该回调函数接受当前状态。
setSearches(searches => searches.concat(query))
使用.concat()
setSearches(searches => [...searches, query])
使用…
扩展
在传递给 setSearches
Hook 的包装函数中,我通过返回更新后的状态 searches.concat(query)
(使用.concat()
)或[...searches, query]
(使用扩展运算符 …
)来更新状态 searches
。
照片来自 Bench Accounting 在 Unsplash
为什么使用包装函数?
在 React Hooks 的 setter 函数内部使用包装函数比仅使用<a class="af oe" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat" rel="noopener ugc nofollow" target="_blank">.concat()</a>
或 …
实现更好。
Jasper Dunn 在回应本文时写道,始终在处理 React 状态时使用包装函数有很多优点:
“从初始 Hook 访问的
searches
的值可能与预期不同,这可能会导致不希望的副作用。”——Jasper Dunn
强烈建议使用包装函数,以便在重新渲染实际发生时访问当前状态,而不是在其他时间访问。
包装函数也称为回调函数,因为它是指将函数传递给另一个函数。
照片来自 Kim S. Ly 在 Unsplash# 总结:如何向状态数组添加内容
通过掌握包装函数,我终于在三个简单的步骤中解决了如何在 React 状态中向数组添加内容的问题:
- 使用
useState([])
Hook 将状态设置为[]
并获取状态设置函数 - 将包装函数作为回调函数传递给状态设置函数
- 在包装函数中使用
.concat
或展开运算符…
更新数组
但是我仍然想知道为什么 React 状态首先是不可变的?
照片由Andrew Neel在Unsplash上拍摄
为什么 .concat()
或 …
是 React 中正确的工具
在 React 中,状态应该是不可变的。例如,这就是为什么 React Hooks 通常使用 <a class="af oe" href="https://medium.com/javascript-in-plain-english/how-to-use-let-var-and-const-in-javascript-cdf42b48d70" rel="noopener">const</a>
进行定义的原因,以提醒不要随意更改状态。
为什么会这样呢?Esteban Herrer 在LogRocket博客中解释了这一点:
“对于大小相同的两个数组,唯一确定它们是否相等的方法是比较每个元素。对于大型数组来说,这是一个昂贵的操作。
最简单的解决方案是使用不可变对象。
如果需要更新对象,则必须创建一个新对象并分配新值,因为原始对象是不可变的,无法更改。”
因为状态是不可变的,React 知道状态何时发生了变化——当创建一个新值并将其分配给 React 状态时。
使用 .concat()
或展开运算符 …
将状态复制到新数组中,我们将其作为更新后的状态传递给 React。
直到 React Hooks 设置函数通知状态实际上已更改,React 才必须更新状态并重新渲染屏幕。
这有利于提高 React 应用程序的性能。
照片由Ross Findon在Unsplash上拍摄
不可变性的好处
显然,在应用程序的某个点上,我们会更新状态——这只是通过提供 React 一个新状态来实现的,覆盖旧状态。
这意味着我们不能在原地改变状态,我们通过用新状态替换它来更新状态——对于数组,使用 .concat()
或展开运算符 …
。
使用不可变状态有助于使我们的代码更加防错,并向虚拟 DOM 发出信号,以在引用更改时更新。
不可变性确保我们只有在实际需要时才会改变状态——不会意外地丢失用户数据或在生产中发生其他可怕的事情。😱
照片由Chris Lawton在Unsplash上拍摄
自己试试吧
我在文章结尾处包含了一个实时演示和完整的代码,演示了一个简单的 React 应用程序,该应用程序将搜索词保存在状态中。
每个短语都使用展开运算符 …
和包装函数添加到 React 状态中,如上所示。
这是它的运行截图:
照片由Kyle Glenn在Unsplash上拍摄
在 CodeSandbox.io 上的实时演示
实时演示的源代码
更多阅读
- Robin Wieruch 在他的博客上全面解释了数组和 React State: 如何使用数组管理 React State - RWieruch
- Jason Arnold 在他的 Medium 博客上解释了如何使用展开运算符实现相同的效果——向 React State 中的数组添加项目: 在 React setState 中使用展开运算符- Flavio Copes 在他的博客上用清晰的语言解释了不可变性的概念:
- Steven de Salas 在他的博客上写道,不可变性被高估了:
JavaScript 中的不可变性:反其道而行之 " desalasworks | Steven de Salas | Melbourne Full…
- Dave Ceddia 在他的博客上教我们使用
setState
Hook 的四个用法:
照片由Robin Benzrihem在Unsplash上提供
Dr. Derek Austin 是《职业编程:如何在 6 个月内成为成功的 6 位数程序员》的作者,现在已经在亚马逊上架。
译自:https://javascript.plainenglish.io/how-to-add-to-an-array-in-react-state-3d08ddb2e1dc
评论(0)