CSS
JavaScript
HTML
ReactJS
2 December 2018

Начать с React и Bootstrap за 2 дня. День №1

Tutorial
Сразу скажу, что сайт будет быстрее работать, если заменить Bootstrap на чистый CSS и JS. Эта статья про то, как быстро начать разрабатывать красивые web-приложения, а оптимизация это уже отдельный вопрос, выходящий за пределы этой статьи.

Для начала надо хотя бы немного разбираться в HTML, CSS, JavaScript, XML, DOM, ООП и уметь работать в терминале (командной строке).

Где брать материалы для изучения?
Для изучения HTML и CSS рекомендую htmlbook.ru
Для изучения JavaScript рекомендую learn.javascript.ru
Для изучения XML рекомендую msiter.ru/tutorials/uchebnik-xml-dlya-nachinayushchih
Про DOM можно почитать в уроке по JavaScript learn.javascript.ru/dom-nodes
Для изучения ООП рекомендую видеокурс proglib.io/p/oop-videocourse
Для изучения командной строки Windows рекомендую cmd.readthedocs.io/cmd.html
Для изучения терминала в Mac рекомендую ixrevo.me/mac-os-x-terminal
Если вы работаете в Linux, то bash и аналоги знаете, в крайнем случае man или help вам помогут.
Для изучения React использую learn-reactjs.ru (который является переводом официальной документации React: reactjs.org).
Для изучения Bootstrap использую bootstrap-4.ru (который является переводом официальной документации Bootstrap: getbootstrap.com).
Для того, чтобы подружить React и Bootstrap нашёл отличную статью webformyself.com/kak-ispolzovat-bootstrap-s-react

В этой статье сделаю выжимку минимально необходимого для работы и сделаем такой таймер:



Установка


Для начала нам нужен менеджер пакетов. Я выбрал npm, а он есть в Node.js
Так что первым делом устанавливаете Node.js на свою операционную систему с официального сайта: nodejs.org/en/download. С установкой вы сможете разобраться, так что процесс установки описывать не буду. Отмечу лишь, что под Ubuntu устанавливается просто:

sudo apt update
sudo apt install nodejs
sudo apt install npm

Через терминал проверяем, что Node.js и npm успешно установились:

nodejs -v
npm -v

Если при выводе возникнут ошибки – значит что-то прошло не так и н��до разбираться, и возможно переустанавливать их. Если же выведется v c цифрами и точками – то всё хорошо.

Установим Create-react-app, чтобы потом можно было быстро создавать каркасы приложений:

npm install -g create-react-app

Далее создаём каркас приложения на React. Назовём наше приложение new-app. Если хотите создать приложение папке отличной от пользовательской папки – то сначала через терминал перейдите в неё с помощью команды cd. Итак, в терминале достаточно ввести 3 команды:

create-react-app new-app
cd new-app
npm start

Создаём приложение new-app. Переходим в папку new-app. Запускаем приложение. После этих строк должен запуститься браузер с приложением React по адресу http://localhost:3000



Терминал должен остаться открытым, без него не будет открываться страница приложения. Если вдруг закрыли – не беда. Достаточно с помощью команды cd перейти в папку приложения и запустить его командой npm start

Теперь установим Bootstrap

npm install bootstrap

Так же советуют установить к нему зависимости jquery и popper.js, но они нужны только для JS-части Bootstrap. Попробовал без них — CSS-часть Bootstrap нормально работает, так что следующие строки в терминале не обязательны:

npm install jquery popper.js

Далее надо внести изменения в файлы приложения, для этого переходим в папку new-app, где находится приложение в файл src/index.js добавляем строку, она должна быть первой:

import 'bootstrap/dist/css/bootstrap.min.css';

Если будете использовать jQuery, popper.js или JS-часть Bootstrap (модальные окна, анимации и т.д), то потребуется под первой строкой добавить ещё 3 строки:

import $ from 'jquery';
import Popper from 'popper.js';
import 'bootstrap/dist/js/bootstrap.bundle.min';

Теперь осталось запустить проект:

npm start

И снова откроется браузер по адресу http://localhost:3000 уже с приложением, облагороженным с помощью Bootstrap:


