Для начала, удалите вовсе компонент <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/[email protected]/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, стало чуть выше. Дело в том, что когда ваши компоненты разрастутся, очень удобно иметь "рендер" хорошо читаемым, то есть таким, в котором все лишнее спрятано.
Посмотрим, что вышло в итоге:
на данный момент.