英文:
In Javascript, how do I get the serial number of an item in Array?
问题
这可能是一个非常愚蠢的问题。
我从一个端点接收到以下格式的数据。我想要为每个预测分配一个序列号,考虑到前一类别中的预测。
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);
// 其他代码
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>
);
});
};
期望:
我希望增加和减少这个序列号,以便在按键向上和向下时可以突出显示项目。因此,我希望预测的序号是0、1、2、3,...n(其中n = 预测数量减1)。
因此,在上面的示例中,Office
预测将具有 serialNum
0、1、2,而 Home
预测将具有 serialNum
3、4、5,依此类推。
预测不一定每个类别都是3个,它们可以是任何数量。
对于任何建议,我都表示感激。
英文:
This is perhaps a very silly question.
I am receiving data from an endpoint in the following format. I want to assign the prediction a serial number taking into account the predictions in the previous category.
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"
}
]
}
];
In React, I am doing the following to render the predictions to the customer based on their query:
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>
);
});
};
Expectation:
I want to increment and decrement this serial number so that I can highlight the item on keyup and down. Hence, I want the predictions to be numbered 0, 1, 2, 3,...n ( where n = number of predictions minus 1)
So in the above example, the Office
predictions would have serialNum
0, 1, 2 and Home
predictions would have serialNum
3, 4, 5 respectively.
The predictions won't always be 3 per category, they can be any number.
Any advice is appreciated.
答案1
得分: 0
我想我会在这里提供我采用的解决方案。事后看来,这不是适用于超过2个部分的解决方案。所以如果你有更好的解决方案,请告诉我。
所以我所做的是使用以下逻辑找到下一部分项目的起始索引:
- 获取预测的总数(在每个类别中)=
totalCount
- 获取当前类别中的预测数量 =
currentCategroryCount
- 从
totalCount
减去currentCategroryCount
。这将有助于识别下一类别预测的起始索引。 - 将该值传递给
showPredictions()
- 将此值添加到
index
const [highlighted, setHighlighted] = useState(-1);
const showPredictions = (predictions, categoryIndex, numberOfItemsRendered = 0) => {
return (
<ul role="listbox">
{predictions.map((prediction, index) => {
const serialNum = index + numberOfItemsRendered;
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 totalPredictions = getTotalPredictions(items);
const currentCategroryCount = getCurrentCategoryCount(category);
const otherSectionCategoryCount = totalPredictions - currentCategroryCount;
// 我们不需要为第一个部分执行上述计算:
const numOfItemsRendered = catIndex > 0 ? otherSectionCategoryCount : 0
const itemList = showPredictions(category.predictions, catIndex, numberOfItemsRendered);
return (
<div>
{categoryTitle}
{itemList}
</div>
);
});
};
英文:
I thought I'll update here with the solution I went with. In hindsight, this isn't the solution for more than 2 sections. So if you have a better solution, do tell.
So what I did was find the starting index for the next section of items with the following logic:
- Get the total number of predictions (in every category) =
totalCount
- Get the number of predictions in the current category =
currentCategroryCount
- Subtract
currentCategroryCount
fromtotalCount
. This will help in identifying the starting index of the next category of predictions. - Pass the value to
showPredictions()
- Add this value to the
index
const [highlighted, setHighlighted] = useState(-1);
const showPredictions = (predictions, categoryIndex, numberOfItemsRendered = 0) => {
return (
<ul role="listbox">
{predictions.map((prediction, index) => {
const serialNum = index + numberOfItemsRendered;
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 totalPredictions = getTotalPredictions(items);
const currentCategroryCount = getCurrentCategoryCount(category);
const otherSectionCategoryCount = totalPredictions - currentCategroryCount;
// we don't need to the the above calculations for the first section:
const numOfItemsRendered = catIndex > 0 ? otherSectionCategoryCount : 0
const itemList = showPredictions(category.predictions, catIndex, numberOfItemsRendered);
return (
<div>
{categoryTitle}
{itemList}
</div>
);
});
};
答案2
得分: 0
基于您的评论,您想要解决的问题是:
> 允许用户使用箭头键在分成三个类别的列表中更改所选项目。
我将使用三个组件:
<Categories>
用于渲染类别数组<Category>
用于渲染单个类别(预测数组)<Prediction>
用于渲染单个预测。
避免使用数组索引作为ID
这并不是不可能,而是会导致以后遇到奇怪的错误。尝试使用单独的ID属性来唯一标识每个预测。对于类别也是如此。
例如:
const prediction = {
id: 1,
name: "Hello, world!"
};
const category = {
id: 1,
name: "First category",
predictions: [prediction]
};
扁平化层次以简化操作
您需要知道当前选择的预测之前和之后的预测。与其使用涉及类别的复杂逻辑,不如将它们完全排除在外!您可以使用内置的JS函数 flatMap
来扁平化您的类别,以便得到一个用于排序的大型预测数组。然后,使用React的内置 useState()
来存储当前选择的索引。
const Categories = ({ categories }) => {
const allItems = categories.flatMap(x => x.predictions);
const [selectedIndex, setSelectedIndex] = useState(0);
const selectedItem = allItems[selectedIndex];
return (<div>
{categories.map(x => <Category key={x.id} name={x.name} predictions={x.predictions} />}
</div>);
};
告诉子组件哪个预测被选中
Categories
目前是唯一具有选择知识的组件,因此我们需要一种方法告诉子组件(渲染预测的组件)它们是否被选中。将当前选择的ID沿着链传递:
// <Categories>
{categories.map(x => <Category
key={x.id}
...
selectedId={selectedItem.id} />)}
ID 就足以标识单个预测。不要传递 selectedIndex
,您需要 allItems
和 categories
来理解它,而将所有这些东西传递下去没有意义。
告诉您的预测如何突出显示
如果您将 selectedId
沿着链传递,这将非常简单。在这一点上,您可以更改默认的选择ID 以确保正确的预测被选中。
const Prediction = ({ id, name, selectedId }) => {
const isSelected = id === selectedId;
return <li className={isSelected ? "highlight" : ""}>{name}</li>;
};
将 onKeyUp
事件绑定到更新状态
在您的 <Categories>
中绑定 onKeyUp
事件,并将其连接到更改 selectedIndex
。为了清晰起见,我省略了边界检查,但您绝对需要它以避免在数组的两端之一溢出。
const Categories = ({ categories }) => {
...
const [selectedIndex, setSelectedIndex] = useState(0);
const selectedItem = allItems[selectedIndex];
const onKeyUp = (event) => {
switch (event.code) {
case "ArrowUp":
setSelectedIndex(selectedIndex - 1);
break;
case "ArrowDown":
setSelectedIndex(selectedIndex + 1);
break;
}
};
return (<div onKeyUp={onKeyUp} tabIndex="-1">
...
</div>);
};
tabIndex="-1"
是必需的,以便 div
可以接收键盘事件。
将所有内容整合在一起
这是一个完整的示例,包括边界检查:
<!-- begin snippet: js hide: false console: false babel: true -->
<!-- language: lang-js -->
// Example data here.
const teeShirts = [
{ id: 1, name: "Black" },
{ id: 2, name: "Red" },
{ id: 3, name: "Blue" },
];
const accessories = [
{ id: 4, name: "Cool Cap" },
{ id: 5, name: "Fancy Tie" },
{ id: 6, name: "Medallion" },
];
const countries = [
{ id: 7, name: "United Kingdom" },
{ id: 8, name: "United States" },
{ id: 9, name: "Australia" },
];
const categories = [
{ id: 1, name: "T-Shirts", predictions: teeShirts },
{ id: 2, name: "Accessories", predictions: accessories },
{ id: 3, name: "Countries", predictions: countries },
];
const Categories = ({ categories }) => {
const allItems = categories.flatMap((x) => x.predictions);
const [selectedIndex, setSelectedIndex] = React.useState(0);
const selectedItem = allItems[selectedIndex];
const onKeyUp = (event) => {
switch (event.code) {
case "ArrowUp":
setSelectedIndex(Math.max(0, selectedIndex - 1));
break;
case "ArrowDown":
setSelectedIndex(
Math.min(allItems.length - 1, selectedIndex + 1)
);
break;
}
};
return (
<div onKeyUp={onKeyUp} tabIndex={-1}>
{categories.map((category) => (
<Category
key={category.name}
id={category.id}
name={category.name}
predictions={category.predictions}
selectedId={selectedItem.id}
/>
))}
</div>
);
};
const Category = ({ name, predictions, selectedId }) => {
return (
<div>
{name}
<ul role="listbox">
{predictions.map((prediction) => (
<Prediction
key={prediction.name}
id={prediction.id}
name={prediction.name}
selectedId={selectedId}
/>
))}
</ul>
</div>
);
};
const Prediction = ({ id, name, selectedId }) => {
const isSelected = id === selected
<details>
<summary>英文:</summary>
Based on your comments, the problem you want to solve is:
> Allow the user to use the arrow keys to change which item is selected in a list, which is split across three categories.
I'm going to be using three components:
- `<Categories>` to render an array of categories
- `<Category>` to render a single category (array of predictions)
- `<Prediction>` to render a single prediction.
#### Avoid using the array index as an ID
It's not that it's not possible, it's that it sets you up to run into strange bugs later on. Try and uniquely identify each prediction with an ID property of its own. Do the same with your categories.
For example:
```js
const prediction = {
id: 1,
name: "Hello, world!"
};
const category = {
id: 1,
name: "First category",
predictions: [prediction]
};
Flatten the hierarchy to make things easier
You need to know which predictions are before and after the currently-selected prediction. Rather than messing about with some convoluted logic involving categories, take them out of the equation entirely!
You can use the built-in JS function flatMap
to flatten your categories so you have a big array of predictions to use for your ordering. Then, use React's built-in useState()
to store the currently-selected index.
const Categories = ({ categories }) => {
const allItems = categories.flatMap(x => x.predictions);
const [selectedIndex, setSelectedIndex] = useState(0);
const selectedItem = allItems[selectedIndex];
return (<div>
{categories.map(x => <Category key={x.id} name={x.name} predictions={x.predictions} />}
</div>);
};
Tell your child components which prediction is selected
Categories
is currently the only component with any knowledge of the selection, so we need a way to tell the child components (the ones that render predictions) whether they're selected or not.
Pass the ID of the current selection down the chain:
// <Categories>
{categories.map(x => <Category
key={x.id}
...
selectedId={selectedItem.id} />)}
The ID is all you need to identify a single prediction. You don't want to pass selectedIndex
, you need allItems
and categories
to make sense of it and there's no point passing all that stuff down.
Tell your predictions how to highlight
If you're passing selectedId
down the chain this is nice and easy. At this point you can change the default selection ID to confirm the right predictions are being selected.
const Prediction = ({ id, name, selectedId }) => {
const isSelected = id === selectedId;
return <li className={isSelected ? "highlight" : ""}>{name}</li>;
};
Bind the onKeyUp
event to update the state
Bind the onKeyUp
event on your <div>
in <Categories>
and wire it up to change selectedIndex
. I've omitted bounds checking for clarity but you definitely want that to avoid going off either end of the array.
const Categories = ({ categories }) => {
...
const [selectedIndex, setSelectedIndex] = useState(0);
const selectedItem = allItems[selectedIndex];
const onKeyUp = (event) => {
switch (event.code) {
case "ArrowUp":
setSelectedIndex(selectedIndex - 1);
break;
case "ArrowDown":
setSelectedIndex(selectedIndex + 1);
break;
}
};
return (<div onKeyUp={onKeyUp} tabIndex="-1">
...
</div>);
};
tabIndex="-1"
is needed so that the div
can receive keyboard events.
Putting it all together
Here's a full example, including bounds checking:
<!-- begin snippet: js hide: false console: false babel: true -->
<!-- language: lang-js -->
// Example data here.
const teeShirts = [
{ id: 1, name: "Black" },
{ id: 2, name: "Red" },
{ id: 3, name: "Blue" },
];
const accessories = [
{ id: 4, name: "Cool Cap" },
{ id: 5, name: "Fancy Tie" },
{ id: 6, name: "Medallion" },
];
const countries = [
{ id: 7, name: "United Kingdom" },
{ id: 8, name: "United States" },
{ id: 9, name: "Australia" },
];
const categories = [
{ id: 1, name: "T-Shirts", predictions: teeShirts },
{ id: 2, name: "Accessories", predictions: accessories },
{ id: 3, name: "Countries", predictions: countries },
];
const Categories = ({ categories }) => {
const allItems = categories.flatMap((x) => x.predictions);
const [selectedIndex, setSelectedIndex] = React.useState(0);
const selectedItem = allItems[selectedIndex];
const onKeyUp = (event) => {
switch (event.code) {
case "ArrowUp":
setSelectedIndex(Math.max(0, selectedIndex - 1));
break;
case "ArrowDown":
setSelectedIndex(
Math.min(allItems.length - 1, selectedIndex + 1)
);
break;
}
};
return (
<div onKeyUp={onKeyUp} tabIndex={-1}>
{categories.map((category) => (
<Category
key={category.name}
id={category.id}
name={category.name}
predictions={category.predictions}
selectedId={selectedItem.id}
/>
))}
</div>
);
};
const Category = ({ name, predictions, selectedId }) => {
return (
<div>
{name}
<ul role="listbox">
{predictions.map((prediction) => (
<Prediction
key={prediction.name}
id={prediction.id}
name={prediction.name}
selectedId={selectedId}
/>
))}
</ul>
</div>
);
};
const Prediction = ({ id, name, selectedId }) => {
const isSelected = id === selectedId;
return <li className={isSelected ? "highlight" : ""}>{name}</li>;
};
// This is just bootstrapping so the example works
const App = () => {
return <Categories categories={categories} />;
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
<!-- language: lang-css -->
.highlight {
color: red;
font-weight: bold;
}
<!-- language: lang-html -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
<div id="root"></div>
<!-- end snippet -->
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论