Книга: react-course-ru-v2
Назад: If-else, тернарный оператор
Дальше: Prop-types

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

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

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


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

на данный момент.

Назад: If-else, тернарный оператор
Дальше: Prop-types