我的应用程序中有一个下拉菜单。当它被选择时,它会触发一个更改 sortOption 状态的方法,然后触发 useEffect 对数据进行排序(称为 allTabs)。然而,数据似乎总是落后一步。例如,默认是从新到旧,但它最初显示未排序的数据。如果随后按下一个新选项(例如 A-Z),数据就会按新到旧排序。如果再次按下新选项,例如从旧到新,数据就会按 A-Z 等排序。我不确定为什么会发生这种情况。我看过有关使用回调的帖子,但我对 React、NodeJS 和 TypeScript 还很陌生,我不确定如何将这些答案集成到我的代码中。
下拉菜单如下所示:
{/* Dropdown for sorting */}
<div className="mb-4">
<label htmlFor="sortOptions" className="mr-2">Sort by:</label>
<select
style={{ color: 'black' }}
id="sortOptions"
value={sortOption}
onChange={handleSortChange}
className="p-2 border rounded"
>
<option value="Newest to Oldest">Newest to Oldest</option>
<option value="Oldest to Newest">Oldest to Newest</option>
<option value="A-Z">A-Z</option>
<option value="Z-A">Z-A</option>
</select>
</div>
这里是handleSortChange方法:
// Handle dropdown change with type annotation
const handleSortChange = (event: ChangeEvent<HTMLSelectElement>) => {
setSortOption(event.target.value);
};
这里是sortOption的useState,以及sortOption或allTabs(我正在排序的数据)变化时触发的useEffect:
const [sortOption, setSortOption] = useState<string>("Newest to Oldest");
// sort tabs based on selected option, update if allTabs or sortOption changes
useEffect(() => {
console.log(allTabs);
console.log(sortOption);
switch (sortOption) {
case "Newest to Oldest":
allTabs.sort(compareDates);
console.log("sorted new to old");
break;
case "Oldest to Newest":
allTabs.sort((a, b) => compareDates(b, a));
console.log("sorted old to new");
break;
case "A-Z":
allTabs.sort((a, b) => a.name.localeCompare(b.name));
console.log("sorted A-Z");
break;
case "Z-A":
allTabs.sort((a, b) => b.name.localeCompare(a.name));
console.log("sorted Z-A");
break;
default:
return;
}
console.log(allTabs);
}, [sortOption, allTabs])
我知道这个 useEffect 中的方法可以正常工作,排序也是如此。
可以看到,useEffect方法中有控制台日志。他们打印出数据和所采取的路径。他们给了我一个有趣的结果。当我在切换之前和之后打印所有选项卡时,它们都会根据打印出来的正确路径进行排序,这意味着一切看起来都是正确的。用户选择的选项确实是打印内容以及数据如何排序。但网站上实际显示的数据仍然是上次选择的数据。因此,我假设更新 allTabs 时数据的呈现不会更新,这是我需要能够做到的。如果我尝试在 useEffect 中实际使用 allTabs 的 setter,它只会导致无限循环,所以我不确定如何做出反应以意识到数据已更新。强制重新加载页面只会重置所有内容,包括数据和排序选项。
编辑: 为了显示数据,我当前正在制作所有选项卡的地图。我应该在组件中执行此操作并使其接受一些数据,以便每当我更改它或其他内容时都可以使用数据调用该组件吗?
这就是数据的显示方式。这发生在应用程序底部的返回中,以及其他一些内容中
{allTabs.map((allTab, index) => (
<React.Fragment key={index}>
<Link
className=""
href="/"
onClick={() => {
localStorage.setItem("tabTitle", allTab.name);
localStorage.setItem("tabContent", allTab.tab);
}}
>
{allTab.name}
</Link>
<span>
{allTab.created_by + " on " + formatDate(allTab.created_at)}
</span>
<div className="ml-auto">
{savedTabs.some(
(savedTab) => savedTab.tabs.id === allTab.id
) ? (
<Image
src="/saved.png"
alt="saved tab"
className="dark:invert"
width={24}
height={24}
onClick={() => {
toggleSaved(userId, allTab);
}}
/>
) : (
<Image
src="/not_saved.png"
alt="unsaved tab"
className="dark:invert"
width={24}
height={24}
onClick={() => {
toggleSaved(userId, allTab);
}}
priority
/>
)}
</div>
</React.Fragment>
))}
看来您没有意识到
.sort
应用了就地突变。如果 allTabs
是某种 React 状态,那么改变状态不会触发组件重新渲染。我怀疑这就是为什么您在效果中看到正确的排序数组突变和日志,但组件没有被触发重新渲染以显示新的排序数组。 useEffect
回调也会在渲染周期结束时调用,因此即使突变可见,也已经为时已晚,渲染周期已完成。
一般来说,在对数组数据进行排序或过滤时,常见的模式是不对源数组进行过滤/排序,例如事实的状态来源,而是使用源数组和对其应用的过滤/排序标准来计算派生的过滤/排序值。
因为
.sort
应用就地排序/突变,您需要首先浅复制 allTabs
数组:
.slice
创建副本:allTabs.slice().sort(compareFn)
[...allTabs].sort(compareFn)
.toSorted
方法:allTabs.toSorted(compareFn)
示例:
const compareFn = (sortOption: string) => (a: Tab, b: Tab) => {
switch (sortOption) {
case "Newest to Oldest":
default:
return compareDates(a, b);
case "Oldest to Newest":
return compareDates(b, a));
case "A-Z":
return a.name.localeCompare(b.name);
case "Z-A":
return b.name.localeCompare(a.name);
}
}
const [sortOption, setSortOption] = useState<string>("Newest to Oldest");
const [allTabs, setAllTabs] = useState<Tab[]>([......]);
const sortedAllTabs = allTabs.slice().sort(compareFn(sortOption));
// const sortedAllTabs = [...allTabs].sort(compareFn(sortOption));
// const sortedAllTabs = allTabs.toSorted(compareFn(sortOption));
如果您愿意,您可以额外记住计算/导出的值作为性能优化的一点。使用
useMemo
钩子仅在任何依赖项更新时重新计算值,而不是在渲染时调用组件主体时重新计算值。
const sortedAllTabs = useMemo(() => {
return allTabs.slice().sort(compareFn(sortOption));
// return [...allTabs].sort(compareFn(sortOption));
// return allTabs.toSorted(compareFn(sortOption));
}, [allTabs, sortOption]);
使用计算/派生的
sortedAllTabs
数组映射到渲染的 JSX。另请注意,由于您正在改变数组顺序,因此使用数组索引作为 React 键的效果非常差,因为数组索引不会“粘住”它们应该表示的数据。您应该使用数据固有的且唯一的值/属性。 Id 属性或兄弟元素中唯一的任何属性就足够了。在您的代码中,似乎 allTab.id
可能合适。
{sortedAllTabs.map((allTab) => (
<React.Fragment key={allTab.id}>
<Link
className=""
href="/"
onClick={() => {
localStorage.setItem("tabTitle", allTab.name);
localStorage.setItem("tabContent", allTab.tab);
}}
>
{allTab.name}
</Link>
<span>
{allTab.created_by + " on " + formatDate(allTab.created_at)}
</span>
<div className="ml-auto">
{savedTabs.some(
(savedTab) => savedTab.tabs.id === allTab.id
) ? (
<Image
src="/saved.png"
alt="saved tab"
className="dark:invert"
width={24}
height={24}
onClick={() => {
toggleSaved(userId, allTab);
}}
/>
) : (
<Image
src="/not_saved.png"
alt="unsaved tab"
className="dark:invert"
width={24}
height={24}
onClick={() => {
toggleSaved(userId, allTab);
}}
priority
/>
)}
</div>
</React.Fragment>
))}