Порефакторим...

Для начала, удалите вовсе компонент <Comments />const Comments... соответственно).

Далее, давайте представим: у наших новостей появляются какие-то дополнительные поля, пользователь начинает взаимодействовать с ними, например "пометить как прочитанное" и так далее. Нам было бы удобно, чтобы каждая новость была представлена отдельным компонентом.

Задача: <News /> должен рендерить список компонентов <Article />. Каждый компонент <Article /> должен получать соответствующие данные, например: первый экземпляр получит первый элемент массива, второй - второй и так далее.

То есть, раньше в map мы возвращали JSX-разметку. Но мы так же можем возвращать и компонент.

Попробуйте сами, а потом смотрите решение ниже.

Подсказка #1: if-else нашего компонента <News />

if (data.length) {
  newsTemplate = data.map(function(item) {
    return <Article key={item.id} data={item}/>
  })
} else {
  newsTemplate = <p>К сожалению новостей нет</p>
}

Подсказка #2 (по сути решение задачи): компонент <Article />

class Article extends React.Component {
  render() {
    const { author, text } = this.props.data
    return (
      <div className="article">
        <p className="news__author">{author}:</p>
        <p className="news__text">{text}</p>
      </div>
    )
  }
}

Что любопытно, больше не изменилось ни-че-го.

Добавьте заголовок "Новости" в <App /> перед компонентом <News />

const App = () => {
  return (
    <React.Fragment>
      <h3>Новости</h3>
      <News data={myNews}/>
    </React.Fragment>
  )
}

Добавьте красоты (CSS) по вкусу, либо возьмите мой вариант:

.none {
  display: none;
}

body {
  background: rgba(0, 102, 255, 0.38);
  font-family: sans-serif;
}

p {
  margin: 0 0 5px;
}

.article {
  background: #FFF;
  border: 1px solid rgba(0, 89, 181, 0.82);
  width: 600px;
  margin: 0 0 5px;
  box-shadow: 2px 2px 5px -1px rgb(0, 81, 202);
  padding: 3px 5px;
}

.news__author {
  text-decoration: underline;
  color: #007DDC;
}
.news__count {
  margin: 10px 0 0 0;
  display: block;
}

С новыми стилями, код сценария выглядит так:

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>React [RU] Tutorial v2</title>
    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>

    <style>
      .none {
        display: none;
      }

      body {
        background: rgba(0, 102, 255, 0.38);
        font-family: sans-serif;
      }

      p {
        margin: 0 0 5px;
      }

      .article {
        background: #FFF;
        border: 1px solid rgba(0, 89, 181, 0.82);
        width: 600px;
        margin: 0 0 5px;
        box-shadow: 2px 2px 5px -1px rgb(0, 81, 202);
        padding: 3px 5px;
      }

      .news__author {
        text-decoration: underline;
        color: #007DDC;
      }
      .news__count {
        margin: 10px 0 0 0;
        display: block;
      }
    </style>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/babel">

      const myNews = [
        {
          id: 1, // добавили id
          author: 'Саша Печкин',
          text: 'В четверг, четвертого числа...'
        },
        {
          id: 2,
          author: 'Просто Вася',
          text: 'Считаю, что $ должен стоить 35 рублей!'
        },
        {
          id: 3,
          author: 'Max Frontend',
          text: 'Прошло 2 года с прошлых учебников, а $ так и не стоит 35'
        },
        {
          id: 4,
          author: 'Гость',
          text: 'Бесплатно. Без смс, про реакт, заходи - https://maxpfrontend.ru'
        }
      ];

      class Article extends React.Component {
        render() {
          const { author, text } = this.props.data
          return (
            <div className="article">
              <p className="news__author">{author}:</p>
              <p className="news__text">{text}</p>
            </div>
          )
        }
      }

      class News extends React.Component {
        render() {
          const { data } = this.props
          let newsTemplate

          if (data.length) {
            newsTemplate = data.map(function(item) {
              return <Article key={item.id} data={item}/>
            })
          } else {
            newsTemplate = <p>К сожалению новостей нет</p>
          }

          return (
            <div className="news">
              {newsTemplate}
              {
                data.length ? <strong className={'news__count'}>Всего новостей: {data.length}</strong> : null
              }
            </div>
          );
        }
      }

      const App = () => {
        return (
          <React.Fragment>
            <h3>Новости</h3>
            <News data={myNews}/>
          </React.Fragment>
        )
      }

      ReactDOM.render(
        <App />,
        document.getElementById('root')
      );

    </script>

  </body>
</html>

В целом меня устраивает почти все. Осталось немного отполировать метод render компонента <News />. Правило следующее: стараемся в render держать как можно меньше кода, чтобы его было легко читать вашим коллегам. Для этого, мы newsTemplate будем заполнять внутри нового метода, который будем вызывать в render.

class News extends React.Component {
  renderNews = () => {
    const { data } = this.props
    let newsTemplate = null

    if (data.length) {
      newsTemplate = data.map(function(item) {
        return <Article key={item.id} data={item}/>
      })
    } else {
      newsTemplate = <p>К сожалению новостей нет</p>
    }

    return newsTemplate
  }
  render() {
    const { data } = this.props

    return (
      <div className="news">
        {this.renderNews()}
        {
          data.length ? <strong className={'news__count'}>Всего новостей: {data.length}</strong> : null
        }
      </div>
    );
  }
}

Что примечательного в этом коде?

Мы создали метод renderNews (внутри class) с помощью так называемой "жирной стрелочной функции" (запись вида methodName = () => ...). При такой записи, внутри функции мы не теряем контекст this. То есть, можем обращаться к this.props, например.

Почему мы метод render не описываем через жирную стрелочную функцию? Потому что, это метод жизненного цикла react-компонента, и туда this "прокидывает" уже сам react.

Далее, так как мы renderNews создали в качестве метода, значит внутри компонента, мы должны обращаться к нему как this.XXX (где XXX - название метода, в нашем случае renderNews).

Что же изменилось, спросите вы? Было много кода в render, стало чуть выше. Дело в том, что когда ваши компоненты разрастутся, очень удобно иметь "рендер" хорошо читаемым, то есть таким, в котором все лишнее спрятано.

Посмотрим, что вышло в итоге:

Исходный код на данный момент.

Last updated