编辑
2023-01-29
前端
00
请注意,本文编写于 662 天前,最后修改于 662 天前,其中某些信息可能已经过时。

目录

操作数组的核心思想
添加元素
删除元素
转换、更改数组
替换元素
插入元素
对数组进行其他更改
更新数组中对象
使用 immer 解决状态更改的逻辑

操作数组的核心思想

在 JavaScript 中,数组只是另一种对象。与 objects 一样,您应该将处于 React 状态的数组视为 read-only。

这意味着您不应该重新分配数组中的项目,例如arr[0] = 'bird',也不应该使用改变数组的方法,例如push()pop()

相反,每次您想更新一个数组时,您都需要将一个新数组传递给您的状态设置函数。为此,您可以通过调用其非变异方法(如filter()map())从您所在 state 的原始数组创建一个新数组。然后您可以将您的状态设置为生成的新数组。

这里有一个常见数组操作的参考表。在处理 React 状态中的数组时,您需要避免使用左栏中的方法,而更喜欢右栏中的方法:

标题
避免(改变数组)更喜欢(返回一个新数组)
添加push, unshiftconcat,[...arr]传播语法
删除pop, shift, splicefilter, slice
更换splice,arr[i] = ...赋值map
排序reverse, sort首先复制数组

或者,您可以使用 Immer,它允许您使用来自两列的方法。

陷阱

slice和splice命名相似但有很大不同:

slice 让您复制数组或其中的一部分。 splice 改变数组(插入或删除项目)。 在 React 中,你会更频繁地使用slice(而不是 splice) 因为你不应该直接改变状态中的对象或数组。

[...arr]是浅拷贝

即使你使用[...arr]复制了一个数组,你也不能直接改变其中的现有项。

这是因为复制是浅层的——新数组将包含与原始数组相同的项。

js
// nextList[0]andlist[0]指向同一个对象 // 如果您修改复制数组中的对象,就会改变现有状态 const nextList = [...list]; nextList[0].seen = true; // Problem: mutates list[0] setList(nextList);

这里一定要注意,可以通过更新嵌套对象来解决 详见:更新数组中的对象

添加元素

你可以使用 [...arr] 进行操作,来代替 添加到数组末尾的 push() 添加到数组头部的 unshift()

js
// 在数组后面添加 setArtists( // Replace the state [ // with a new array ...artists, // that contains all the old items { id: nextId++, name: name } // and one new item at the end ] );
js
// 在数组前面添加 setArtists([ { id: nextId++, name: name }, ...artists // Put old items at the end ]);

删除元素

你可以使用 filter(), 将需要的内容保留即可,来代替 删除原数组的 pop()

js
setArtists( artists.filter(a => a.id !== artist.id) );

转换、更改数组

如果要更改数组的部分或全部项目,可以使用map()创建一个新数组。您将传递给的函数map可以根据其数据或索引(或两者)决定如何处理每个项目。

js
const a = [1, 5, 6] const b = a.map(v=>{ return v*2 }) // b ==> [2, 10 ,12]

替换元素

要替换项目,请使用 . 创建一个新数组map。在您的map调用中,您将收到项目索引作为第二个参数。用它来决定是否返回原始项目(第一个参数)或其他东西:

js
let initialCounters = [ 0, 0, 0 ]; const [counters, setCounters] = useState( initialCounters ); const nextCounters = counters.map((c, i) => { if (i === index) { // Increment the clicked counter return c + 1; } else { // The rest haven't changed return c; } }); setCounters(nextCounters);

插入元素

有时,您可能希望在既不是开头也不是结尾的特定位置插入一个项目。为此,您可以将...数组展开语法与slice()方法一起使用。该slice()方法允许您切割数组的“切片”。

要插入一个项目,您将创建一个数组

  1. 该数组展开插入点之前的切片
  2. 然后是新元素
  3. 最后是原始数组的其余部分。
js
const insertAt = 1; // Could be any index const nextArtists = [ // Items before the insertion point: ...artists.slice(0, insertAt), // New item: { id: nextId++, name: name }, // Items after the insertion point: ...artists.slice(insertAt) ]; setArtists(nextArtists);

对数组进行其他更改

JavaScript 的 reverse()和sort()方法正在改变原始数组,因此您不能直接使用它们。

但是,您可以先复制数组,然后再对其进行更改。

js
const [list, setList] = useState(initialList); function handleClick() { const nextList = [...list]; nextList.reverse(); setList(nextList); }

更新数组中对象

对象并不真正位于数组“内部”。 它们可能看起来在代码中“内部”,但数组中的每个对象都是一个单独的值,数组“指向”。这就是为什么在更改嵌套字段,另一个人的列表可能指向数组的同一个元素!

更新嵌套状态时,您需要从要更新的点开始创建副本,一直到顶层。

js
setMyList(myList.map(artwork => { if (artwork.id === artworkId) { // Create a *new* object with changes return { ...artwork, seen: nextSeen }; } else { // No changes return artwork; } });

使用 immer 解决状态更改的逻辑

详细的使用可以查看 immer 的相关文档

js
import { useImmer } from 'use-immer'; export default function BucketList() { const [myList, updateMyList] = useImmer( initialList ); function handleToggleMyList(id, nextSeen) { updateMyList(draft => { const artwork = draft.find(a => a.id === id ); artwork.seen = nextSeen; }); } return ( <> <h1>Art Bucket List</h1> <ItemList artworks={myList} onToggle={handleToggleMyList} /> ); }

本文作者:Silon汐冷

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!