Так же для отладки React можно установить расширение «React Developer Tools» для браузера. Актуальные ссылки на расширение для Chrome и Firefox и другие варианты использования указаны в официальном репозитории github.com/facebook/react-devtools
Установка и первоначальная настройка на этом завершена.

JSX, компоненты и свойства


Давайте посмотрим, что нам сгенерировал create-react-app — исходные файлы лежат в каталоге src. Во-первых, посмотрим файл index.js – там несколько строчек импорта. По строчкам понятно, что они делают, так что не буду комментировать.

Самая важная строка в этом файле:

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

В ней рисуется страница приложения. В исходном HTML-файле находится элемент <div> с id=root. В этом <div> выводится компонент App, который рисуется функцией render класса ReactDOM. При этом компонент рисуется в форме, похожей на XML, которая и называется JSX (о котором позже).

Теперь перейдём в файл App.js, где находится реализация класса App, который наследуется от класса React.Component.

class App extends React.Component {
В классе вызывается метод <b>render()</b>, который рисует страницу на JSX:
<div className="App">
  <header className="App-header">
    <img src={logo} className="App-logo" alt="logo" />
    <p>
      Edit <code>src/App.js</code> and save to reload.
    </p>
    <a
      className="App-link"
      href="https://reactjs.org"
      target="_blank"
      rel="noopener noreferrer"
    >
      Learn React
    </a>
  </header>
</div>

JSX очень похож на HTML, при этом есть вставки JS-кода в фигурных скобках {}. И обязательно должен быть один корневой элемент, в данном случае <div>.

Чтобы лучше разобраться – сотрём весь код метода render(), и напишем простейший компонент:

class App extends React.Component {
  render() {
    return <h1>Привет, {this.props.name}!</h1>;
  }
}

А теперь вернёмся в файл index.js и исправим

ReactDOM.render(<App name="Мир" />, document.getElementById('root'));

После сохранения файлов – в браузере обновится страница. А теперь будем разбираться.

Концептуально, компоненты подобны JavaScript-функциям. Они принимают произвольные данные (называемые props) и возвращают React-элементы, описывающие что должно появиться на экране. Компоненты позволяют разделить UI на независимые, переиспользуемые части и работать с каждой из них отдельно.

Когда React видит, что элемент представляет собой пользовательский компонент, он передает все JSX-атрибуты в этот компонент единым объектом. Такой объект называется props.

В примере параметр name передаётся в компонент как атрибут тега <App> со значением «Мир». Далее в методе render() класса App в качестве результата функции внутри JSX, который фактически является HTML-шаблоном – в фигурных скобках {} указывается this – текущий класс, props – пользовательский объект, name – название параметра объекта.

Конструктор, жизненный цикл и изменение состояния


Помимо параметров, хранящихся в props можно хранить состояние объекта в state.

Сделаем таймер. Для таймера не нужны параметры, поэтому уберём параметры в index.js:

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

А теперь в файле App.js заменим весь текст между import и export:

const INTERVAL = 100;

class App extends Component {
	
  constructor(props) {
    super(props);
    this.state = {value: 0};
  }

  increment(){
    this.setState({value: this.state.value + 1});
  }

