Сразу скажу, что сайт будет быстрее работать, если заменить Bootstrap на чистый CSS и JS. Эта статья про то, как быстро начать разрабатывать красивые web-приложения, а оптимизация это уже отдельный вопрос, выходящий за пределы этой статьи.
Для начала надо хотя бы немного разбираться в HTML, CSS, JavaScript, XML, DOM, ООП и уметь работать в терминале (командной строке).
В этой статье сделаю выжимку минимально необходимого для работы и сделаем такой таймер:
Для начала нам нужен менеджер пакетов. Я выбрал npm, а он есть в Node.js
Так что первым делом устанавливаете Node.js на свою операционную систему с официального сайта: nodejs.org/en/download. С установкой вы сможете разобраться, так что процесс установки описывать не буду. Отмечу лишь, что под Ubuntu устанавливается просто:
Через терминал проверяем, что Node.js и npm успешно установились:
Если при выводе возникнут ошибки – значит что-то прошло не так и надо разбираться, и возможно переустанавливать их. Если же выведется v c цифрами и точками – то всё хорошо.
Установим Create-react-app, чтобы потом можно было быстро создавать каркасы приложений:
Далее создаём каркас приложения на React. Назовём наше приложение new-app. Если хотите создать приложение папке отличной от пользовательской папки – то сначала через терминал перейдите в неё с помощью команды cd. Итак, в терминале достаточно ввести 3 команды:
Создаём приложение new-app. Переходим в папку new-app. Запускаем приложение. После этих строк должен запуститься браузер с приложением React по адресу http://localhost:3000
Терминал должен остаться открытым, без него не будет открываться страница приложения. Если вдруг закрыли – не беда. Достаточно с помощью команды cd перейти в папку приложения и запустить его командой npm start
Теперь установим Bootstrap
Так же советуют установить к нему зависимости jquery и popper.js, но они нужны только для JS-части Bootstrap. Попробовал без них — CSS-часть Bootstrap нормально работает, так что следующие строки в терминале не обязательны:
Далее надо внести изменения в файлы приложения, для этого переходим в папку new-app, где находится приложение в файл src/index.js добавляем строку, она должна быть первой:
Если будете использовать jQuery, popper.js или JS-часть Bootstrap (модальные окна, анимации и т.д), то потребуется под первой строкой добавить ещё 3 строки:
Теперь осталось запустить проект:
И снова откроется браузер по адресу http://localhost:3000 уже с приложением, облагороженным с помощью Bootstrap:
Так же для отладки React можно установить расширение «React Developer Tools» для браузера. Актуальные ссылки на расширение для Chrome и Firefox и другие варианты использования указаны в официальном репозитории github.com/facebook/react-devtools
Установка и первоначальная настройка на этом завершена.
Давайте посмотрим, что нам сгенерировал create-react-app — исходные файлы лежат в каталоге src. Во-первых, посмотрим файл index.js – там несколько строчек импорта. По строчкам понятно, что они делают, так что не буду комментировать.
Самая важная строка в этом файле:
В ней рисуется страница приложения. В исходном HTML-файле находится элемент <div> с id=root. В этом <div> выводится компонент App, который рисуется функцией render класса ReactDOM. При этом компонент рисуется в форме, похожей на XML, которая и называется JSX (о котором позже).
Теперь перейдём в файл App.js, где находится реализация класса App, который наследуется от класса React.Component.
JSX очень похож на HTML, при этом есть вставки JS-кода в фигурных скобках {}. И обязательно должен быть один корневой элемент, в данном случае <div>.
Чтобы лучше разобраться – сотрём весь код метода render(), и напишем простейший компонент:
А теперь вернёмся в файл index.js и исправим
После сохранения файлов – в браузере обновится страница. А теперь будем разбираться.
Концептуально, компоненты подобны JavaScript-функциям. Они принимают произвольные данные (называемые props) и возвращают React-элементы, описывающие что должно появиться на экране. Компоненты позволяют разделить UI на независимые, переиспользуемые части и работать с каждой из них отдельно.
Когда React видит, что элемент представляет собой пользовательский компонент, он передает все JSX-атрибуты в этот компонент единым объектом. Такой объект называется props.
В примере параметр name передаётся в компонент как атрибут тега <App> со значением «Мир». Далее в методе render() класса App в качестве результата функции внутри JSX, который фактически является HTML-шаблоном – в фигурных скобках {} указывается this – текущий класс, props – пользовательский объект, name – название параметра объекта.
Помимо параметров, хранящихся в props можно хранить состояние объекта в state.
Сделаем таймер. Для таймера не нужны параметры, поэтому уберём параметры в index.js:
А теперь в файле App.js заменим весь текст между import и export:
После вставки и сохранения этого кода на странице появится и автоматически запустится таймер.
Давайте разбирать этот код. До класса объявили константу, благодаря которой можно регулировать частоту обновления таймера.
Далее внутри класса идёт обязательный конструктор класса, в который передаётся 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(), которая округляет число в меньшую сторону, деления(/) и получение остатка от деления(%) получаем части таймера, которые далее выводим в одну строку после слова Таймер. Можно посмотреть результаты нашей работы.
Не удобно, что таймер работает сразу при обновлении страницы. Хотелось бы, чтобы он запускался и останавливался при нажатии на соответствующие кнопки. А ещё хотелось бы, чтобы он был в центре и крупный.
Начнём с оформления. Для этого в файл App.css добавим следующие строки:
Благодаря встроенному в Bootstrap адаптивно-резиновому контейнеру container-fluid, который помогает создать полностью гибкий макет страницы или некоторого блока. Данный контейнер имеет 100% ширину. Сделаем контейнер flex, с направлением выстраивания элементов по вертикали – чтобы он занял всё пространство и его можно было выровнять по центру.
Теперь доработаем метод render() в App.js, чтобы применить стили Bootstrap и добавить пару кнопок. Для этого заменим возвращаемое методом значение на следующее:
В первой строке к корневому <div> добавили 2 класса Bootstrap: container-fluid(о котором писал выше) и align-items-center – который как раз и выравнивает элементы контейнера по центру.
Далее два <div> с классом display-1 – этот класс как раз для показа крупного текста.
Далее на цифры добавил новый тег <kbd> — который обычно используется для подсветки клавиш, которые нужно нажать. В данном случае он отлично подходит для контраста показываемых цифр.
В последней цифре, показывающей части секунды добавлено условное выражение, позволяющее для однозначных цифр (<10) выводить в начале 0, и не выводить его для двухзначных чисел. Это нужно, чтобы цифры каждую секунду не дёргались. Для этого используем тернарный оператор JavaScript: условие? true: false
Далее в отдельном <div> поместил 2 кнопки с классом Display-4 – этот класс подобрал, как наиболее подходящий по размеру, чтобы кнопки соответствовали размеру таймера. Между кнопками вставил символ — неразрывный пробел, чтобы кнопки не сливались.
Можно запустить, но кнопки пока не работают. Научим кнопки работать.
Для начала добавим в код вывода кнопок вызов соответствующих функций:
Обратите внимание, что в React обработчик события onClick, а не onclick, как в JavaScript и вызываемая функция указывается в фигурных скобках без круглых скобок и с указанием объекта, из которого вызывается метод, в данном случае это this.
Теперь определим указанные методы stopTimer() и resetTimer():
Но этого ещё недостаточно и если оставить так, то при нажатии кнопки будет появляться ошибка, т.к. this при вызове функции будет undefined. Это возникает из-за того, что в JavaScript, методы класса не привязаны по умолчанию. Как правило, если вы ссылаетесь на метод без () после него, например, onClick={this.resetTimer}, вам необходимо привязать этот метод.
Привяжем методы в конструкторе класса, добавив туда 2 строчки:
Отлично, заработало! Вот только кнопкой остановки можно воспользоваться только 1 раз, и после этого кнопки перестают работать. И это логично, ведь вызвав stopTimer() мы отключили регулярный вызов функций, вызвав clearInterval().
В комментариях посоветовали использовать стрелочные функции. Попробовал, это работает. Так что можно можно не добавлять 2 строки в конструктор, а сами функции заменить следующими стрелочными функциями:
Чтобы решить это – сделаем, чтобы кнопка «Остановить» работала ещё и как «Запустить».
Для начала добавим в конструктор булево состояние stopped, чтобы понимать, в каком режиме работает кнопка:
Теперь полностью заменяем содержимое метода stopTimer():
В начале метода меняем состояние stopped на противоположное через setState().
Далее, если таймер должен быть остановлен (т.е. stopped = true) – то отключаем регулярный вызов функций через clearInterval(), а если таймер должен быть запущен (т.е. stopped = false), то запускаем регулярный вызов функций аналогично методу componentDidMount().
Также надо исправить метод increment(), чтобы он останавливался, когда stopped = true:
И напоследок меняем название кнопки в зависимости от состояния stopped, вставив вместо «Остановить» следующее:
Теперь у нас получился красивый, удобный таймер.
Напоследок хотелось бы изменить стандартные заголовок и иконку окна на наши.
Изменить заголовок можно установив document.title в методе componentDidMount(), но мы пойдём дальше и сделаем, чтобы в заголовке страницы отображалось время с точностью до секунд, для этого добавим установку document.title в специальный метод componentDidUpdate():
Теперь в заголовке страницы повторяется таймер до секунд, а когда таймер остановлен, то показывается лишь слово Таймер.
С иконкой всё просто. Достаточно подготовить картинку в формате jpg, bmp, gif, png, закинуть в папку public (а не src, в которой мы в основном работали), назвав, например favicon.png и поменять в файле public\index.html строку:
на строку:
На сегодня это всё, что я хотел рассказать. В следующей статье подробнее расскажу про Bootstrap, которого в этой статье лишь слегка коснулся. Кроме Bootstrap остались ещё важные темы: списки, таблицы, формы и мышление в стиле React.
Напоследок репозиторий в BitBucket, в котором есть весь код к этой статье
Для начала надо хотя бы немного разбираться в 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
Для изучения 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, в котором есть весь код к этой статье