create-react-app

Здесь я буду предельно краток: facebook выкатили удобный инструмент для старта приложения. Поддерживается новый синтаксис, импорты, тестирование, перезагрузка страницы при изменениях, линтер и многое другое.

Во всем разнообразии мы сейчас разбираться не будем. Цель: разбить index.html на компоненты, подключить их, навести порядок.

Скопируйте index.html куда-нибудь на память, скоро мы разобьем его на мелкие удобные компоненты.

Установка и запуск create-react-app

npx create-react-app my-app
cd my-app
npm start

Если вы не знакомы с данными командами, значит вам нужно поставить себе node.js и ввести их в терминале после.

После запуска мы получим следующую картину в браузере:

И следующую файловую структуру:

+-- node_modules (здесь расположены пакеты для работы приложения)
+-- public (здесь расположены публичные файлы, такие как index.html и favicon)
+-- src (здесь сейчас уже живет компонент App)
+-- .gitignore (файл для гита)
+-- package.json (файл с зависимостями проекта)
+-- README.md (описание проекта)
+-- yarn.lock (может быть, а может и не быть - тоже относится к теме зависимостей проекта)

Восстановим баланс в src

src/App.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;
}
.test-input {
  margin: 0 5px 5px 0;
}

.add {
  margin: 0 5px 5px 0;
  width: 210px;
  border: 1px dashed rgba(0, 89, 181, 0.82);
  padding: 5px;
}
.add__author, .add__text, .add__btn, .add__checkrule {
  display: block;
  margin: 0 0 5px 0;
  padding: 5px;
  width: 94%;
  border: 1px solid rgba(0, 89, 181, 0.82);
}
.add__checkrule {
  border: none;
  font-size: 12px;
}
.add__btn {
  box-sizing: content-box;
  color: #FFF;
  text-transform: uppercase;
  background: #007DDC;
}
.add__btn:disabled {
  background: #CCC;
  color: #999;
}

src/App.js (копируем почти все из тэга script)

import React from 'react'; // подключение библиотеки React
import './App.css'; // подключение файла стилей

// далее скопировано из тэга script

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

class Article extends React.Component {
  state = {
    visible: false
  };
  handleReadMoreClck = e => {
    e.preventDefault();
    this.setState({ visible: true });
  };
  render() {
    const { author, text, bigText } = this.props.data;
    const { visible } = this.state;
    return (
      <div className="article">
        <p className="news__author">{author}:</p>
        <p className="news__text">{text}</p>
        {!visible && (
          <a
            onClick={this.handleReadMoreClck}
            href="#"
            className="news__readmore"
          >
            Подробнее
          </a>
        )}
        {visible && <p className="news__big-text">{bigText}</p>}
      </div>
    );
  }
}

Article.propTypes = {
  data: PropTypes.shape({
    id: PropTypes.number.isRequired, // добавили id, это число, обязательно
    author: PropTypes.string.isRequired,
    text: PropTypes.string.isRequired,
    bigText: PropTypes.string.isRequired
  })
};

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

News.propTypes = {
  data: PropTypes.array.isRequired
};

class Add extends React.Component {
  state = {
    name: "",
    text: "",
    bigText: "",
    agree: false
  };
  onBtnClickHandler = e => {
    e.preventDefault();
    const { name, text, bigText } = this.state;
    this.props.onAddNews({
      id: +new Date(),
      author: name,
      text,
      bigText
    });
  };
  handleChange = e => {
    const { id, value } = e.currentTarget;
    this.setState({ [id]: e.currentTarget.value });
  };
  handleCheckboxChange = e => {
    this.setState({ agree: e.currentTarget.checked });
  };
  validate = () => {
    const { name, text, agree } = this.state;
    if (name.trim() && text.trim() && agree) {
      return true;
    }
    return false;
  };
  render() {
    const { name, text, bigText, agree } = this.state;
    return (
      <form className="add">
        <input
          id="name"
          type="text"
          onChange={this.handleChange}
          className="add__author"
          placeholder="Ваше имя"
          value={name}
        />
        <textarea
          id="text"
          onChange={this.handleChange}
          className="add__text"
          placeholder="Текст новости"
          value={text}
        />
        <textarea
          id="bigText"
          onChange={this.handleChange}
          className="add__text"
          placeholder="Текст новости подробно"
          value={bigText}
        />
        <label className="add__checkrule">
          <input type="checkbox" onChange={this.handleCheckboxChange} /> Я
          согласен с правилами
        </label>
        <button
          className="add__btn"
          onClick={this.onBtnClickHandler}
          disabled={!this.validate()}
        >
          Показать alert
        </button>
      </form>
    );
  }
}

Add.propTypes = {
  onAddNews: PropTypes.func.isRequired
};

class App extends React.Component {
  state = {
    news: myNews
  };
  handleAddNews = data => {
    const nextNews = [data, ...this.state.news];
    this.setState({ news: nextNews });
  };
  render() {
    return (
      <React.Fragment>
        <Add onAddNews={this.handleAddNews} />
        <h3>Новости</h3>
        <News data={this.state.news} />
      </React.Fragment>
    );
  }
}

// скопировано все кроме ReactDOM.render

// добавился export
export default App;

Удалите файл src/Logo.svg, остальное не трогайте, но можете посмотреть :)

Create-react-app (CRA) при каждом изменении в файлах внутри директории src - перезагружает страницу в браузере.

У нас нет PropTypes в проекте. Так как раньше это был тэг script, а теперь нам нужен npm-пакет.

Остановите работу create-react-app в терминале и добавьте пакет prop-types

npm install --save prop-types

Я не привожу сюда примеров для yarn, так как если у вас yarn вы итак в курсе, как ставить пакеты через него.

Подключите PropTypes в начале файла:

src/App.js

import React from 'react'
import PropTypes from 'prop-types'
import './App.css'
...

Запустите приложение еще раз:

npm start

Итого: мы перевели приложение на CRA.

Исходный код на данный момент (добавлен конфиг для преттира - .prettierrc, не обращайте внимание).

Last updated