  componentDidMount() {
    this.timerID = setInterval(() => this.increment(), 1000/INTERVAL);
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  render() {
    const value = this.state.value
    return (
      <div>
        <p>Таймер:</p>
        <p>
          <span>{Math.floor(value/INTERVAL/60/60)} : </span>
          <span>{Math.floor(value/INTERVAL/60) % 60} : </span>
          <span>{Math.floor(value/INTERVAL) % 60} . </span>
          <span>{value % INTERVAL}</span>
        </p>
      </div>
    );
  }
}

После вставки и сохранения этого кода на странице появится и автоматически запустится таймер.

Давайте разбирать этот код. До класса объявили константу, благодаря которой можно регулировать частоту обновления таймера.

Далее внутри класса идёт обязательный конструктор класса, в который передаётся props. Далее стандартная обработка конструктора родительского класса super(props) и определение состояния value через this – текущий объект. Это единственное место, где можно напрямую установить состояние. В остальных местах доступно только чтение, либо установка состояния специальным методом setState(), который используется в следующем методе increment() для увеличения состояния value на единицу.

В приложениях с множеством компонентов очень важно высвобождать ресурсы, занятые компонентами, когда они уничтожаются. Нам необходимо устанавливать таймер каждый раз, когда DOM отрисовывается в первый раз. В React это называется «монтированием/монтажом». Также нам нужно очищать этот таймер, каждый раз когда DOM, созданный компонентом, удаляется. В React это называется «демонтированием/демонтажём».

Для этого и используются методы componentDidMount() и componentWillUnmount(). В документации эти методы носят название «lifecycle hooks». Мы же будем для простоты называть их методами жизненного цикла. Метод componentDidMount() срабатывает после того, как компонент был отрисован в DOM. Это хорошее место, чтобы установить таймер. Очищать таймер будем в методе componentWillUnmount() жизненного цикла.

Обратите внимание, как мы в componentDidMount() сохраняем ID таймера прямо в this используя стрелочную функцию. В то время как this.props самостоятельно устанавливаются React-ом и this.state имеет определенное значение, вы свободно можете добавить дополнительные поля в класс вручную, если вам необходимо хранить что-нибудь, что не используется для визуального вывода. Если вы не используете что-то в render(), оно не должно находиться в состоянии state.

Далее на время выполнения render() в локальной константе value фиксируется значение состояния value. И далее с помощью математической функции floor(), которая округляет число в меньшую сторону, деления(/) и получение остатка от деления(%) получаем части таймера, которые далее выводим в одну строку после слова Таймер. Можно посмотреть результаты нашей работы.

Оформление с помощью Bootstrap


Не удобно, что таймер работает сразу при обновлении страницы. Хотелось бы, чтобы он запускался и останавливался при нажатии на соответствующие кнопки. А ещё хотелось бы, чтобы он был в центре и крупный.

Начнём с оформления. Для этого в файл App.css добавим следующие строки:

.container-fluid {
  display: flex;
  flex-direction: column;
}

Благодаря встроенному в Bootstrap адаптивно-резиновому контейнеру container-fluid, который помогает создать полностью гибкий макет страницы или некоторого блока. Данный контейнер имеет 100% ширину. Сделаем контейнер flex, с направлением выстраивания элементов по вертикали – чтобы он занял всё пространство и его можно было выровнять по центру.

Теперь доработаем метод render() в App.js, чтобы применить стили Bootstrap и добавить пару кнопок. Для этого заменим возвращаемое методом значение на следующее:

<div class="container-fluid align-items-center">
  <h1 class="display-1">Таймер</h1>
  <h1 class="display-1">
    <span><kbd>{Math.floor(value/INTERVAL/60/60)}</kbd> : </span>
    <span><kbd>{Math.floor(value/INTERVAL/60) % 60}</kbd> : </span>
    <span><kbd>{Math.floor(value/INTERVAL) % 60}</kbd> . </span>
    <span><kbd>{value % INTERVAL < 10 ? '0' : ''}{value % INTERVAL}</kbd></span>
  </h1>						
  <div>
    <button class="display-4">Остановить</button> 
    <button class="display-4">Сбросить</button>
  </div>
</div>

В первой строке к корневому <div> добавили 2 класса Bootstrap: container-fluid(о котором писал выше) и align-items-center – который как раз и выравнивает элементы контейнера по центру.

Далее два <div> с классом display-1 – этот класс как раз для показа крупного текста.

Далее на цифры добавил новый тег <kbd> — который обычно используется для подсветки клавиш, которые нужно нажать. В данном случае он отлично подходит для контраста показываемых цифр.

В последней цифре, показывающей части секунды добавлено условное выражение, позволяющее для однозначных цифр (<10) выводить в начале 0, и не выводить его для двухзначных чисел. Это нужно, чтобы цифры каждую секунду не дёргались. Для этого используем тернарный оператор JavaScript: условие? true: false

Далее в отдельном <div> поместил 2 кнопки с классом Display-4 – этот класс подобрал, как наиболее подходящий по размеру, чтобы кнопки соответствовали размеру таймера. Между кнопками вставил символ   — неразрывный пробел, чтобы кнопки не сливались.

Можно запустить, но кнопки пока не работают. Научим кнопки работать.

Обработка событий


Для начала добавим в код вывода кнопок вызов соответствующих функций:

<button class="display-4" onClick={this.stopTimer}>Остановить</button> 
<button class="display-4" onClick={this.resetTimer}>Сбросить</button>

Обратите внимание, что в React обработчик события onClick, а не onclick, как в JavaScript и вызываемая функция указывается в фигурных скобках без круглых скобок и с указанием объекта, из которого вызывается метод, в данном случае это this.

Теперь определим указанные методы stopTimer() и resetTimer():

stopTimer(){
  clearInterval(this.timerID);
}

resetTimer(){
  this.setState({value: 0});		
}

Но этого ещё недостаточно и если оставить так, то при нажатии кнопки будет появляться ошибка, т.к. this при вызове функции будет undefined. Это возникает из-за того, что в JavaScript, методы класса не привязаны по умолчанию. Как правило, если вы ссылаетесь на метод без () после него, например, onClick={this.resetTimer}, вам необходимо привязать этот метод.

Привяжем методы в конструкторе класса, добавив туда 2 строчки:

this.stopTimer = this.stopTimer.bind(this);
this.resetTimer = this.resetTimer.bind(this);

Отлично, заработало! Вот только кнопкой остановки можно воспользоваться только 1 раз, и после этого кнопки перестают работать. И это логично, ведь вызвав stopTimer() мы отключили регулярный вызов функций, вызвав clearInterval().

В комментариях посоветовали использовать стрелочные функции. Попробовал, это работает. Так что можно можно не добавлять 2 строки в конструктор, а сами функции заменить следующими стрелочными функциями:
stopTimer = () => {
  this.timerID = setInterval(() => this.increment(), 1000/INTERVAL);
}

resetTimer = () => {
  this.setState({value: 0});		
}

Чтобы решить это – сделаем, чтобы кнопка «Остановить» работала ещё и как «Запустить».

Для начала добавим в конструктор булево состояние stopped, чтобы понимать, в каком режиме работает кнопка:

this.state = {value: 0, stopped: false};

Теперь полностью заменяем содержимое метода stopTimer():


