You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 03:37:00