React中使用slice函数渲染筛选器组件时出现空白页问题
问题:React筛选器组件使用slice截取数组后页面空白
开发React筛选器组件,需求是仅显示指定数量的筛选项,其余放入下拉列表。传入的filters数组包含name、id和imgurl字段。使用slice函数截取数组前limit_items项渲染时页面空白(控制台可正常打印截取后的数组),但直接渲染原filters数组时功能正常。
问题代码
组件代码
import React,{useState,useEffect} from 'react'; import './Filters.css' import MultipleSelectCheckmarks from '../MultipleSelectCheckmarks/MultipleSelectCheckmarks' export default function Filters({filters,randomize,limit_items,onChange,space='100px'}) { const [activeFilters,setActiveFilter] = useState([]); const [deActiveFilters,setDeactiveFilter]= useState([]); // this updates the activeFilters and deActiveFilters arrays when filter-item change its state function setFilterState(id,state){ if(state==0){ setActiveFilter( activeFilters.filter((f)=>f!==id )); setDeactiveFilter( deActiveFilters=>[...deActiveFilters,id].sort((e,f)=>e-f) ); } else{ setDeactiveFilter( deActiveFilters.filter((f)=>f!==id )); setActiveFilter( activeFilters=>[...activeFilters,id].sort((e,f)=>e-f) ); } } // render the filter-items let filterItm = function filter_item(name,img,position,count) { const [filterState,TougleState] = useState(0); //store each filter item state const Tougle = event => { //create a event to triger when filter item get clicked. tougle the state and tougle filter-active class to add/remove styling if(filterState == 0) { TougleState(1); event.currentTarget.classList.add("filter-active"); setFilterState(count, 1); } else { TougleState(0); event.currentTarget.classList.remove("filter-active"); setFilterState(count,0); } } //render the filter item return( <div className="filter-item" onClick={Tougle} key={count} style={{transform:`translateX( calc( ${space} * ${position} ) )`}} id={'filter-'+count}> <div className="filter-item-icon" style={{backgroundImage:`url(${img})`}}></div> <p className="filter-item-name">{name}</p> </div> ); } // run only once and set all the filters as deactive filters if(deActiveFilters.length == 0) { filters.map((e) => setDeactiveFilter(deActiveFilters => [...deActiveFilters, e.id].sort((e,f) => e-f))); } // if state change on any filter item change the order of items useEffect(() => { onChange(activeFilters); if (randomize) { //if randomize is set to true then order as items randomly activeFilters.sort(() => Math.random() > 0.5 ? -1 : 1); deActiveFilters.sort(() => Math.random() > 0.5 ? -1 : 1); } // set each filter item position let pos = 0; activeFilters.map((f) => { document.getElementById('filter-'+f).style.transform = `translateX( calc( 100px * ${pos++} ) )`; }); deActiveFilters.map((f) => { document.getElementById('filter-'+f).style.transform = `translateX( calc( 100px * ${pos++} ) )`; }); },[activeFilters,deActiveFilters]); //render the filter section const slicedfilters = filters.slice(0,limit_items); //console.log(filters.slice(0,limit_items)) //console.log(filters) return( <> <div className="filter-container"> { //filters.map((e)=>filterItm(e.name,e.imgurl,e.id,e.id)) slicedfilters.map((e)=>filterItm(e.name,e.imgurl,e.id,e.id)) //this line is the problem } <div className="filter-item" style={{width:'120px',left:'unset',right:'0'}}> <MultipleSelectCheckmarks></MultipleSelectCheckmarks> </div> </div> </> ) }
传入的filters数组
const cardsjson= [ {'id':0, 'name':'test0','imgurl':url}, {'id':1,'name':'test1','imgurl':url}, {'id':2,'name':'test2','imgurl':url}, {'id':3,'name':'test3','imgurl':url}, {'id':4,'name':'test4','imgurl':url}, {'id':5,'name':'test5','imgurl':url}, {'id':6,'name':'test6','imgurl':url}, {'id':7,'name':'test7','imgurl':url}, {'id':8,'name':'test8','imgurl':url}, {'id':9,'name':'test9','imgurl':url}, {'id':10,'name':'test10','imgurl':url}, {'id':11,'name':'test1','imgurl':url}, {'id':12,'name':'test2','imgurl':url}, {'id':13,'name':'test3','imgurl':url}, {'id':14,'name':'test4','imgurl':url}, {'id':15,'name':'test5','imgurl':url}, {'id':16,'name':'test6','imgurl':url}, {'id':17,'name':'test7','imgurl':url}, ];
组件调用代码
<Filters filters={cardsjson} randomize={false} onChange={getdata} limit_items={10} ></Filters>
问题分析
- 组件函数体内直接调用setState:
if(deActiveFilters.length == 0)这段逻辑在每次组件重渲染时都会执行,调用setDeactiveFilter会触发新的重渲染,导致无限循环;同时初始化时会添加原filters所有id,但渲染的是slice后的项,后续useEffect中尝试获取不存在的DOM元素(如id=10及以上),引发报错导致页面空白。 - 内部定义函数组件:
filterItm是在组件内部定义的函数组件,每次重渲染都会重新创建,导致子组件状态丢失或渲染异常。 - 直接操作DOM:使用
document.getElementById修改样式不符合React数据驱动的范式,容易引发DOM不存在的错误。 - 状态数组原地修改:
activeFilters.sort()和deActiveFilters.sort()是原地修改数组,React无法正确检测状态变化,导致渲染异常。
修复后的代码
import React, { useState, useEffect } from 'react'; import './Filters.css'; import MultipleSelectCheckmarks from '../MultipleSelectCheckmarks/MultipleSelectCheckmarks'; // 独立的筛选项子组件 const FilterItem = ({ name, img, position, id, isActive, onToggle }) => { return ( <div className={`filter-item ${isActive ? 'filter-active' : ''}`} onClick={() => onToggle(id)} key={id} style={{ transform: `translateX( calc( 100px * ${position} ) )` }} > <div className="filter-item-icon" style={{ backgroundImage: `url(${img})` }}></div> <p className="filter-item-name">{name}</p> </div> ); }; export default function Filters({ filters, randomize, limit_items, onChange, space = '100px' }) { const [activeFilters, setActiveFilter] = useState([]); // 初始化时截取需要显示的筛选项 const [displayFilters] = useState(filters.slice(0, limit_items)); // 初始化时通知父组件初始状态 useEffect(() => { onChange(activeFilters); }, []); // 切换筛选项状态 const toggleFilter = (id) => { setActiveFilter(prev => { const newActive = prev.includes(id) ? prev.filter(f => f !== id) : [...prev, id].sort((a, b) => a - b); onChange(newActive); return newActive; }); }; // 计算每个筛选项的位置:活跃项在前,非活跃项在后 const getFilterPosition = (id) => { let pos = 0; // 先统计活跃项的位置 for (const activeId of activeFilters) { if (activeId === id) return pos; pos++; } // 再统计非活跃项的位置 for (const filter of displayFilters) { if (!activeFilters.includes(filter.id)) { if (filter.id === id) return pos; pos++; } } return pos; }; // 随机排序逻辑:仅对活跃项排序 useEffect(() => { if (randomize) { setActiveFilter(prev => { const shuffled = [...prev].sort(() => Math.random() > 0.5 ? -1 : 1); onChange(shuffled); return shuffled; }); } }, [randomize]); return ( <> <div className="filter-container"> {displayFilters.map(filter => ( <FilterItem key={filter.id} name={filter.name} img={filter.imgurl} position={getFilterPosition(filter.id)} id={filter.id} isActive={activeFilters.includes(filter.id)} onToggle={toggleFilter} /> ))} <div className="filter-item" style={{ width: '120px', left: 'unset', right: '0' }}> <MultipleSelectCheckmarks /> </div> </div> </> ); }
修复说明
- 独立子组件:将
FilterItem抽成独立组件,避免每次重渲染重新创建,保证状态稳定。 - 状态推导:不再单独维护
deActiveFilters,通过activeFilters推导非活跃项,减少状态冗余。 - 数据驱动样式:通过
isActive属性控制filter-active类,移除直接操作DOM的代码。 - 正确初始化状态:用
useState直接初始化显示的筛选项,避免循环调用setState。 - 避免原地修改:所有状态更新都创建新数组,确保React能检测到状态变化。
- 位置计算逻辑:通过函数动态计算每个筛选项的位置,替代DOM操作。
内容的提问来源于stack exchange,提问作者Sakindu Ransindu
相关产品推荐
相关产品推荐