  this.setState({stopped: !this.state.stopped});
  if(this.state.stopped){
    clearInterval(this.timerID);
  }
  else
  {
    this.timerID = setInterval(() => this.increment(), 1000/INTERVAL);
  };

В начале метода меняем состояние stopped на противоположное через setState().

Далее, если таймер должен быть остановлен (т.е. stopped = true) – то отключаем регулярный вызов функций через clearInterval(), а если таймер должен быть запущен (т.е. stopped = false), то запускаем регулярный вызов функций аналогично методу componentDidMount().

Также надо исправить метод increment(), чтобы он останавливался, когда stopped = true:
increment(){
  if(!this.state.stopped) (this.setState({value: this.state.value + 1}));
}

И напоследок меняем название кнопки в зависимости от состояния stopped, вставив вместо «Остановить» следующее:

{this.state.stopped?'Продолжить':'Остановить'}

Теперь у нас получился красивый, удобный таймер.

Вместо заключения или вишенка на торте


Напоследок хотелось бы изменить стандартные заголовок и иконку окна на наши.

Изменить заголовок можно установив document.title в методе componentDidMount(), но мы пойдём дальше и сделаем, чтобы в заголовке страницы отображалось время с точностью до секунд, для этого добавим установку document.title в специальный метод componentDidUpdate():

componentDidUpdate(){
const value = this.state.value;
if (this.state.stopped) document.title = "Таймер";
else document.title = "Таймер: "+Math.floor(value/INTERVAL/60/60)+":"
+Math.floor(value/INTERVAL/60) % 60+":"+Math.floor(value/INTERVAL) % 60;
}

Теперь в заголовке страницы повторяется таймер до секунд, а когда таймер остановлен, то показывается лишь слово Таймер.

С иконкой всё просто. Достаточно подготовить картинку в формате jpg, bmp, gif, png, закинуть в папку public (а не src, в которой мы в основном работали), назвав, например favicon.png и поменять в файле public\index.html строку:

<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">

на строку:

<link rel="shortcut icon" type="image/png" href="/favicon.png"/>

На сегодня это всё, что я хотел рассказать. В следующей статье подробнее расскажу про Bootstrap, которого в этой статье лишь слегка коснулся. Кроме Bootstrap остались ещё важные темы: списки, таблицы, формы и мышление в стиле React.

Напоследок репозиторий в BitBucket, в котором есть весь код к этой статье

+3
17.9k 173
Comments 42
Top of the day