在 JavaScript 中,数组只是另一种对象。与 objects 一样,您应该将处于 React 状态的数组视为 read-only。
这意味着您不应该重新分配数组中的项目,例如arr[0] = 'bird'
,也不应该使用改变数组的方法,例如push()
和pop()
。
相反,每次您想更新一个数组时,您都需要将一个新数组传递给您的状态设置函数。为此,您可以通过调用其非变异方法(如filter()
和map()
)从您所在 state 的原始数组创建一个新数组。然后您可以将您的状态设置为生成的新数组。
这里有一个常见数组操作的参考表。在处理 React 状态中的数组时,您需要避免使用左栏中的方法,而更喜欢右栏中的方法:
标题 | ||
---|---|---|
避免(改变数组) | 更喜欢(返回一个新数组) | |
添加 | push, unshift | concat,[...arr]传播语法 |
删除 | pop, shift, splice | filter, 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()
jssetArtists(
artists.filter(a => a.id !== artist.id)
);
如果要更改数组的部分或全部项目,可以使用map()
创建一个新数组。您将传递给的函数map可以根据其数据或索引(或两者)决定如何处理每个项目。
jsconst a = [1, 5, 6]
const b = a.map(v=>{
return v*2
})
// b ==> [2, 10 ,12]
要替换项目,请使用 . 创建一个新数组map。在您的map
调用中,您将收到项目索引作为第二个参数。用它来决定是否返回原始项目(第一个参数)或其他东西:
jslet 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()方法允许您切割数组的“切片”。
要插入一个项目,您将创建一个数组
jsconst 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()方法正在改变原始数组,因此您不能直接使用它们。
但是,您可以先复制数组,然后再对其进行更改。
jsconst [list, setList] = useState(initialList);
function handleClick() {
const nextList = [...list];
nextList.reverse();
setList(nextList);
}
对象并不真正位于数组“内部”。 它们可能看起来在代码中“内部”,但数组中的每个对象都是一个单独的值,数组“指向”。这就是为什么在更改嵌套字段,另一个人的列表可能指向数组的同一个元素!
更新嵌套状态时,您需要从要更新的点开始创建副本,一直到顶层。
jssetMyList(myList.map(artwork => {
if (artwork.id === artworkId) {
// Create a *new* object with changes
return { ...artwork, seen: nextSeen };
} else {
// No changes
return artwork;
}
});
详细的使用可以查看 immer 的相关文档
jsimport { 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 许可协议。转载请注明出处!