React中如何为跨分类预测项分配全局连续序列号?
问题
从接口获取如下格式的数据,需要为每个预测项分配全局连续递增的序列号(0,1,2,...,n,n为总预测项数-1),实现通过上下键控制高亮的功能。当前代码中每个分类内的序列号从0开始,无法满足全局连续的需求。
接口数据示例:
const items = [{ "category": "Office", "predictions": [ { "id": "2599", "value": "Printer", "type": "OFF" }, { "id": "2853", "value": "Camera", "type": "OFF" }, { "id": "2202", "value": "Keyboard", "type": "OFF" } ] }, { "category": "Home", "predictions": [ { "id": "2899", "value": "Television", "type": "ELEC" }, { "id": "2853", "value": "Microwave", "type": "ELEC" }, { "id": "2732", "value": "Washing Machine", "type": "ELEC" } ] } ];
当前React渲染代码:
const [highlighted, setHighlighted] = useState(-1); // other code const showPredictions = (predictions, categoryIndex) => { return ( <ul role="listbox"> {predictions.map((prediction, index) => { const serialNum = index; const isHighlighted = highlighted === serialNum; return ( <li onMouseEnter={() => handleSuggestionMouseEnter(serialNum)} className={isHighlighted ? 'highlight' : ''} > {prediction.value} </li> ); })} </ul> ); }; const showPredictionsByCategory = (items) => { return items.map((category, catIndex) => { const categoryTitle = renderTitleText(category); const itemList = showPredictions(category.predictions, catIndex); return ( <div> {categoryTitle} {itemList} </div> ); }); };
预期效果:Office分类的预测项序列号为0、1、2,Home分类的为3、4、5,实现全局连续的序列号用于上下键高亮控制。
解决方法
方案1:预计算全局偏移量
在渲染时动态计算每个分类的起始偏移量(即当前分类之前所有分类的预测项总数),然后每个预测项的序列号为偏移量 + 自身索引。
修改后的代码:
const showPredictions = (predictions, startOffset) => { return ( <ul role="listbox"> {predictions.map((prediction, index) => { const serialNum = startOffset + index; const isHighlighted = highlighted === serialNum; return ( <li onMouseEnter={() => handleSuggestionMouseEnter(serialNum)} className={isHighlighted ? 'highlight' : ''} > {prediction.value} </li> ); })} </ul> ); }; const showPredictionsByCategory = (items) => { let startOffset = 0; return items.map((category) => { const categoryTitle = renderTitleText(category); const itemList = showPredictions(category.predictions, startOffset); // 更新偏移量,加上当前分类的预测项数量 startOffset += category.predictions.length; return ( <div> {categoryTitle} {itemList} </div> ); }); };
方案2:扁平化数据后渲染
先将所有分类的预测项合并成一个全局数组(可保留分类信息用于渲染标题),直接基于扁平化数组生成全局连续序列号。这种方式更直观,也便于后续上下键控制逻辑的编写。
// 扁平化数据,同时保留分类标记 const flattenedPredictions = useMemo(() => { let result = []; items.forEach(category => { // 添加分类标题项(按需保留) result.push({ type: 'category', title: category.category }); // 添加该分类下的所有预测项 result.push(...category.predictions.map(pred => ({ ...pred, type: 'item' }))); }); return result; }, [items]); // 渲染扁平化后的列表 const renderFlattenedList = () => { return ( <div> {flattenedPredictions.map((item, serialNum) => { if (item.type === 'category') { return <div key={`cat-${serialNum}`}>{renderTitleText({category: item.title})}</div>; } const isHighlighted = highlighted === serialNum; return ( <li key={`item-${item.id}`} onMouseEnter={() => handleSuggestionMouseEnter(serialNum)} className={isHighlighted ? 'highlight' : ''} > {item.value} </li> ); })} </div> ); };
方案3:通过分类索引计算全局序列号
如果不想调整现有渲染结构,可通过分类索引计算当前分类之前的总项数,作为起始偏移量。
// 计算当前分类之前的所有预测项总数 const getPreviousItemsCount = (items, currentCatIndex) => { return items.slice(0, currentCatIndex).reduce((total, cat) => { return total + cat.predictions.length; }, 0); }; const showPredictions = (predictions, categoryIndex) => { const startOffset = getPreviousItemsCount(items, categoryIndex); return ( <ul role="listbox"> {predictions.map((prediction, index) => { const serialNum = startOffset + index; const isHighlighted = highlighted === serialNum; return ( <li onMouseEnter={() => handleSuggestionMouseEnter(serialNum)} className={isHighlighted ? 'highlight' : ''} > {prediction.value} </li> ); })} </ul> ); }; // 原showPredictionsByCategory保持不变,传入分类索引即可 const showPredictionsByCategory = (items) => { return items.map((category, catIndex) => { const categoryTitle = renderTitleText(category); const itemList = showPredictions(category.predictions, catIndex); return ( <div> {categoryTitle} {itemList} </div> ); }); };
补充:上下键控制高亮逻辑
有了全局序列号后,可通过以下代码实现上下键循环高亮:
const handleKeyDown = (e) => { const totalItems = items.reduce((sum, cat) => sum + cat.predictions.length, 0); if (e.key === 'ArrowDown') { e.preventDefault(); setHighlighted(prev => { const next = prev + 1; return next >= totalItems ? 0 : next; }); } else if (e.key === 'ArrowUp') { e.preventDefault(); setHighlighted(prev => { const next = prev - 1; return next < 0 ? totalItems - 1 : next; }); } }; // 在组件最外层绑定键盘事件 <div onKeyDown={handleKeyDown} tabIndex="0"> {showPredictionsByCategory(items)} </div>
内容的提问来源于stack exchange,提问作者Shelikestocode
相关产品推荐
相关产品推荐

