【问题标题】:Checking if items within mapped array share values检查映射数组中的项目是否共享值
【发布时间】:2018-08-22 16:05:23
【问题描述】:

我正在使用 React 和 moment.js 构建新闻提要。使用.map 我正在渲染带有标题和内容的项目。我想检查一个项目是否与另一个项目在同一年和同一月发布。如果是这种情况,我想隐藏第二个项目的标题。

Please see my fiddle

目前我的代码呈现这个:

2018 年 3 月

新闻一

2018 年 3 月

新闻二

2017 年 9 月

新闻三

2017 年 6 月

新闻四

由于项目 1 和 2 共享相同的月份和年份,我想改为这样呈现:

2018 年 3 月

新闻一

新闻二

2017 年 9 月

新闻三

2017 年 6 月

新闻四

Based on this answer我试图找到具有重复类名的节点,但我不在那里:

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.log(count(months)) // {March_2018: 2, December_2017: 1, November_2017: 1}
console.log(duplicates(count(months))) // [ 'March_2018' ]

也许我用错了方法,首先使用 .map 是个坏主意?

更新

感谢所有出色的答案,很难在它们之间做出选择,因为它们都运行良好。我不得不接受 David Ibl 的回答,因为他首先提供了一个工作示例。

【问题讨论】:

    标签: javascript arrays reactjs time duplicates


    【解决方案1】:

    Array.prototype.reduce() 可以通过yearmonthObject 形式组织您的Articles

    请参阅下面的实际示例。

    JSFiddle

    // 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>

    【讨论】:

    • 如果我能接受多个答案,我会接受这个
    【解决方案2】:

    你可以这样做: 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>
    )
    }
    

    也许您应该根据之前的年份和月份对项目进行排序,以确保即使最初未对项目进行排序也能保持正确的行为。但这适用于您的示例。

    【讨论】:

      【解决方案3】:

      假设数据结构如下所示:

      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
      }, {})
      

      Example Codepen

      【讨论】:

      • 遗憾的是,请参阅我的小提琴以了解数据的结构
      • 这是从 api 获取的
      【解决方案4】:

      1) 您可以定义previousYearMonth 变量并将之前的年份和月份名称存储在那里。您可以检查您当前的yearMonthpreviousYearMonth 是否相同并显示/隐藏相应的标签:

      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>
        )
      }
      

      检查the fork of your fiddle

      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> 
      

      检查the fiddle with this approach

      【讨论】:

      • 如果有多个重复项,这会起作用吗?
      • "如果有多个重复项,这会起作用吗?"是的,检查这个小提琴 - jsfiddle.net/starw38a/1
      • 如果我能接受多个答案,我会接受这个
      【解决方案5】:

      考虑到此列表来自 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>
        )
      }
      

      Working jsfiddle

      【讨论】:

      • 如果我能接受多个答案,我会接受这个
      猜你喜欢
      • 1970-01-01
      • 2012-03-25
      • 1970-01-01
      • 1970-01-01
      • 2017-02-20
      • 1970-01-01
      • 1970-01-01
      • 2019-08-20
      • 1970-01-01
      相关资源
      最近更新 更多