深圳住房和建设局网站登录东莞免费建站在线咨询
news/
2025/9/23 23:22:24/
文章来源:
深圳住房和建设局网站登录,东莞免费建站在线咨询,手机在线,网站建设公司市场定位参考文章
更新 state 中的数组
数组是另外一种可以存储在 state 中的 JavaScript 对象#xff0c;它虽然是可变的#xff0c;但是却应该被视为不可变。同对象一样#xff0c;当想要更新存储于 state 中的数组时#xff0c;需要创建一个新的数组#xff08;或者创建一份已…参考文章
更新 state 中的数组
数组是另外一种可以存储在 state 中的 JavaScript 对象它虽然是可变的但是却应该被视为不可变。同对象一样当想要更新存储于 state 中的数组时需要创建一个新的数组或者创建一份已有数组的拷贝值并使用新数组设置 state。
在没有 mutation 的前提下更新数组
在 JavaScript 中数组只是另一种对象。同对象一样需要将 React state 中的数组视为只读的。这意味着不应该使用类似于 arr[0] bird 这样的方式来重新分配数组中的元素也不应该使用会直接修改原始数组的方法例如 push() 和 pop()。
相反每次要更新一个数组时需要把一个新的数组传入 state 的 setting 方法中。为此可以通过使用像 filter() 和 map() 这样不会直接修改原始值的方法从原始数组生成一个新的数组。然后就可以将 state 设置为这个新生成的数组。
下面是常见数组操作的参考表。当操作 React state 中的数组时需要避免使用左列的方法而首选右列的方法
避免使用 (会改变原始数组)推荐使用 (会返回一个新数组添加元素pushunshiftconcat[...arr] 展开语法例子删除元素popshiftsplicefilterslice例子替换元素splicearr[i] ... 赋值map例子排序reversesort先将数组复制一份例子
或者可以使用 Immer 这样便可以使用表格中的所有方法了。
注意不幸的是虽然 slice 和 splice 的名字相似但作用却迥然不同
slice 可以拷贝数组或是数组的一部分。splice 会直接修改 原始数组插入或者删除元素。
在 React 中更多情况下会使用 slice因为不想改变 state 中的对象或数组。
向数组中添加元素
应该创建一个 新 数组其包含了原始数组的所有元素 以及 一个在末尾的新元素。这可以通过很多种方法实现最简单的一种就是使用 ... 数组展开 语法
setArtists( // 替换 state[ // 是通过传入一个新数组实现的...artists, // 新数组包含原数组的所有元素{ id: nextId, name: name } // 并在末尾添加了一个新的元素]
);示例代码
import { useState } from react;let nextId 0;export default function List() {const [name, setName] useState();const [artists, setArtists] useState([]);return (h1振奋人心的雕塑家们/h1inputvalue{name}onChange{e setName(e.target.value)}/button onClick{() {setArtists([...artists,{ id: nextId, name: name }]);}}添加/buttonul{artists.map(artist (li key{artist.id}{artist.name}/li))}/ul/);
}数组展开运算符还允许把新添加的元素放在原始的 ...artists 之前
setArtists([{ id: nextId, name: name },...artists // 将原数组中的元素放在末尾
]);这样一来展开操作就可以完成 push() 和 unshift() 的工作将新元素添加到数组的末尾和开头。
从数组中删除元素
从数组中删除一个元素最简单的方法就是将它过滤出去。换句话说需要生成一个不包含该元素的新数组。这可以通过 filter 方法实现例如
import { useState } from react;let initialArtists [{ id: 0, name: Marta Colvin Andrade },{ id: 1, name: Lamidi Olonade Fakeye},{ id: 2, name: Louise Nevelson},
];export default function List() {const [artists, setArtists] useState(initialArtists);return (h1振奋人心的雕塑家们/h1ul{artists.map(artist (li key{artist.id}{artist.name}{ }button onClick{() {setArtists(artists.filter(a a.id ! artist.id));}}删除/button/li))}/ul/);
}点击“删除”按钮几次并且查看按钮处理点击事件的代码。
setArtists(artists.filter(a a.id ! artist.id)
);这里artists.filter(s s.id ! artist.id) 表示“创建一个新的数组该数组由那些 ID 与 artists.id 不同的 artists 组成”。换句话说每个 artist 的“删除”按钮会把 那一个 artist 从原始数组中过滤掉并使用过滤后的数组再次进行渲染。注意filter 并不会改变原始数组。
转换数组
如果想改变数组中的某些或全部元素可以用 map() 创建一个新数组。传入 map 的函数决定了要根据每个元素的值或索引或二者都要对元素做何处理。
在下面的例子中一个数组记录了两个圆形和一个正方形的坐标。当点击按钮时仅有两个圆形会向下移动 100 像素。这是通过使用 map() 生成一个新数组实现的。
import { useState } from react;let initialShapes [{ id: 0, type: circle, x: 50, y: 100 },{ id: 1, type: square, x: 150, y: 100 },{ id: 2, type: circle, x: 250, y: 100 },
];export default function ShapeEditor() {const [shapes, setShapes] useState(initialShapes);function handleClick() {const nextShapes shapes.map(shape {if (shape.type square) {// 不作改变return shape;} else {// 返回一个新的圆形位置在下方 50px 处return {...shape,y: shape.y 50,};}});// 使用新的数组进行重渲染setShapes(nextShapes);}return (button onClick{handleClick}所有圆形向下移动/button{shapes.map(shape (divkey{shape.id}style{{background: purple,position: absolute,left: shape.x,top: shape.y,borderRadius:shape.type circle? 50% : ,width: 20,height: 20,}} /))}/);
}替换数组中的元素
想要替换数组中一个或多个元素是非常常见的。类似 arr[0] bird 这样的赋值语句会直接修改原始数组所以在这种情况下也应该使用 map。
要替换一个元素请使用 map 创建一个新数组。在 map 回调里第二个参数是元素的索引。使用索引来判断最终是返回原始的元素即回调的第一个参数还是替换成其他值
import { useState } from react;let initialCounters [0, 0, 0
];export default function CounterList() {const [counters, setCounters] useState(initialCounters);function handleIncrementClick(index) {const nextCounters counters.map((c, i) {if (i index) {// 递增被点击的计数器数值return c 1;} else {// 其余部分不发生变化return c;}});setCounters(nextCounters);}return (ul{counters.map((counter, i) (li key{i}{counter}button onClick{() {handleIncrementClick(i);}}1/button/li))}/ul);
}向数组中插入元素
有时也许想向数组特定位置插入一个元素这个位置既不在数组开头也不在末尾。为此可以将数组展开运算符 ... 和 slice() 方法一起使用。slice() 方法让从数组中切出“一片”。为了将元素插入数组需要先展开原数组在插入点之前的切片然后插入新元素最后展开原数组中剩下的部分。
下面的例子中插入按钮总是会将元素插入到数组中索引为 1 的位置。
import { useState } from react;let nextId 3;
const initialArtists [{ id: 0, name: Marta Colvin Andrade },{ id: 1, name: Lamidi Olonade Fakeye},{ id: 2, name: Louise Nevelson},
];export default function List() {const [name, setName] useState();const [artists, setArtists] useState(initialArtists);function handleClick() {const insertAt 1; // 可能是任何索引const nextArtists [// 插入点之前的元素...artists.slice(0, insertAt),// 新的元素{ id: nextId, name: name },// 插入点之后的元素...artists.slice(insertAt)];setArtists(nextArtists);setName();}return (h1振奋人心的雕塑家们/h1inputvalue{name}onChange{e setName(e.target.value)}/button onClick{handleClick}插入/buttonul{artists.map(artist (li key{artist.id}{artist.name}/li))}/ul/);
}其他改变数组的情况
总会有一些事是仅仅依靠展开运算符和 map() 或者 filter() 等不会直接修改原值的方法所无法做到的。例如可能想翻转数组或是对数组排序。而 JavaScript 中的 reverse() 和 sort() 方法会改变原数组所以无法直接使用它们。
然而可以先拷贝这个数组再改变这个拷贝后的值。
例如
import { useState } from react;const initialList [{ id: 0, title: Big Bellies },{ id: 1, title: Lunar Landscape },{ id: 2, title: Terracotta Army },
];export default function List() {const [list, setList] useState(initialList);function handleClick() {const nextList [...list];nextList.reverse();setList(nextList);}return (button onClick{handleClick}翻转/buttonul{list.map(artwork (li key{artwork.id}{artwork.title}/li))}/ul/);
}在这段代码中先使用 [...list] 展开运算符创建了一份数组的拷贝值。当有了这个拷贝值后就可以使用像 nextList.reverse() 或 nextList.sort() 这样直接修改原数组的方法。甚至可以通过 nextList[0] something 这样的方式对数组中的特定元素进行赋值。
然而即使拷贝了数组还是不能直接修改其内部的元素。这是因为数组的拷贝是浅拷贝——新的数组中依然保留了与原始数组相同的元素。因此如果修改了拷贝数组内部的某个对象其实正在直接修改当前的 state。举个例子像下面的代码就会带来问题。
const nextList [...list];
nextList[0].seen true; // 问题直接修改了 list[0] 的值
setList(nextList);虽然 nextList 和 list 是两个不同的数组nextList[0] 和 list[0] 却指向了同一个对象。因此通过改变 nextList[0].seenlist[0].seen 的值也被改变了。这是一种 state 的 mutation 操作应该避免这么做可以用类似于 更新嵌套的 JavaScript 对象 的方式解决这个问题——拷贝想要修改的特定元素而不是直接修改它。下面是具体的操作。
更新数组内部的对象
对象并不是 真的 位于数组“内部”。可能他们在代码中看起来像是在数组“内部”但其实数组中的每个对象都是这个数组“指向”的一个存储于其它位置的值。这就是当在处理类似 list[0] 这样的嵌套字段时需要格外小心的原因。其他人的艺术品清单可能指向了数组的同一个元素
当更新一个嵌套的 state 时需要从想要更新的地方创建拷贝值一直这样直到顶层。 让我们看一下这该怎么做。
在下面的例子中两个不同的艺术品清单有着相同的初始 state。他们本应该互不影响但是因为一次 mutation他们的 state 被意外地共享了勾选一个清单中的事项会影响另外一个清单
import { useState } from react;let nextId 3;
const initialList [{ id: 0, title: Big Bellies, seen: false },{ id: 1, title: Lunar Landscape, seen: false },{ id: 2, title: Terracotta Army, seen: true },
];export default function BucketList() {const [myList, setMyList] useState(initialList);const [yourList, setYourList] useState(initialList);function handleToggleMyList(artworkId, nextSeen) {const myNextList [...myList];const artwork myNextList.find(a a.id artworkId);artwork.seen nextSeen;setMyList(myNextList);}function handleToggleYourList(artworkId, nextSeen) {const yourNextList [...yourList];const artwork yourNextList.find(a a.id artworkId);artwork.seen nextSeen;setYourList(yourNextList);}return (h1艺术愿望清单/h1h2我想看的艺术清单/h2ItemListartworks{myList}onToggle{handleToggleMyList} /h2你想看的艺术清单/h2ItemListartworks{yourList}onToggle{handleToggleYourList} //);
}function ItemList({ artworks, onToggle }) {return (ul{artworks.map(artwork (li key{artwork.id}labelinputtypecheckboxchecked{artwork.seen}onChange{e {onToggle(artwork.id,e.target.checked);}}/{artwork.title}/label/li))}/ul);
}问题出在下面这段代码中:
const myNextList [...myList];
const artwork myNextList.find(a a.id artworkId);
artwork.seen nextSeen; // 问题直接修改了已有的元素
setMyList(myNextList);虽然 myNextList 这个数组是新的但是其内部的元素本身与原数组 myList 是相同的。因此修改 artwork.seen其实是在修改原始的 artwork 对象。而这个 artwork 对象也被 yourList 使用这样就带来了 bug。这样的 bug 可能难以想到但好在如果避免直接修改 state它们就会消失。
可以使用 map 在没有 mutation 的前提下将一个旧的元素替换成更新的版本。
setMyList(myList.map(artwork {if (artwork.id artworkId) {// 创建包含变更的新对象return { ...artwork, seen: nextSeen };} else {// 没有变更return artwork;}
}));此处的 ... 是一个对象展开语法被用来创建一个对象的拷贝.
通过这种方式没有任何现有的 state 中的元素会被改变bug 也就被修复了。
通常来讲应该只直接修改刚刚创建的对象。如果正在插入一个新的 artwork可以修改它但是如果想要改变的是 state 中已经存在的东西就需要先拷贝一份了。
使用 Immer 编写简洁的更新逻辑
在没有 mutation 的前提下更新嵌套数组可能会变得有点重复。就像对对象一样:
通常情况下应该不需要更新处于非常深层级的 state 。如果有此类需求或许需要调整一下数据的结构让数据变得扁平一些。如果想改变 state 的数据结构可以使用 Immer 它让你可以继续使用方便的但会直接修改原值的语法并负责为你生成拷贝值。
下面是我们用 Immer 来重写的艺术愿望清单的例子
import { useState } from react;
import { useImmer } from use-immer;let nextId 3;
const initialList [{ id: 0, title: Big Bellies, seen: false },{ id: 1, title: Lunar Landscape, seen: false },{ id: 2, title: Terracotta Army, seen: true },
];export default function BucketList() {const [myList, updateMyList] useImmer(initialList);const [yourList, updateYourList] useImmer(initialList);function handleToggleMyList(id, nextSeen) {updateMyList(draft {const artwork draft.find(a a.id id);artwork.seen nextSeen;});}function handleToggleYourList(artworkId, nextSeen) {updateYourList(draft {const artwork draft.find(a a.id artworkId);artwork.seen nextSeen;});}return (h1艺术愿望清单/h1h2我想看的艺术清单/h2ItemListartworks{myList}onToggle{handleToggleMyList} /h2你想看的艺术清单/h2ItemListartworks{yourList}onToggle{handleToggleYourList} //);
}function ItemList({ artworks, onToggle }) {return (ul{artworks.map(artwork (li key{artwork.id}labelinputtypecheckboxchecked{artwork.seen}onChange{e {onToggle(artwork.id,e.target.checked);}}/{artwork.title}/label/li))}/ul);
}请注意当使用 Immer 时类似 artwork.seen nextSeen 这种会产生 mutation 的语法不会再有任何问题了
updateMyTodos(draft {const artwork draft.find(a a.id artworkId);artwork.seen nextSeen;
});这是因为并不是在直接修改原始的 state而是在修改 Immer 提供的一个特殊的 draft 对象。同理也可以为 draft 的内容使用 push() 和 pop() 这些会直接修改原值的方法。
在幕后Immer 总是会根据对 draft 的修改来从头开始构建下一个 state。这使得你的事件处理程序非常的简洁同时也不会直接修改 state。
摘要
可以把数组放入 state 中但不应该直接修改它。不要直接修改数组而是创建它的一份 新的 拷贝然后使用新的数组来更新它的状态。可以使用 [...arr, newItem] 这样的数组展开语法来向数组中添加元素。可以使用 filter() 和 map() 来创建一个经过过滤或者变换的数组。可以使用 Immer 来保持代码简洁。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/914211.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!