React下拉选择排序数组时渲染效果滞后一步问题求解
问题原因
- 核心问题是
Array.sort()为原地排序方法:会直接修改调用它的原数组,返回值仍然是原数组的引用。你直接对state中的albums调用sort,相当于未经setter直接修改了state的内部内容,再调用setAlbums时,因为传入的数组引用和当前state的引用完全相等,React会判定状态无变化,跳过本次重渲染,页面不会更新。但此时原数组的内容已经被修改,等到下一次修改sort值触发useEffect时,拿到的是已经被上一次排序修改过的数组,执行本次排序后才触发更新,就出现了效果滞后一步的问题。 - 次要隐患:select的
onChange事件返回的e.target.value是字符串类型,你初始化的sort是数字类型,虽然用==可以临时匹配,但存在类型混淆的风险。
解决方法
方案1:最小改动适配现有逻辑
排序前先对原数组做浅拷贝,生成新数组后再排序,保证传给setAlbums的是新的数组引用,让React可以正常触发重渲染;同时统一sort值的类型。
修改后的代码如下:
import { useEffect, useState } from "react"; const Test = (props) => { // 统一使用数字类型 const [sort, setSort] = useState(1); const [albums, setAlbums] = useState([ { "userId": 1, "id": 1, "title": "A letter", "photos": { "albumId": 1, "id": 1, "title": "accusamus beatae ad facilis cum similique qui sunt", "url": "https://via.placeholder.com/600/92c952", "thumbnailUrl": "https://via.placeholder.com/150/92c952" }, }, { "userId": 1, "id": 2, "title": "C letter", "photos": { "albumId": 2, "id": 1, "title": "accusamus beatae ad facilis cum similique qui sunt", "url": "https://via.placeholder.com/600/92c952", "thumbnailUrl": "https://via.placeholder.com/150/92c952" }, }, { "userId": 1, "id": 3, "title": "B letter", "photos": { "albumId": 3, "id": 1, "title": "accusamus beatae ad facilis cum similique qui sunt", "url": "https://via.placeholder.com/600/92c952", "thumbnailUrl": "https://via.placeholder.com/150/92c952" }, }, ]); useEffect(() => { // 先浅拷贝数组再排序,生成新的引用触发更新 const newAlbums = [...albums]; if (sort === 1) { newAlbums.sort((a, b) => a.id - b.id); } else if (sort === 2) { newAlbums.sort((a, b) => a.title.localeCompare(b.title)); } else if (sort === 3) { newAlbums.sort((a, b) => b.title.localeCompare(a.title)); } setAlbums(newAlbums); }, [sort]); return ( <> {/* 把选中值转成数字,保持类型统一 */} <select value={sort} onChange={e => setSort(Number(e.target.value))} > <option value='1'>val 1</option> <option value='2'>val 2</option> <option value='3'>val 3</option> </select> {albums.map((item, key) => { return ( <p key={key}>{item.title}</p> ) })} </> ) } export default Test;
方案2:优化逻辑(推荐)
排序结果属于派生状态,不需要单独存到state里,可以用useMemo基于原始数据和排序规则直接计算,避免状态冗余和不同步问题,代码更简洁:
import { useMemo, useState } from "react"; const Test = (props) => { const [sort, setSort] = useState(1); // 原始数据保持不变,不做修改 const [originalAlbums] = useState([ { "userId": 1, "id": 1, "title": "A letter", "photos": { "albumId": 1, "id": 1, "title": "accusamus beatae ad facilis cum similique qui sunt", "url": "https://via.placeholder.com/600/92c952", "thumbnailUrl": "https://via.placeholder.com/150/92c952" }, }, { "userId": 1, "id": 2, "title": "C letter", "photos": { "albumId": 2, "id": 1, "title": "accusamus beatae ad facilis cum similique qui sunt", "url": "https://via.placeholder.com/600/92c952", "thumbnailUrl": "https://via.placeholder.com/150/92c952" }, }, { "userId": 1, "id": 3, "title": "B letter", "photos": { "albumId": 3, "id": 1, "title": "accusamus beatae ad facilis cum similique qui sunt", "url": "https://via.placeholder.com/600/92c952", "thumbnailUrl": "https://via.placeholder.com/150/92c952" }, }, ]); // 依赖变化时自动计算排序后的列表,无需手动操作state const sortedAlbums = useMemo(() => { const newAlbums = [...originalAlbums]; if (sort === 1) { return newAlbums.sort((a, b) => a.id - b.id); } else if (sort === 2) { return newAlbums.sort((a, b) => a.title.localeCompare(b.title)); } else if (sort === 3) { return newAlbums.sort((a, b) => b.title.localeCompare(a.title)); } return newAlbums; }, [originalAlbums, sort]); return ( <> <select value={sort} onChange={e => setSort(Number(e.target.value))} > <option value='1'>val 1</option> <option value='2'>val 2</option> <option value='3'>val 3</option> </select> {sortedAlbums.map((item, key) => { return ( <p key={key}>{item.title}</p> ) })} </> ) } export default Test;
内容的提问来源于stack exchange,提问作者vimuth
相关产品推荐
相关产品推荐

