React+TS+Redux Toolkit页面重载时SearchFail组件闪现问题求助
我有一个基于TS+React+Redux Toolkit的项目,会从接口获取数据存入items数组。在Main组件中,通过Redux Toolkit将filterMenu(字母/生日排序筛选)与filterSearch(搜索输入框)的数据传入filter状态,用于过滤数据。当过滤后的数组为空时,希望渲染SearchFail组件。
但页面重载时,SearchFail组件会短暂显示,之后才加载出数据。user切片状态包含isLoading、error和items,items初始状态为空数组,页面重载时数组长度为0,请问如何避免这种情况下SearchFail组件的加载?
问题核心是初始加载阶段isLoading为false,同时items为空,导致逻辑误判为“过滤后无数据”。可以通过以下两种方式解决:
方式一:调整初始加载状态,优先显示骨架屏
修改user-slice.ts的初始状态,将isLoading设为true,同时从localStorage读取缓存数据(你已经在请求成功后把数据存在localStorage),让初始状态更合理:
// user-slice.ts const initialState: IItmes = { // 优先从localStorage读取缓存,避免初始为空 items: JSON.parse(localStorage.getItem('items') || '[]'), // 初始设为加载中,页面刚打开时直接显示骨架屏 isLoading: true, error: '' }
这样页面重载时,isLoading初始为true,会先渲染<SkeletonItem>,直到接口请求完成(或读取缓存后)才会切换到数据列表或SearchFail,避免了初始空数组触发SearchFail的问题。
方式二:在Main组件中增加“首次加载完成”的判断逻辑
如果不想修改初始isLoading,可以在Main组件中添加一个状态,标记是否已经完成第一次接口请求:
// Main.tsx const Main: React.FC = () => { const { items, isLoading, error } = useAppSelector(state => state.user) const { filterSearch, filterMenu, filter } = useAppSelector(state => state.filter) const dispatch = useAppDispatch() // 新增状态:标记是否完成首次数据加载 const [isFirstLoadComplete, setIsFirstLoadComplete] = useState(false) // 原filterItems逻辑不变 const filterItems = filterMenu === 'alphabet' ? items.slice().sort((a,b) => a.firstName.localeCompare(b.firstName)).filter(item => { if (filterSearch === '') { return item } else { return item.firstName.toLowerCase().includes(filterSearch.toLowerCase()) || item.lastName.toLowerCase().includes(filterSearch.toLowerCase()) || item.userTag.toLowerCase().includes(filterSearch.toLowerCase()) } }) : ( items.slice().sort((a,b) => a.birthday.localeCompare(b.birthday)).filter(item => { if (filterSearch === '') { return item } else { return item.firstName.toLowerCase().includes(filterSearch.toLowerCase()) || item.lastName.toLowerCase().includes(filterSearch.toLowerCase()) || item.userTag.toLowerCase().includes(filterSearch.toLowerCase()) } }) ) useEffect(() => { const fetchData = async () => { await dispatch(getUsers(filter)).unwrap() setIsFirstLoadComplete(true) } fetchData() const timer = setInterval(() => { dispatch(getUsers(filter)) }, 50000) return () => clearInterval(timer) }, [filter]) return ( <main className='main'> {error && <ErrorPage />} {isLoading ? <SkeletonItem count={6} /> : // 只有首次加载完成,且过滤后为空时,才显示SearchFail (isFirstLoadComplete && filterItems.length === 0) ? <SearchFail /> : <div className="main__wrapper"> {filterItems.map(item => ( <MainItem data={item} key={item.id} /> ))} </div> } </main> ); };
这种方式通过isFirstLoadComplete状态,确保只有在首次请求完成后,才会判断是否显示SearchFail,避免初始空数组的误判。
你的filterItems中排序后的过滤逻辑完全重复,可以提取成单独函数,减少代码冗余:
// Main.tsx中新增过滤函数 const filterUserItems = (items: IUser[]) => { if (filterSearch === '') return items return items.filter(item => item.firstName.toLowerCase().includes(filterSearch.toLowerCase()) || item.lastName.toLowerCase().includes(filterSearch.toLowerCase()) || item.userTag.toLowerCase().includes(filterSearch.toLowerCase()) ) } // 简化filterItems const filterItems = filterMenu === 'alphabet' ? filterUserItems([...items].sort((a,b) => a.firstName.localeCompare(b.firstName))) : filterUserItems([...items].sort((a,b) => a.birthday.localeCompare(b.birthday)))
(注:原代码中生日排序的逻辑有误,a.birthday.localeCompare(b.firstName)应改为a.birthday.localeCompare(b.birthday))
内容的提问来源于stack exchange,提问作者Anan Poinf

