Я создаю новостную ленту, используя React и moment.js. Используя .map, я визуализирую элементы с заголовком и содержанием. Я хотел бы проверить, был ли элемент размещен в том же году и месяце, что и другой элемент. В этом случае я хочу скрыть заголовок второго элемента.
Пожалуйста, посмотри на мою скрипку
В настоящее время мой код отображает это:
новость первая
новость вторая
новость третья
новость четвертая
Since item one and two share the same month and year I would like to render like this instead:
новость первая
новость вторая
новость третья
новость четвертая
На основании этого ответа Я пытался найти узлы с повторяющимися именами классов, но я не совсем там:
let monthNodes = [...document.querySelectorAll('.months')];
let months = []
monthNodes.map(month => {
months.push(month.className)
})
const count = names =>
names.reduce((a, b) =>
Object.assign(a, {[b]: (a[b] || 0) + 1}), {})
const duplicates = dict =>
Object.keys(dict).filter((a) => dict[a] > 1)
console.info(count(months)) // {March_2018: 2, December_2017: 1, November_2017: 1}
console.info(duplicates(count(months))) // [ 'March_2018' ]
Может, я ошибаюсь и использование .map в первую очередь - плохая идея?
Обновлять
Спасибо за все отличные ответы, было трудно выбирать между ними, так как все они работают хорошо. Я должен принять ответ Дэвида Ибла, поскольку он первым представил рабочий пример.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


вы можете сделать это так: https://jsfiddle.net/2e27jvvh/
render() {
const arr = new Array();
return(
<div>
{this.state.news.map(item => {
const yearMonth = moment(item.date).format("MMMM YYYY");
const yearM = moment(item.date).format("MMMM_YYYY");
if (arr.indexOf(yearMonth) < 0){
arr.push(yearMonth);
return (
<div className = {'months ' + yearM}>
<h2>{yearMonth}</h2>
<p>{item.content}</p>
</div>
)
} else {
return (
<div className = {'months ' + yearM}>
<p>{item.content}</p>
</div>
)
}
})
}
</div>
)
}
Возможно, вам следует отсортировать элементы по году и месяцу раньше, чтобы обеспечить правильное поведение, даже если элементы не отсортированы изначально. Но это работает с вашим примером.
Предполагая, что структура данных выглядела так:
const data = [
{
date: 'March 2018',
item: {
title: 'news item one'
}
},
{
date: 'March 2018',
item: {
title: 'news item two'
}
},
{
date: 'September 2017',
item: {
title: 'news item two'
}
},
{
date: 'June 2017',
item: {
title: 'news item one'
}
}
]
Вы можете использовать Array.reduce:
const formatted = data.reduce((obj, item) => {
obj[item.date] = obj[item.date] ? [...obj[item.date], item] : [item]
return obj
}, {})
это загружается из API
1) Вы можете определить переменную previousYearMonth и сохранить в ней названия предыдущего года и месяца. Вы можете проверить, совпадают ли ваши текущие yearMonth и previousYearMonth, и показать / скрыть соответствующий тег:
render() {
return(
<div>
{this.state.news.map((item, index) => {
const yearMonth = moment(item.date).format("MMMM YYYY");
const yearM = moment(item.date).format("MMMM_YYYY");
const previousYearMonth = index
? moment(this.state.news[index - 1].date).format("MMMM YYYY")
: false;
return(
<div className = {'months ' + yearM}>
{
(yearMonth !== previousYearMonth) && (<h2>{yearMonth}</h2>)
}
<p>{item.content}</p>
</div>
)
})
}
</div>
)
}
Отметьте вилка твоей скрипки.
2) Другой способ предварительной обработки данных в методе constructor и установка свойства title следующим образом:
constructor(props) {
super(props);
const data = [
{content: 'news item one', date: '2018-03-02'},
{content: 'news item two', date: '2018-03-17'},
{content: 'news item two', date: '2017-09-21'},
{content: 'news item one', date: '2017-06-15'}
];
data.forEach((item, index) => {
const yearMonth = moment(item.date).format("MMMM YYYY");
const previousYearMonth = index
? moment(data[index - 1].date).format("MMMM YYYY")
: false;
if (yearMonth !== previousYearMonth) {
item.title = yearMonth
}
});
this.state = {
news: data
}
}
В этом случае в методе render вы можете просто проверить, что свойство title существует, и показать заголовок:
<div>
{
item.title && (<h2>{item.title}</h2>)
}
<p>{item.content}</p>
</div>
Проверьте рабочий пример с этим подходом.
будет ли это работать, если существует более одного набора дубликатов?
"Будет ли это работать, если существует более одного набора дубликатов?" Да, проверьте эту скрипку - jsfiddle.net/starw38a/1
если бы я мог принять более одного ответа, я бы принял это
Array.prototype.reduce() можно использовать для организации вашего Articles в форме Object с помощью year и month.
См. Практический пример ниже.
// News
class News extends React.Component {
// State.
state = {
news: [
{content: 'news item one -- march 2018', date: '2018-03-02'},
{content: 'news item two -- march 2018', date: '2018-03-17'},
{content: 'news item two -- sep 2017', date: '2017-09-21'},
{content: 'news item one -- june 2017', date: '2017-06-15'}
]
}
// Render.
render = () => (
<div>
{
Object.entries(this.organised()).map(([yearmonth, articles], i) => (
<div key = {i}>
<h3>{yearmonth}</h3>
{articles.map((article, j) => (
<div key = {j}>
{article.content}
</div>
))}
</div>
))
}
</div>
)
// Organised.
organised = () => this.state.news.reduce((total, article) => {
const key = moment(article.date).format('YYYY MMMM')
return {
...total,
[key]: total[key] && total[key].concat(article) || [article]
}
}, {})
}
// Mount.
ReactDOM.render(<News/>,document.getElementById('container'));<script src = "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.21.0/moment.min.js"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id = "container"></div>если бы я мог принять более одного ответа, я бы принял это
Учитывая, что этот список поступает из API, вы можете получить что-то подобное после получения данных.
const monthwise = this.state.news.reduce((res, obj) => {
const ym = moment(obj.date).format("YYYY-MM")
res[ym] = res[ym] || []
res[ym].push(obj);
return res;
}, {})
this.setState({monthwise})
Теперь в вашем render вы можете иметь:
render() {
return(
<div>
{Object.values(this.state.monthwise).map((items, index) => {
return(
<div key = {index}>
<h4>{moment(items[0].date).format("YYYY MMMM")}</h4>
{items.map((item, key) => <div key = {key}>{item.content}</div> )}
</div>
)
})}
</div>
)
}
если бы я мог принять более одного ответа, я бы принял это
к сожалению, нет, пожалуйста, посмотрите мою скрипку, чтобы узнать, как структурированы данные