├── README.md └── manuscript ├── Book.txt ├── chapter1.md ├── chapter2.md ├── chapter3.md ├── chapter4.md ├── chapter5.md ├── chapter6.md ├── contributor.md ├── deployChapter.md ├── finalwords.md └── foreword.md /README.md: -------------------------------------------------------------------------------- 1 | # Путь к изучению React [Книга] 2 | 3 | [![Slack](https://slack-the-road-to-learn-react.wieruch.com/badge.svg)](https://slack-the-road-to-learn-react.wieruch.com/) 4 | 5 | Официальный репозиторий [Путь к изучению React (The Road to learn React)](https://www.robinwieruch.de/the-road-to-learn-react/). Кроме того, здесь вы можете найти [исходный код](https://github.com/the-road-to-learn-react/hackernews-client) и [полный курс](https://roadtoreact.com/). Если вы хотите оставить отзыв, вы можете сделать это на [Amazon](https://www.amazon.com/dp/B077HJFCQX?tag=21moves-20) или [Goodreads](https://www.goodreads.com/book/show/37503118-the-road-to-learn-react). Эта книга также размещена на [Leanpub](https://leanpub.com/the-road-to-learn-react). 6 | 7 | ## Переводы книги на Leanpub 8 | 9 | Книга «Путь к изучению React» также доступна на нескольких языках. Спасибо всем переводчикам за их потрясающую работу! 10 | 11 | * 🇨🇳 [Китайский упрощённый](https://leanpub.com/the-road-to-learn-react-chinese) 12 | * 🇫🇷 [Французский](https://leanpub.com/the-road-to-learn-react-french) 13 | * 🇰🇷 [Корейский](https://leanpub.com/the-road-to-learn-react-korean) 14 | * 🇧🇷 [Португальский](https://leanpub.com/the-road-to-learn-react-portuguese) 15 | * 🇷🇺 [Русский](https://leanpub.com/the-road-to-learn-react-russian) 16 | 17 | ## Обновление, помощь и поддержка 18 | 19 | * Получайте обновления книги по [электронной почте](https://www.getrevue.co/profile/rwieruch) или в [Twitter](https://twitter.com/rwieruch) 20 | * Получите помощь во время изучения React с этой книгой или учите других в официальном [канале в Slack](https://slack-the-road-to-learn-react.wieruch.com/) 21 | * Узнайте, как можно [поддержать книгу](https://www.robinwieruch.de/about/) 22 | 23 | ## Участие в проекте 24 | 25 | Вы можете помочь улучшить книгу, открыв ишью или пулреквесты. 26 | 27 | Вы можете открыть любой пулреквест, который исправляет орфографию или объясняет определённую тему более детально. При написании такой технической книги трудно определить что требует больше объяснений, так и то, что уже хорошо объяснено. 28 | 29 | Помимо этого, вы можете открыть задачи при возникновении проблем. Чтобы сделать исправление для проблемы максимально простым, предоставьте пару таких подробностей, как журнал ошибок, скриншот, используемая версия node (в командной строке: `node -v`) и ссылка на ваш собственный репозиторий. Не все эти данные обязательны, но большинство из них помогут исправить проблему и улучшить книгу. 30 | 31 | Большое спасибо за помощь! 32 | 33 | ## Стать меценатом 34 | 35 | Путь к изучению React — бесплатная книга, которая даёт возможность каждому изучить React. Этот вид учебного материала нуждается в вашей поддержке для дальнейшего существования. Робин Вирух (Robin Wieruch) также пишет много образовательного контента на [своём сайте](https://www.robinwieruch.de/). Поэтому вы можете поддержать его, став [меценатом на Patreon](https://www.patreon.com/rwieruch). 36 | -------------------------------------------------------------------------------- /manuscript/Book.txt: -------------------------------------------------------------------------------- 1 | {frontmatter} 2 | 3 | foreword.md 4 | contributor.md 5 | 6 | {mainmatter} 7 | 8 | chapter1.md 9 | chapter2.md 10 | chapter3.md 11 | chapter4.md 12 | chapter5.md 13 | chapter6.md 14 | deployChapter.md 15 | 16 | {backmatter} 17 | 18 | finalwords.md -------------------------------------------------------------------------------- /manuscript/chapter1.md: -------------------------------------------------------------------------------- 1 | # Введение в React 2 | 3 | В этой главе даётся введение в React. Вы можете спросить себя: почему я должен изучить React в первую очередь? Эта глава пытается ответить на данный вопрос. После этого вы погрузитесь в экосистему, создав ваше первое React-приложение без какой-либо конфигурации. По ходу дела вы познакомитесь с JSX и ReactDOM. Поэтому будьте готовы к вашим первым компонентам React. 4 | 5 | ## Привет, меня зовут React. 6 | 7 | **Зачем вам нужно изучать React?** В последние годы стали популярны одностраничные приложения ([single-page application, SPA](https://ru.wikipedia.org/wiki/%D0%9E%D0%B4%D0%BD%D0%BE%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%87%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%B8%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5)). Фреймворки, такие как Angular, Ember и Backbone, помогали разработчикам JavaScript создавать современные веб-приложения за пределами использования чистого (ванильного) JavaScript и jQuery. Список этих популярных решений далеко не полный. Существует широкий круг фреймворков для создания SPA. Если посмотреть на даты релизов, то большинство из них относятся к первому поколению SPA: Angular 2010, Backbone 2010 и Ember 2011. 8 | 9 | React был изначально выпущен Facebook в 2013 году. React — это не SPA-фреймворк, а библиотека для разработки пользовательских интерфейсов (UI). Это только представление, буква V в аббревиатуре [MVC](https://ru.wikipedia.org/wiki/Model-View-Controller) (Model View Controller). Она позволяет вам отрисовывать (render) компоненты в качестве видимых элементов в браузере. Однако целая экосистема вокруг React позволяет создавать одностраничные приложения. 10 | 11 | Но почему вы должны рассмотреть использование React, а не первое поколение SPA-фреймворков? В то время как первое поколение фреймворков пыталось решить сразу много всего, React используется только для создания слоя представления. Это библиотека, а не фреймворк. Идея React заключается в том, что ваше представление представляет собой иерархию составных компонентов. 12 | 13 | В React вы можете сосредоточиться на слое представления, перед тем как внедрять остальные концепции в приложение. Каждый новый аспект — это ещё один строительный блок вашего SPA-приложения. Эти строительные блоки необходимы для создания зрелого приложения, и у них есть два преимущества. 14 | 15 | Во-первых, вы можете изучать строительные блоки по одному, не понимая их вообще. 16 | Напротив, SPA даёт вам каждый строительный блок с самого начала. В этой книге основное внимание уделяется React как первому строительному блоку. В дальнейшем последует всё больше строительных блоков. 17 | 18 | Во-вторых, все строительные блоки взаимозаменяемы, что делает экосистему React очень инновационной. Несколько решений конкурируют друг с другом, и вы можете выбрать наиболее привлекательное решение для вас и вашего варианта использования. 19 | 20 | Первое поколение SPA-фреймворков достигло промышленного уровня; такие фреймворки менее гибкие. React остаётся инновационным и используется многими технологическими компаниями-лидерами, такими как [Airbnb, Netflix и, конечно же, Facebook](https://github.com/facebook/react/wiki/Sites-Using-React). Все они инвестируют в будущее React и довольствуются React и его экосистемой. 21 | 22 | React — один из лучших выборов для создания современных веб-приложений в настоящее время. Он обеспечивает только уровень представления, [но экосистема React представляет собой гибкий и взаимозаменяемый фреймворк](https://www.robinwieruch.de/essential-react-libraries-framework/). React имеет небольшой API, удивительную экосистему и отличное сообщество. Вы можете прочитать о моём опыте, [почему я перешёл с Angular на React](https://www.robinwieruch.de/reasons-why-i-moved-from-angular-to-react/). Я настоятельно рекомендую понять, почему вы выбрали React, а не другой фреймворк или библиотеку. В конце концов, каждый стремится узнать, куда приведёт нас React в ближайшие несколько лет. 23 | 24 | ### Упражнения 25 | 26 | * прочитайте о том, [почему я перешёл с Angular на React](https://www.robinwieruch.de/reasons-why-i-moved-from-angular-to-react/) 27 | * прочитайте о [гибкой экосистеме React](https://www.robinwieruch.de/essential-react-libraries-framework/) 28 | * прочитайте о том, [как изучать фреймворк](https://www.robinwieruch.de/how-to-learn-framework/) 29 | 30 | ## Требования 31 | 32 | Какие требования для чтения этой книги? Прежде всего, вы должны быть знакомы с основами веб-разработки. Вы должны знать, как использовать HTML, CSS и JavaScript. Возможно, имеет смысл также знать, что такое означает термин [API](https://www.robinwieruch.de/what-is-an-api-javascript/), потому что вы будете использовать API в данной книге. Кроме того, я призываю вас вступить в официальную [группу в Slack](https://slack-the-road-to-learn-react.wieruch.com/) этой книги для получения помощи или чтобы помочь другим. 33 | 34 | ### Редактор и терминал 35 | 36 | Как насчёт среды разработки? Вам понадобится работающий редактор или IDE, а также терминал (инструмент командной строки). Вы можете [последовать моему руководству по настройке](https://www.robinwieruch.de/developer-setup/). Он предназначен для пользователей MacOS, но вы также можете найти руководство по настройке для Windows. В целом, есть много статей, которые покажут вам, как наилучшим образом настроить окружение веб-разработки более изысканным способом для используемой вами ОС. 37 | 38 | При желании вы можете использовать git для хранения своих проектов и отслеживания прогресса изучения в репозиториях на GitHub, выполняя упражнения этой в книге. Существует [небольшое руководство](https://www.robinwieruch.de/git-essential-commands/) по использованию этих инструментов. Но опять же, это не необязательно для книги и может стать сдерживающим фактором при изучении всего этого с нуля. Вы можете пропустить эту часть, если вы новичок в веб-разработке, чтобы сосредоточиться на основных частях, описанных в книге. 39 | 40 | ### Node и NPM 41 | 42 | И последнее, но не менее важное: вам потребуется установка [node и npm](https://nodejs.org/en/). Оба они используются для управления библиотеками, которые нам понадобятся по ходу дела. В этой книге вы будете устанавливать внешние node-пакеты через npm (node package manager). Эти node-пакеты могут быть библиотеками или целыми фреймворками. 43 | 44 | Проверить версии node и npm можно в командной строке. Если вы не видите какой-либо вывод в терминале, вам сначала нужно установить node и npm. Ниже показаны только мои версии, которые я использовал во время написания книги: 45 | 46 | {title="Командная строка",lang="text"} 47 | ~~~~~~~~ 48 | node --version 49 | *v8.9.4 50 | npm --version 51 | *v5.6.0 52 | ~~~~~~~~ 53 | 54 | ## node и npm 55 | 56 | В этой главе приведён небольшой обзор node и npm. Он не исчерпывающий, но даст вам все необходимые инструменты. Если вы знакомы с обоими из них, то вы можете пропустить этот раздел. 57 | 58 | **Менеджер пакетов node** (node package manager, npm) позволяет устанавливать **node-пакеты (node packages)** из командной строки. Эти пакеты могут быть набором утилитарных функций, библиотеками или целыми фреймворками. Все они являются зависимостями для вашего приложения. Вы можете установить все эти зависимости в папку с глобальными или локальными node-пакетами. 59 | 60 | Глобальные node-пакеты доступны из любого места в терминале, и их необходимо установить только один раз в глобальный каталог. Вы можете установить глобальный пакет, введя в терминал: 61 | 62 | {title="Командная строка",lang="text"} 63 | ~~~~~~~~ 64 | npm install -g 65 | ~~~~~~~~ 66 | 67 | Флаг `-g` сообщает npm, что пакет нужно установить глобально. Локальные пакеты используются в вашем приложении. Например, React как библиотека будет локальным пакетом, который требуется для работы вашего приложения. Вы можете установить его через терминал, набрав: 68 | 69 | {title="Командная строка",lang="text"} 70 | ~~~~~~~~ 71 | npm install 72 | ~~~~~~~~ 73 | 74 | Команда установки React будет выглядеть следующим образом: 75 | 76 | {title="Командная строка",lang="text"} 77 | ~~~~~~~~ 78 | npm install react 79 | ~~~~~~~~ 80 | 81 | Установленный пакет автоматически появится в папке *node_modules/* и будет перечислен в файле *package.json* вместе с другими зависимостями. 82 | 83 | Но как инициализировать папку *node_modules/* и файл *package.json* для проекта в первую очередь? Для этого у нас есть команда npm, инициализирующая проект npm и, следовательно, файл *package.json*. Когда у вас есть этот файл, вы можете установить новые пакеты, используя npm. 84 | 85 | {title="Командная строка",lang="text"} 86 | ~~~~~~~~ 87 | npm init -y 88 | ~~~~~~~~ 89 | 90 | Флаг `-y` — ярлык для инициализации всех значений по умолчанию в *package.json*. Без использования этого флага, вам нужно самому решить, как сконфигурировать этот файл. После инициализации вашего npm-проекта вы готовы к установке новых пакетов через команду `npm install `. 91 | 92 | Ещё пару слов об *package.json*. Данный файл позволяет вам поделиться вашим проектом с другими разработчиками без передачи всех node-пакетов. Этот файл содержит все ссылки на пакеты node, используемые в вашем проекте. Эти пакеты называются зависимостями. Каждый может скопировать ваш проект без этих зависимостей. Зависимости — это ссылки в *package.json*. Кто-то, кто копирует ваш проект, может просто установить все пакеты, используя `npm install` в командной строке. Команда `npm install` возьмёт все зависимости, перечисленные в файле *package.json* и установит их в папку *node_modules/*. 93 | 94 | Я хочу рассмотреть ещё одну npm-команду: 95 | 96 | {title="Командная строка",lang="text"} 97 | ~~~~~~~~ 98 | npm install --save-dev 99 | ~~~~~~~~ 100 | 101 | Флаг `--save-dev` указывает, что node-пакет используется только в окружении разработки. Он не будет использоваться в продакшене при развёртывании вашего приложения на сервер. Какие node-пакеты должны устанавливаться с помощью этого флага? Представьте, что вы хотите протестировать приложение с помощью node-пакета. Вам нужно установить этот пакет через npm, но вы хотите исключить его из рабочего окружения. Тестирование должно происходить только в процессе разработки, а не тогда, когда приложение уже работает в продакшене. Там вам больше не нужно тестировать приложение. Приложение должно уже быть протестировано и работать из коробки для ваших пользователей. Это как раз тот случай, когда вы захотите использовать флаг `--save-dev`. 102 | 103 | Вы столкнётесь с большим количеством npm-команд по ходу чтения, но этого пока будет достаточно. 104 | 105 | ### Упражнения: 106 | 107 | * создайте npm-проект 108 | * создайте каталог с помощью `mkdir ` 109 | * перейдите в каталог с помощью `cd ` 110 | * выполните `npm init -y` или `npm init` 111 | * установите локальный пакет React с помощью `npm install react` 112 | * убедитесь, что существует файл *package.json* и каталог *node_modules/* 113 | * выясните самостоятельно, как удалить node-пакет *react* 114 | * узнайте больше о [npm](https://docs.npmjs.com/) 115 | 116 | ## Установка 117 | 118 | Существует несколько способов начать работу с приложением React. 119 | 120 | Первый из них — использовать CDN. Это может звучать сложнее, чем есть на самом деле. A CDN — [сеть доставки содержимого](https://ru.wikipedia.org/wiki/Content_Delivery_Network). У нескольких компаний есть CDN, которые публично размещают файлы, чтобы люди могли использовать их. Этими файлами могут быть библиотеки, такие как React, поскольку собранная (bundled) библиотека React — это обычный JavaScript-файл *react.js*. Он может быть размещён где-то, и вы можете использовать его в своём приложении. 121 | 122 | Как использовать CDN для начала работы с React? Вы можете встроить его в HTML-разметку с помощью тега ` 127 | 128 | ~~~~~~~~ 129 | 130 | Но почему мы должны использовать CDN, когда есть npm для установки node-пакетов, таких как React? 131 | 132 | Когда в приложении есть файл *package.json*, вы можете установить *react* и *react-dom* из командной строки. Однако папка должна быть инициализирована как npm-проект с помощью `npm init -y` с файлом *package.json*. Вы можете установить несколько node-пакетов в одну строку через npm. 133 | 134 | {title="Командная строка",lang="text"} 135 | ~~~~~~~~ 136 | npm install react react-dom 137 | ~~~~~~~~ 138 | 139 | Этот подход часто используется для добавления React в существующее приложение, управляемое с использованием npm. 140 | 141 | К сожалению, это ещё не всё. Вам придётся столкнуться с [Babel](http://babeljs.io/) для того, чтобы приложение могло использовать JSX (синтаксис React) и JavaScript ES6. Babel транспилирует (transpiles) ваш код так, чтобы браузеры могли интерпретировать код JavaScript ES6 и JSX, поскольку не все браузеры способны интерпретировать этот синтаксис. Эта установка включает в себя много настроек и инструментов, и для новичков в React это может быть слишком трудным, чтобы возиться со всей этой конфигурацией самостоятельно. 142 | 143 | По этой причине Facebook представил *create-react-app* в качестве решения для быстрого запуска создания React-приложений без конфигурации (или как пишут — zero-configuration). В следующей главе будет показано, как настроить приложение, используя этот инструмент для инициализации приложения. 144 | 145 | ### Упражнения: 146 | 147 | * узнайте больше про [установку React](https://ru.reactjs.org/docs/getting-started.html) 148 | 149 | ## Установка без конфигурации 150 | 151 | В нашей книге вы будете использовать [create-react-app](https://github.com/facebookincubator/create-react-app) для начальной инициализации вашего приложения. Это предварительно настроенный без необходимости в ручной конфигурации стартовый набор для React-приложений, представленный Facebook в 2016 году, и согласно опросу в Twitter [рекомендуется 96% начинающим разработчикам React](https://twitter.com/dan_abramov/status/806985854099062785). В *create-react-app* инструменты и конфигурация отходит на задний план, тогда как основное внимание уделяется реализации приложения. 152 | 153 | Чтобы начать работу, вам требуется установить пакет в каталог глобальных node-пакетов. После этого вы всегда будете иметь возможность из командной строки инициализировать новое React-приложение. 154 | 155 | {title="Командная строка",lang="text"} 156 | ~~~~~~~~ 157 | npm install -g create-react-app 158 | ~~~~~~~~ 159 | 160 | Вы можете проверить версию *create-react-app*, чтобы убедиться в успешной установке из командной строки: 161 | 162 | {title="Командная строка",lang="text"} 163 | ~~~~~~~~ 164 | create-react-app --version 165 | *v1.5.1 166 | ~~~~~~~~ 167 | 168 | Теперь вы можете инициализировать своё первое React-приложение. Мы назовём его *hackernews*, но вы можете выбрать другое имя. Весь процесс настройки займёт пару секунд. После этого перейдите в папку: 169 | 170 | {title="Командная строка",lang="text"} 171 | ~~~~~~~~ 172 | create-react-app hackernews 173 | cd hackernews 174 | ~~~~~~~~ 175 | 176 | Теперь вы можете открыть приложение в своём редакторе. Будет представлена следующая структура папок или её вариация, в зависимости от версии *create-react-app*: 177 | 178 | {title="Структура каталогов",lang="text"} 179 | ~~~~~~~~ 180 | hackernews/ 181 | README.md 182 | node_modules/ 183 | package.json 184 | .gitignore 185 | public/ 186 | favicon.ico 187 | index.html 188 | manifest.json 189 | src/ 190 | App.css 191 | App.js 192 | App.test.js 193 | index.css 194 | index.js 195 | logo.svg 196 | registerServiceWorker.js 197 | ~~~~~~~~ 198 | 199 | Ниже представлен краткий список каталогов и файлов. Это нормально, если вы не понимаете их все в самом начале. 200 | 201 | * **README.md:** Расширение `.md` указывает, что это текстовый файл в формате Markdown. Markdown используется как лёгкий язык разметки с синтаксисом для форматирования текста. Во многих проектах с открытым исходным кодом есть файл *README.md*, чтобы дать пользователям первоначальные инструкции по проекту. Когда вы размещаете свой проект на такой платформе, как GitHub, при открытии репозитория вы увидите визуальное отображение этого файла *README.md*. Поскольку вы использовали *create-react-app*, ваш *README.md* будет таким же, как в [GitHub-репозитории create-react-app](https://github.com/facebookincubator/create-react-app). 202 | 203 | * **node_modules/:** В этой папке находятся все node-пакеты, которые были и будут установлены через npm. Так как вы использовали *create-react-app*, здесь должно уже быть пару установленных node-модулей. Обычно вы никогда не будете иметь дело с этим каталогом, поскольку установка и удаление node-пакетов происходит с помощью пакетных менеджеров (например, npm) из командной строки. 204 | 205 | * **package.json:** Данный файл показывает список зависимостей node-пакетов и прочую конфигурационную информацию проекта. 206 | 207 | * **.gitignore:** В этом файле указываются все файлы и каталоги, которые не должны быть добавлены в ваш git-репозиторий при использовании git; такие файлы будут находиться только в локальном проекте. Каталог _node_modules/_ как раз является таким каталогом, который должен игнорироваться git. Достаточно, чтобы файл _package.json_ находился под git для возможности совместного использования с вашими коллегами, чтобы они смогли самостоятельно установить все зависимости без разделения с ними папки с зависимостями. 208 | 209 | * **public/:** Этот каталог содержит корневые файлы разработки, такие как _public/index.html_. Это индексный файл, который отображается при переходе на localhost:3000 при разработке приложения. Стандартная заготовка (boilerplate) учитывает этот файл, чтобы связать его со всеми скриптами в _src/_. 210 | 211 | * **build/** Этот каталог будет создан при сборке проекта для продакшена. Он содержит все готовые файлы при сборке приложения для продакшен-окружения. Весь ваш код, написанный в каталогах _src/_ и _public/_, будет собран (bundled) в пару файлов при выполнении сборки проекта и будут размещены в каталоге build. 212 | 213 | * **manifest.json** and **registerServiceWorker.js:** не обращайте внимания на эти файлы на данном этапе, нам они не понадобятся в этом проекте. 214 | 215 | Вам не нужно трогать указанные файлы и каталоги. В самом начале всё, что вам нужно, находится в каталоге *src/*. Основное внимание уделяется файлу *src/App.js* для реализации React-компонентов. Он будет использоваться для реализации приложения, но позже вы можете разделить свои компоненты на несколько файлов, тогда каждый файл представляет собой один или несколько компонентов. 216 | 217 | Кроме того, вы найдёте файл *src/App.test.js* для своих тестов и *src/index.js* как точку входа (entry point) в мир React. Об этих двух файлах вы узнаете в следующей главе. Вдобавок есть файлы *src/index.css* и *src/App.css* для стилизации общего приложения и ваших компонентов. У всех у них есть стили по умолчанию, если вы их откроете. 218 | 219 | Приложение *create-react-app* — проект npm. Вы будете использовать npm для установки и удаления node-пакетов. Кроме того, вместе с ним идут npm-скрипты для выполнения в командной строке: 220 | 221 | {title="Командная строка",lang="text"} 222 | ~~~~~~~~ 223 | # Запускает приложение по адресу http://localhost:3000 224 | npm start 225 | 226 | # Запускает выполнение тестов 227 | npm test 228 | 229 | # Запускает сборку приложения для продакшена 230 | npm run build 231 | ~~~~~~~~ 232 | 233 | Эти скрипты определены в вашем *package.json*. Теперь заготовка для React-приложения готова к работе. Следующие упражнения позволят вам, наконец, запустить созданное приложение в браузере. 234 | 235 | ### Упражнения: 236 | 237 | * выполните команду `npm start` и перейдите к просмотру приложения в вашем браузере (вы можете выйти из команды, завершить её, нажав на Control + C) 238 | * запустите интерактивный скрипт `npm test` 239 | * запустите скрипт `npm run build` и убедитесь, что в проекте создался каталог *build/* (вы можете удалить его потом; обратите внимание, что каталог сборки может использоваться позже для [развёртывания приложения](https://www.robinwieruch.de/deploy-applications-digital-ocean/)) 240 | * ознакомьтесь со структурой каталогов 241 | * ознакомьтесь с содержимым файлов 242 | * узнайте подробнее про [npm-скрипты в пакете create-react-app](https://github.com/facebookincubator/create-react-app) 243 | 244 | ## Введение в JSX 245 | 246 | Теперь вы узнаете о JSX — синтаксисе React. Как упоминалось ранее, *create-react-app* уже подготовил заготовку приложения для вас. Каждый файл имеет реализацию по умолчанию. Давайте погрузимся в исходный код. Единственным файлом, с которым вы в первую очередь будете работать — *src/App.js*. 247 | 248 | {title="src/App.js",lang=javascript} 249 | ~~~~~~~~ 250 | import React, { Component } from 'react'; 251 | import logo from './logo.svg'; 252 | import './App.css'; 253 | 254 | class App extends Component { 255 | render() { 256 | return ( 257 |
258 |
259 | logo 260 |

Добро пожаловать в React

261 |
262 |

263 | Для начала отредактируйте src/App.js и сохраните его для перезагрузки. 264 |

265 |
266 | ); 267 | } 268 | } 269 | 270 | export default App; 271 | ~~~~~~~~ 272 | 273 | Не позволяйте себе запутаться в выражениях import/export и объявлении класса. Эти возможности уже JavaScript ES6. Мы рассмотрим их в следующей главе. 274 | 275 | В файле есть **React-компонент, определённый через класс ES6** с именем App. Это объявление компонента. В основном после того, как вы объявили компонент, вы можете использовать его в качестве элемента повсюду в своём приложении. Он будет создавать **экземпляр** вашего **компонента** или другими словами: компонент создаёт экземпляр (инстанцируется). 276 | 277 | Возвращаемый **элемент** указывается в методе `render()`. Элементы — это то, из чего состоят компоненты. Важно понимать различия между компонентом, экземпляром и элементом. 278 | 279 | Довольно скоро вы увидите, где создаётся экземпляр компонента App. В противном случае вы не увидите отрисованный вывод, не так ли? Компонент App — это только объявление, но не его использование. Вы должны инстанцировать компонент где-то в своём JSX с помощью ``. 280 | 281 | Содержимое в блоке render выглядит довольно похожим на HTML, но это JSX. JSX позволяет смешивать HTML и JavaScript. Он мощный, но сбивает с толку, когда вы используете его для разделения HTML и JavaScript. Вот почему хорошей отправной точкой считается использовать обычный HTML в JSX. Для начала откройте файл `App.js` и замените HTML-код на тот, который показан ниже. 282 | 283 | {title="src/App.js",lang=javascript} 284 | ~~~~~~~~ 285 | import React, { Component } from 'react'; 286 | import './App.css'; 287 | 288 | class App extends Component { 289 | render() { 290 | return ( 291 |
292 |

Добро пожаловать в Путь к изучению React

293 |
294 | ); 295 | } 296 | } 297 | 298 | export default App; 299 | ~~~~~~~~ 300 | 301 | Теперь вы возвращаете только HTML из метода `render()` без всякого JavaScript. Давайте определим "Добро пожаловать в Путь к изучению React" в качестве переменной. Переменная может использоваться в JSX с использованием фигурных скобок. 302 | 303 | {title="src/App.js",lang=javascript} 304 | ~~~~~~~~ 305 | import React, { Component } from 'react'; 306 | import './App.css'; 307 | 308 | class App extends Component { 309 | render() { 310 | # leanpub-start-insert 311 | var helloWorld = 'Добро пожаловать в Путь к изучению React'; 312 | # leanpub-end-insert 313 | return ( 314 |
315 | # leanpub-start-insert 316 |

{helloWorld}

317 | # leanpub-end-insert 318 |
319 | ); 320 | } 321 | } 322 | 323 | export default App; 324 | ~~~~~~~~ 325 | 326 | Он должен заработать, когда вы запустите своё приложение в командной строке с помощью команды `npm start` снова. 327 | 328 | Кроме того, вы могли заметить атрибут `className`. Он представляет стандартный атрибут `class` в HTML. По техническим причинам в JSX пришлось заменить несколько встроенных HTML-атрибутов. Вы можете найти все [поддерживаемые HTML-атрибуты в документации к React](https://ru.reactjs.org/docs/dom-elements.html#all-supported-html-attributes). Все они следуют соглашению написания в camelCase. На вашем пути к изучению React, вы столкнётесь с некоторыми специфическими атрибутами JSX. 329 | 330 | ### Упражнения: 331 | 332 | * определите больше переменных и отрисуйте их в вашем JSX 333 | * используйте сложный объект для представления пользователя с именем и фамилией 334 | * отобразите свойства пользователя в своём JSX 335 | * ознакомьтесь подробнее с синтаксисом [JSX](https://ru.reactjs.org/docs/introducing-jsx.html) 336 | * узнайте больше про [компоненты, элементы и экземпляры в React](https://reactjs.org/blog/2015/12/18/react-components-elements-and-instances.html) 337 | 338 | ## const и let в ES6 339 | 340 | Я думаю, вы заметили, что мы объявили переменную `helloWorld`, используя выражение `var`. В JavaScript ES6 появилось два варианта для определения переменных: `const` и `let`. В JavaScript ES6 вы редко теперь найдёте использование `var` для определения переменных. 341 | 342 | Переменная, объявленная с помощью `const`, не может быть повторно объявлена или изменена (она неизменяемая). Как только структура данных определена, вы не сможете её изменить. 343 | 344 | {title="Код",lang="javascript"} 345 | ~~~~~~~~ 346 | // изменение не разрешено 347 | const helloWorld = 'Добро пожаловать в Путь к изучению React'; 348 | helloWorld = 'Пока-пока, React'; 349 | ~~~~~~~~ 350 | 351 | Переменная, объявленная с помощью `let`, может быть изменена. 352 | 353 | {title="Код",lang="javascript"} 354 | ~~~~~~~~ 355 | // изменение разрешено 356 | let helloWorld = 'Добро пожаловать в Путь к изучению React'; 357 | helloWorld = 'Пока-пока, React'; 358 | ~~~~~~~~ 359 | 360 | Вам стоит объявлять переменные через `let`, если потребуется позже повторно переназначить переменную. 361 | 362 | Однако нужно быть аккуратнее с `const`. Переменная, объявленная с использованием `const` не может быть изменена. Но в случае, если эта переменная — массив или объект, значение изменится как обычно. Подобное значение не является неизменяемым. 363 | 364 | {title="Код",lang="javascript"} 365 | ~~~~~~~~ 366 | // изменение разрешено 367 | const helloWorld = { 368 | text: 'Добро пожаловать в Путь к изучению React' 369 | }; 370 | helloWorld.text = 'Пока-пока, React'; 371 | ~~~~~~~~ 372 | 373 | Но в каких случаях следует использовать тот или иной способ определения переменной? Существуют разные мнения на этот счёт. Я предлагаю использовать `const` каждый раз при определении переменной. Это будет означать, что вы хотите иметь неизменяемую структуру данных, даже несмотря на то, что значения в объектах и массивах могут изменяться. Если переменная будет изменяемой, то вы можете использовать `let`. 374 | 375 | Неизменяемость охватывает React и его экосистему. Вот почему `const` должен быть вашим выбором по умолчанию при определении переменной. Тем не менее, в сложных объектах значения внутри могут быть изменены. Будьте осторожны с этим поведением. 376 | 377 | В вашем приложении используйте `const` вместо `var`. 378 | 379 | {title="src/App.js",lang=javascript} 380 | ~~~~~~~~ 381 | import React, { Component } from 'react'; 382 | import './App.css'; 383 | 384 | class App extends Component { 385 | render() { 386 | # leanpub-start-insert 387 | const helloWorld = 'Добро пожаловать в Путь к изучению React'; 388 | # leanpub-end-insert 389 | return ( 390 |
391 |

{helloWorld}

392 |
393 | ); 394 | } 395 | } 396 | 397 | export default App; 398 | ~~~~~~~~ 399 | 400 | ### Упражнения: 401 | 402 | * узнайте больше о [выражении const в ES6](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Statements/const) 403 | * узнайте больше о [выражении let в ES6](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Statements/let) 404 | * узнайте подробнее про неизменяемые структуры данных 405 | * почему они вообще имеют смысл в программировании 406 | * почему они используются в React и его экосистеме 407 | 408 | ## ReactDOM 409 | 410 | Прежде чем продолжить с компонентом App, возможно, вы захотите посмотреть, где он используется. Он используется в вашей точке входа в мир React — в файле *src/index.js*. 411 | 412 | {title="src/index.js",lang=javascript} 413 | ~~~~~~~~ 414 | import React from 'react'; 415 | import ReactDOM from 'react-dom'; 416 | import App from './App'; 417 | import './index.css'; 418 | 419 | ReactDOM.render( 420 | , 421 | document.getElementById('root') 422 | ); 423 | ~~~~~~~~ 424 | 425 | В основном `ReactDOM.render()` использует DOM-узел в вашей HTML-разметке для замены его вашим JSX. Вот так легко вы можете интегрировать React в любое стороннее приложение. Не запрещается использовать `ReactDOM.render()` несколько раз в приложении. Вы можете использовать его в нескольких местах для начальной загрузки простого JSX-синтаксиса, React-компонента, нескольких React-компонентов или всего приложения. Но в простом React-приложении вы будете использовать его только один раз для инициализации всего дерева компонентов. 426 | 427 | `ReactDOM.render()` ожидает два аргумента. Первый аргумент — JSX, который будет отрисовываться. Второй аргумент указывает место, где React-приложение привяжется к вашему HTML. Он ожидает элемент с `id='root'`. Вы можете открыть файл *public/index.html*, чтобы найти этот атрибут. 428 | 429 | В текущей реализации `ReactDOM.render()` уже принимает компонент App. Тем не менее, было бы неплохо передать более простой JSX. Это необязательно должно быть инстанцирование компонента. 430 | 431 | {title="Код",lang=javascript} 432 | ~~~~~~~~ 433 | ReactDOM.render( 434 |

Привет, мир React

, 435 | document.getElementById('root') 436 | ); 437 | ~~~~~~~~ 438 | 439 | ### Упражнения: 440 | 441 | * откройте файл *public/index.html*, чтобы посмотреть, где React-приложение монтируется в HTML 442 | * узнайте больше про [отрисовку элементов в React](https://ru.reactjs.org/docs/rendering-elements.html) 443 | 444 | ## Горячая перезагрузка 445 | 446 | Горячая перезагрузка модулей (Hot Module Replacement, HMR) — это то, что вы в качестве разработчика можете сделать в файле *src/index.js* для улучшения процесса разработки. 447 | 448 | По умолчанию *create-react-app* заставит обновлять страницу в браузере при изменении исходного кода. Попробуйте сами, изменив переменную `helloWorld` в файле *src/App.js*. Браузер должен обновить содержимое страницы. Но есть лучший способ сделать это. 449 | 450 | Горячая перезагрузка модулей или замена модулей без полной перезагрузки страницы — это инструмент для перезагрузки приложения в браузере. Браузер не выполняет обновление страницы. Вы можете легко активировать его в *create-react-app*. В *src/index.js*, точке входа React, вы можете добавить следующую настройку. 451 | 452 | {title="src/index.js",lang=javascript} 453 | ~~~~~~~~ 454 | import React from 'react'; 455 | import ReactDOM from 'react-dom'; 456 | import App from './App'; 457 | import './index.css'; 458 | 459 | ReactDOM.render( 460 | , 461 | document.getElementById('root') 462 | ); 463 | 464 | # leanpub-start-insert 465 | if (module.hot) { 466 | module.hot.accept(); 467 | } 468 | # leanpub-end-insert 469 | ~~~~~~~~ 470 | 471 | Вот и всё. Повторите попытку изменить переменную `helloWorld` в файле *src/App.js*. Браузер не должен обновить страницу, но приложение перезагрузится и покажет корректный (актуальный) вывод. У HMR есть несколько преимуществ: 472 | 473 | Представьте, что делаете отладку кода с использованием выражений `console.log()`. Эти выражения останутся в консоли разработчика, даже если вы изменили код, потому что браузер больше не обновляет страницу. Это может быть удобно для целей отладки. 474 | 475 | В развивающемся приложении обновление страницы задерживает вашу продуктивность. Вы должны подождать, пока страница загрузится. Перезагрузка страницы может занять несколько секунд в большом приложении. HMR устраняет этот недостаток. 476 | 477 | Наконец, самое большое преимущество HMR в том, что вы можете сохранить состояние после перезагрузки приложения. Представьте, что у вас есть диалоговое окно в вашем приложении с несколькими шагами, и вы находитесь на шаге 3. В целом, это напоминает мастер настройки. Без HMR вы измените исходный код, и браузер обновит страницу. Вам нужно снова открыть диалоговое окно и перейти с шага 1 на шаг 3. С использованием HMR диалоговое окно остаётся открытым на шаге 3. Он сохраняет состояние приложения, даже если исходный код изменяется. Перезагружается только само приложение, а не страница. 478 | 479 | ### Упражнения: 480 | 481 | * измените исходный код *src/App.js* несколько раз, чтобы увидеть работу HMR в действии 482 | * посмотрите первые 10 минут видеоролика (на английском) Дэна Абрамова (Dan Abramov) [Live React: Hot Reloading with Time Travel](https://www.youtube.com/watch?v=xsSnOQynTHs) 483 | 484 | ## Комплексный JavaScript в JSX 485 | 486 | Вернёмся к нашему компоненту App. До сих пор вы отрисовывали некоторые примитивные переменные в вашем JSX. Теперь вы начнёте отрисовывать список элементов. В начале список будет состоять из демонстрационных данных, но позже данные вы будете получать из внешнего [API](https://www.robinwieruch.de/what-is-an-api-javascript/), что будет намного интереснее. 487 | 488 | Сначала определим список элементов. 489 | 490 | {title="src/App.js",lang=javascript} 491 | ~~~~~~~~ 492 | import React, { Component } from 'react'; 493 | import './App.css'; 494 | 495 | # leanpub-start-insert 496 | const list = [ 497 | { 498 | title: 'React', 499 | url: 'https://reactjs.org/', 500 | author: 'Jordan Walke', 501 | num_comments: 3, 502 | points: 4, 503 | objectID: 0, 504 | }, 505 | { 506 | title: 'Redux', 507 | url: 'https://redux.js.org/', 508 | author: 'Dan Abramov, Andrew Clark', 509 | num_comments: 2, 510 | points: 5, 511 | objectID: 1, 512 | }, 513 | ]; 514 | # leanpub-end-insert 515 | 516 | class App extends Component { 517 | ... 518 | } 519 | ~~~~~~~~ 520 | 521 | Демо-данные представляют данные, которые будут получены позже из API. У элемента в списке есть заголовок, ссылка и автор. Кроме того, он содержит идентификатор, баллы (которые указывают, насколько популярна статья) и количество комментариев. 522 | 523 | Теперь вы можете использовать встроенную функцию JavaScript `map` в JSX. Она позволяет перебирать список элементов для их отображения. Снова вы будете использовать фигурные скобки для вставки (инкапсуляции) JavaScript-выражений в вашем JSX. 524 | 525 | {title="src/App.js",lang=javascript} 526 | ~~~~~~~~ 527 | class App extends Component { 528 | render() { 529 | return ( 530 |
531 | # leanpub-start-insert 532 | {list.map(function (item) { 533 | return
{item.title}
; 534 | })} 535 | # leanpub-end-insert 536 |
537 | ); 538 | } 539 | } 540 | 541 | export default App; 542 | ~~~~~~~~ 543 | 544 | Использование JavaScript вместе с HTML очень эффективно в JSX. Вы могли использовать `map` для преобразования одного списка элементов в другой список элементов. Но на этот раз вы можете использовать `map` для преобразования списка элементов в HTML-элементы. 545 | 546 | Пока что для каждого элемента отображается `title`. Давайте отобразим ещё больше свойств элементов. 547 | 548 | {title="src/App.js",lang=javascript} 549 | ~~~~~~~~ 550 | class App extends Component { 551 | render() { 552 | return ( 553 |
554 | # leanpub-start-insert 555 | {list.map(function (item) { 556 | return ( 557 |
558 | 559 | {item.title} 560 | 561 | {item.author} 562 | {item.num_comments} 563 | {item.points} 564 |
565 | ); 566 | })} 567 | # leanpub-end-insert 568 |
569 | ); 570 | } 571 | } 572 | 573 | export default App; 574 | ~~~~~~~~ 575 | 576 | Вы видите, что функция `map` просто встроена в ваш JSX. Каждое свойство элемента отображается в теге ``. Кроме того, свойство url элемента используется в атрибуте `href` тега ``. 577 | 578 | React выполнит всю работу за вас и отобразит каждый элемент, но вы должны добавить одного помощника для React, чтобы полностью использовать его потенциал и улучшить его производительность. Вы должны назначить атрибут key для каждого элемента списка. Таким образом, React может идентифицировать добавленные, изменённые и удалённые элементы при изменении списка. У элементов списка демо-данных уже есть идентификатор. 579 | 580 | {title="src/App.js",lang=javascript} 581 | ~~~~~~~~ 582 | {list.map(function (item) { 583 | return ( 584 | # leanpub-start-insert 585 |
586 | # leanpub-end-insert 587 | 588 | {item.title} 589 | 590 | {item.author} 591 | {item.num_comments} 592 | {item.points} 593 |
594 | ); 595 | })} 596 | ~~~~~~~~ 597 | 598 | Вы должны убедиться, что атрибут key — уникальный идентификатор. Не допускайте ошибку, использовав индекс элемента в массиве. Индекс массива вообще непостоянный. Например, когда список изменяет свой порядок, React будет трудно идентифицировать элементы правильно. 599 | 600 | {title="src/App.js",lang=javascript} 601 | ~~~~~~~~ 602 | // не делайте так 603 | {list.map(function (item, key) { 604 | return ( 605 |
606 | ... 607 |
608 | ); 609 | })} 610 | ~~~~~~~~ 611 | 612 | Теперь вы отображаете оба списка. Вы можете запустить приложение, открыть браузер и увидеть оба элемента списка. 613 | 614 | ### Упражнения: 615 | 616 | * узнайте подробнее про [списки и ключи React](https://ru.reactjs.org/docs/lists-and-keys.html) 617 | * повторите [стандартные встроенные функции массива в JavaScript](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array/) 618 | * используйте больше JavaScript-выражений в JSX 619 | 620 | ## Стрелочные функции ES6 621 | 622 | В стандарте ES6 появились стрелочные функции (arrow functions). Выражение стрелочной функции короче функциональных выражений. 623 | 624 | {title="Код",lang="javascript"} 625 | ~~~~~~~~ 626 | // объявление функции 627 | function () { ... } 628 | 629 | // объявление стрелочной функции 630 | () => { ... } 631 | ~~~~~~~~ 632 | 633 | Вам необходимо знать о функциональности стрелочных функций. Одна из их особенностей — это другое поведение с объектом `this`. Функция всегда определяет свой собственный объект `this`.Стрелочная функция получает значение `this` из окружающего контекста. 634 | 635 | Есть ещё один важный факт о стрелочных функций, касающийся круглых скобок. Вы можете удалить круглые скобки, когда функция принимает только один аргумент, но их нужно оставить в случае, если стрелочная функция принимает несколько аргументов (или ни одного — прим. пер.). 636 | 637 | {title="Код",lang="javascript"} 638 | ~~~~~~~~ 639 | // разрешено 640 | item => { ... } 641 | 642 | // разрешено 643 | (item) => { ... } 644 | 645 | // не разрешено 646 | item, key => { ... } 647 | 648 | // разрешено 649 | (item, key) => { ... } 650 | ~~~~~~~~ 651 | 652 | Давайте посмотрим на функцию `map`. Вы можете написать её более кратко с помощью стрелочных функций из ES6. 653 | 654 | {title="src/App.js",lang=javascript} 655 | ~~~~~~~~ 656 | # leanpub-start-insert 657 | {list.map(item => { 658 | # leanpub-end-insert 659 | return ( 660 |
661 | 662 | {item.title} 663 | 664 | {item.author} 665 | {item.num_comments} 666 | {item.points} 667 |
668 | ); 669 | })} 670 | ~~~~~~~~ 671 | 672 | Кроме того, вы можете удалить *тело блока*, то есть фигурные скобки стрелочной функции ES6. В *сокращённом теле блока* подразумевается неявный возврат. Таким образом, вы можете удалить выражение `return`. Такая форма стрелочной функции в книге будет использоваться чаще, поэтому убедитесь, что понимаете разницу между телом блока и сокращённым телом блока при использовании стрелочных функций. 673 | 674 | {title="src/App.js",lang=javascript} 675 | ~~~~~~~~ 676 | # leanpub-start-insert 677 | {list.map(item => 678 | # leanpub-end-insert 679 |
680 | 681 | {item.title} 682 | 683 | {item.author} 684 | {item.num_comments} 685 | {item.points} 686 |
687 | # leanpub-start-insert 688 | )} 689 | # leanpub-end-insert 690 | ~~~~~~~~ 691 | 692 | Теперь ваш JSX выглядит более кратким и читаемым. В нём нет выражения `function`, фигурных скобок и выражения `return`. Вместо это разработчик может сосредоточиться на деталях реализации. 693 | 694 | ### Упражнения: 695 | 696 | * изучите подробнее [стрелочные функции ES6](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Functions/Arrow_functions) 697 | 698 | ## Классы ES6 699 | 700 | В стандарте ES6 появился синтаксис классов. Класс обычно используется в объектно-ориентированных языках программирования. JavaScript был и есть очень гибкий в своих парадигмах программирования. Вы можете использовать функциональное программирование и объектно-ориентированное программирование бок о бок, в зависимости от конкретных случаев использования. 701 | 702 | Несмотря на то, что React охватывает функциональное программирование, например, с неизменяемыми структурами данных, классы используются для объявления компонентов. Они называются компонентами класса ES6 или классовыми компонентами ES6. React смешивает хорошие части этих парадигм программирования. 703 | 704 | Давайте рассмотрим следующий класс Developer для изучения классов JavaScript ES6, не думая о компоненте. 705 | 706 | {title="Код",lang="javascript"} 707 | ~~~~~~~~ 708 | class Developer { 709 | constructor(firstname, lastname) { 710 | this.firstname = firstname; 711 | this.lastname = lastname; 712 | } 713 | 714 | getName() { 715 | return this.firstname + ' ' + this.lastname; 716 | } 717 | } 718 | ~~~~~~~~ 719 | 720 | У класса есть конструктор, чтобы сделать его инстанцируемым (иметь возможность создавать из него объекты — прим. пер.). Конструктор может принимать аргументы, чтобы их можно было в качестве свойств назначить экземпляру класса. Кроме того, в классе можно определять функции. Поскольку функция связана с классом, она называется методом. Часто он упоминается как метод класса. 721 | 722 | Класс Developer — это только объявление класса. Вы можете создать несколько экземпляров класса путём его вызова. Он похож на компонент класса ES6, который имеет объявление, но вы должны использовать его в другом месте для создания экземпляра. 723 | 724 | Давайте посмотрим, как вы можете создать экземпляр класса и как использовать его методы. 725 | 726 | {title="Код",lang="javascript"} 727 | ~~~~~~~~ 728 | const robin = new Developer('Robin', 'Wieruch'); 729 | console.log(robin.getName()); 730 | // выведет: Robin Wieruch 731 | ~~~~~~~~ 732 | 733 | React использует классы JavaScript ES6 для классов-компонентов. Вы уже использовали один ES6-класс компонента. 734 | 735 | {title="src/App.js",lang=javascript} 736 | ~~~~~~~~ 737 | import React, { Component } from 'react'; 738 | 739 | ... 740 | 741 | class App extends Component { 742 | render() { 743 | ... 744 | } 745 | } 746 | ~~~~~~~~ 747 | 748 | Класс App наследует `Component`. В основном, когда вы объявляете компонент App, он наследуется от другого компонента. Что значит "наследуется"? В объектно-ориентированном программировании у вас есть принцип наследования, который означает, что функциональные возможности могут передаваться из одного класса в другой. 749 | 750 | Класс App наследует функциональность из класса Component. Более конкретно — он наследует функциональность из класса Component. Класс Component используется для наследования базового класса ES6 в класс компонента ES6. Он имеет всю функциональность, которую имеет компонент в React. Метод render — одна из тех функциональностей, которые вы уже использовали. В дальнейшем вы узнаете о других методах класса компонента. 751 | 752 | Класс `Component` инкапсулирует все детали реализации компонента React. Это позволяет разработчикам использовать классы как компоненты в React. 753 | 754 | Методы, предоставляемые компонентом `Component`, являются открытым интерфейсом. Один из этих методов должен быть переопределён, другие не должны быть переопределены. Вы узнаете о последних методах, когда позже в этой книге будут рассматриваться методы жизненного цикла. Метод `render()` должен быть переопределён, поскольку он определяет вывод компонента React `Component`. 755 | 756 | В данный момент вы узнали об основах классов JavaScript ES6 и о том, как они используются в React для создания компонентов. Вы узнаете подробнее о методах Component, когда в книге будут описаны методы жизненного цикла React. 757 | 758 | ### Упражнения: 759 | 760 | * узнайте получше [классы в ES6](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Classes) 761 | * убедитесь, что знаете хорошо [основы JavaScript перед тем как перейти к изучению React](https://www.robinwieruch.de/javascript-fundamentals-react-requirements/) 762 | 763 | {pagebreak} 764 | 765 | Вы научились создавать своё собственное React-приложение! Давайте вспомним последние темы: 766 | 767 | * React 768 | * create-react-app для создания заготовки приложения React 769 | * JSX смешивает HTML и JavaScript для определения вывода React-компонентов в своих методах render 770 | * компоненты, экземпляры и элементы — разные понятия в React 771 | * `ReactDOM.render()` — это точка входа для React-приложения, которая привязывает React к DOM 772 | * встроенная функциональность JavaScript может использоваться в JSX 773 | * функцию `map` можно использовать для отрисовки списка элементов как HTML-элементов 774 | * ES6 775 | * объявления переменных с помощью `const` и `let` могут использоваться в зависимости от конкретных случаев 776 | * использование `const` вместо `let` в React-приложениях 777 | * стрелочные функции используются для краткого написания ваших функций 778 | * классы используются для определения компонентов в React путём их наследования 779 | 780 | На данном этапе имеет смысл сделать перерыв. Усвоить полученные знания и применить их на практике самостоятельно. Вы можете поэкспериментировать с исходным кодом, написанным в рамках этой главы. Его можно найти в [официальном репозитории](https://github.com/the-road-to-learn-react/hackernews-client/tree/5.1). 781 | -------------------------------------------------------------------------------- /manuscript/chapter4.md: -------------------------------------------------------------------------------- 1 | # Организация и тестирование кода 2 | 3 | В этой главе мы сосредоточим внимание на важных темах сохранения кода удобным для поддержки в масштабируемом приложении. Вы узнаете об организации кода, чтобы освоить лучшие способы структурирования ваших папок и файлов. Ещё один аспект, который вы узнаете, — это тестирование, которое важно для написания надёжного кода. Наконец, вы узнаете о полезном инструменте для отладки ваших React-приложений. Бо́льшая часть главы отойдёт в сторону от практического приложения и объяснит вам пару этих тем. 4 | 5 | ## Модули ES6: импорт и экспорт 6 | 7 | В JavaScript ES6 вы можете импортировать и экспортировать функциональность из модулей. Этой функциональностью могут быть функции, классы, компоненты, константы и т.д. В основном это всё то, что вы можете присвоить переменной. Модули могут быть отдельными файлами или целыми папками с одним индексным файлом (index.js) в качестве точки входа. 8 | 9 | В начале книги после того, как вы инициализировали приложение с помощью *create-react-app*, вы уже сталкивались с несколькими выражениями `import` и `export` в ваших файлах исходного кода. Настало время объяснить, зачем всё это. 10 | 11 | Выражения `import` и `export` помогают распространять код между несколькими файлами. Раньше в среде JavaScript было несколько решений для этой цели. Это был беспорядок, потому что вы, вероятно, хотели бы следовать стандартизированному способу, а не иметь несколько подходов к одному и тому же. Теперь это нативная возможность, начиная с JavaScript ES6. 12 | 13 | Кроме того, эти выражения охватывает идею разделения кода. Вы разделяете свой код между несколькими файлами для возможности сделать его многоразовым и поддерживаемым. Первое верно, потому что вы можете импортировать часть кода в несколько файлов. Последнее верно, потому что у вас есть один источник, в котором вы поддерживаете часть кода. 14 | 15 | И последнее, но не менее важное: это поможет вам подумать о инкапсуляции кода. Не каждая функциональность должна быть экспортирована из файла. Некоторые из этих функциональных возможностей должны использоваться только в файле, где они были определены. Экспорт файла в основном означает общедоступный API для использования в другом файле. Доступны для повторного использования в другом месте кодовой базы только экспортированные функциональные возможности. Это следует передовой практике инкапсуляции. 16 | 17 | Но давайте перейдём к практике. Как эти выражения `import` и `export` работают? В следующих примерах приводятся выражения, совместно использующие одну или несколько переменных в двух файлах. В конце концов, этот подход можно масштабировать на несколько файлов и обмениваться не только простыми переменными. 18 | 19 | Вы можете экспортировать одну или несколько переменных. Это называется именованным экспортом. 20 | 21 | {title="Код: file1.js",lang="javascript"} 22 | ~~~~~~~~ 23 | const firstname = 'Robin'; 24 | const lastname = 'Wieruch'; 25 | 26 | export { firstname, lastname }; 27 | ~~~~~~~~ 28 | 29 | И импортируйте их в другой файл с помощью относительного пути к первому файлу. 30 | 31 | {title="Код: file2.js",lang="javascript"} 32 | ~~~~~~~~ 33 | import { firstname, lastname } from './file1.js'; 34 | 35 | console.log(firstname); 36 | // выведет: Robin 37 | ~~~~~~~~ 38 | 39 | Вы также можете импортировать все экспортируемые данные из другого файла в виде одного объекта. 40 | 41 | {title="Код: file2.js",lang="javascript"} 42 | ~~~~~~~~ 43 | import * as person from './file1.js'; 44 | 45 | console.log(person.firstname); 46 | // выведет: Robin 47 | ~~~~~~~~ 48 | 49 | У выражений импорта могут быть псевдонимы. Может произойти такая ситуация, когда вы импортируете функциональность из нескольких файлов с одним и тем же именем при экспорте. По этой причине вы можете использовать псевдоним. 50 | 51 | {title="Код: file2.js",lang="javascript"} 52 | ~~~~~~~~ 53 | import { firstname as username } from './file1.js'; 54 | 55 | console.log(username); 56 | // выведет: Robin 57 | ~~~~~~~~ 58 | 59 | И последнее, но не менее важное: существует выражение по умолчанию — `default`. Его можно использовать в следующих случаях: 60 | * для экспорта и импорта единственной функциональной возможности 61 | * для выделения основных функциональных возможностей экспортированного API модуля 62 | * для того, чтобы иметь резервную (фолбэк) функциональность при импорте 63 | 64 | {title="Код: file1.js",lang="javascript"} 65 | ~~~~~~~~ 66 | const robin = { 67 | firstname: 'Robin', 68 | lastname: 'Wieruch', 69 | }; 70 | 71 | export default robin; 72 | ~~~~~~~~ 73 | 74 | Вы можете опустить фигурные скобки при импорте по умолчанию. 75 | 76 | {title="Код: file2.js",lang="javascript"} 77 | ~~~~~~~~ 78 | import developer from './file1.js'; 79 | 80 | console.log(developer); 81 | // выведет: { firstname: 'Robin', lastname: 'Wieruch' } 82 | ~~~~~~~~ 83 | 84 | Кроме того, название импорта можно различать от экспортированного названия по умолчанию. Вы также можете использовать это в сочетании с именованными выражениями экспорта и импорта. 85 | 86 | {title="Код: file1.js",lang="javascript"} 87 | ~~~~~~~~ 88 | const firstname = 'Robin'; 89 | const lastname = 'Wieruch'; 90 | 91 | const person = { 92 | firstname, 93 | lastname, 94 | }; 95 | 96 | export { 97 | firstname, 98 | lastname, 99 | }; 100 | 101 | export default person; 102 | ~~~~~~~~ 103 | 104 | Затем импортируйте импорт по умолчанию, а также экспорт по имени в другой файл. 105 | 106 | {title="Код: file2.js",lang="javascript"} 107 | ~~~~~~~~ 108 | import developer, { firstname, lastname } from './file1.js'; 109 | 110 | console.log(developer); 111 | // выведет: { firstname: 'Robin', lastname: 'Wieruch' } 112 | console.log(firstname, lastname); 113 | // выведет: Robin Wieruch 114 | ~~~~~~~~ 115 | 116 | При использовании именованного экспорта вы можете сэкономить дополнительные строки и непосредственно экспортировать переменные. 117 | 118 | {title="Код: file1.js",lang="javascript"} 119 | ~~~~~~~~ 120 | export const firstname = 'Robin'; 121 | export const lastname = 'Wieruch'; 122 | ~~~~~~~~ 123 | 124 | Это основная функциональность модулей ES6. Они помогают вам организовать собственный код, поддерживать его и проектировать API-интерфейсы повторно используемых модулей. Вы также можете экспортировать и импортировать функциональность для их тестирования. Вы сделаете это в одной из следующих глав. 125 | 126 | ### Упражнения: 127 | 128 | * ознакомьтесь подробнее с [импортом в ES6](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Statements/import) 129 | * ознакомьтесь подробнее с [экспортом в ES6](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Statements/export) 130 | 131 | ## Организация кода с помощью модулей в ES6 132 | 133 | Вы могли бы задаться вопросом: почему мы не последовали передовому опыту разделения кода для файла *src/App.js*? В этом файле у нас уже есть несколько компонентов, которые могут быть определены в собственных файлах/папках (модулях). В интересах изучения React, целесообразно хранить их в одном месте. Но как только ваше приложение React растёт, вам следует рассмотреть возможность разделения этих компонентов на несколько модулей. Только так ваше приложение масштабируется. 134 | 135 | Ниже я предлагаю несколько модулей, которые вы *могли* бы применить. Я бы рекомендовал применить их в качестве упражнения в конце книги. Чтобы книга оставалась простой, я не буду выполнять разделение кода и продолжу следующие главы с файлом *src/App.js*. 136 | 137 | Одной из возможных структур модулей могла быть такая: 138 | 139 | {title="Структура каталогов",lang="text"} 140 | ~~~~~~~~ 141 | src/ 142 | index.js 143 | index.css 144 | App.js 145 | App.test.js 146 | App.css 147 | Button.js 148 | Button.test.js 149 | Button.css 150 | Table.js 151 | Table.test.js 152 | Table.css 153 | Search.js 154 | Search.test.js 155 | Search.css 156 | ~~~~~~~~ 157 | 158 | Такой подход разделяет компоненты на отдельные файлы, но он не выглядит слишком перспективным. Вы можете видеть много дублирования названий, которые различаются только расширением файла. Совершенно другой структурой модулей может быть подобное разделение: 159 | 160 | {title="Структура каталогов",lang="text"} 161 | ~~~~~~~~ 162 | src/ 163 | index.js 164 | index.css 165 | App/ 166 | index.js 167 | test.js 168 | index.css 169 | Button/ 170 | index.js 171 | test.js 172 | index.css 173 | Table/ 174 | index.js 175 | test.js 176 | index.css 177 | Search/ 178 | index.js 179 | test.js 180 | index.css 181 | ~~~~~~~~ 182 | 183 | Это уже выглядит чище, чем предыдущий способ. Файлы с названием index описывает его как файл точки входа в папку (модуль). Это просто обычное соглашение (правило) об наименовании, но вы также можете использовать собственное соглашение. В этой структуре модулей компонент определяется объявлением компонента в файле JavasScript (index.js) , а также его стилем (index.css) и тестами для него (test.js). 184 | 185 | Следующим шагом могло бы быть стать извлечение констант из компонента App. Эти константы использовались для формирования URL к API Hacker News. 186 | 187 | {title="Структура каталогов",lang="text"} 188 | ~~~~~~~~ 189 | src/ 190 | index.js 191 | index.css 192 | # leanpub-start-insert 193 | constants/ 194 | index.js 195 | components/ 196 | # leanpub-end-insert 197 | App/ 198 | index.js 199 | test.js 200 | index..css 201 | Button/ 202 | index.js 203 | test.js 204 | index..css 205 | ... 206 | ~~~~~~~~ 207 | 208 | Естественно, что модули бы разделились на *src/constants/* и *src/components/*. Теперь файл *src/constants/index.js* может выглядеть следующим образом: 209 | 210 | {title="Код: src/constants/index.js",lang="javascript"} 211 | ~~~~~~~~ 212 | export const DEFAULT_QUERY = 'redux'; 213 | export const DEFAULT_HPP = '100'; 214 | export const PATH_BASE = 'https://hn.algolia.com/api/v1'; 215 | export const PATH_SEARCH = '/search'; 216 | export const PARAM_SEARCH = 'query='; 217 | export const PARAM_PAGE = 'page='; 218 | export const PARAM_HPP = 'hitsPerPage='; 219 | ~~~~~~~~ 220 | 221 | В файле *App/index.js* вы можете импортировать эти константы для использования. 222 | 223 | {title="Код: src/components/App/index.js",lang=javascript} 224 | ~~~~~~~~ 225 | import { 226 | DEFAULT_QUERY, 227 | DEFAULT_HPP, 228 | PATH_BASE, 229 | PATH_SEARCH, 230 | PARAM_SEARCH, 231 | PARAM_PAGE, 232 | PARAM_HPP, 233 | } from '../constants/index.js'; 234 | 235 | ... 236 | ~~~~~~~~ 237 | 238 | Когда вы используете соглашение по именованию, включающее *index.js*, вы можете опустить название файла из относительно пути. 239 | 240 | {title="Код: src/components/App/index.js",lang=javascript} 241 | ~~~~~~~~ 242 | import { 243 | DEFAULT_QUERY, 244 | DEFAULT_HPP, 245 | PATH_BASE, 246 | PATH_SEARCH, 247 | PARAM_SEARCH, 248 | PARAM_PAGE, 249 | PARAM_HPP, 250 | # leanpub-start-insert 251 | } from '../constants'; 252 | # leanpub-end-insert 253 | 254 | ... 255 | ~~~~~~~~ 256 | 257 | Но что стоит за именованием файлов *index.js*? Соглашение было введено в мире Node.js. Индексный файл является точкой входа в модуль. Он описывает общедоступный API для модуля. Внешним модулям разрешено использовать файл *index.js* для импорта разделяемого кода из модуля. Рассмотрим следующую составленную структуру модуля, чтобы продемонстрировать её: 258 | 259 | {title="Структура каталогов",lang="text"} 260 | ~~~~~~~~ 261 | src/ 262 | index.js 263 | App/ 264 | index.js 265 | Buttons/ 266 | index.js 267 | SubmitButton.js 268 | SaveButton.js 269 | CancelButton.js 270 | ~~~~~~~~ 271 | 272 | В каталоге *Buttons/* есть несколько компонентов, определённых в разных файлах. Каждый файл компонента может использовать `export default`, чтобы его можно было импортировать в файле *Buttons/index.js*. Файл *Buttons/index.js* импортирует все различные представления кнопок и экспортирует их в качестве общедоступного API модуля. 273 | 274 | {title="Код: src/Buttons/index.js",lang="javascript"} 275 | ~~~~~~~~ 276 | import SubmitButton from './SubmitButton'; 277 | import SaveButton from './SaveButton'; 278 | import CancelButton from './CancelButton'; 279 | 280 | export { 281 | SubmitButton, 282 | SaveButton, 283 | CancelButton, 284 | }; 285 | ~~~~~~~~ 286 | 287 | Теперь из файла *src/App/index.js* можно импортировать кнопки из общедоступного API модуля, расположенного в файле *index.js*. 288 | 289 | {title="Код: src/App/index.js",lang=javascript} 290 | ~~~~~~~~ 291 | import { 292 | SubmitButton, 293 | SaveButton, 294 | CancelButton 295 | } from '../Buttons'; 296 | ~~~~~~~~ 297 | 298 | Рассматривая ограничения данного подхода, было бы неудачной практикой импортировать функциональность из других файловых путей, отличных от *index.js*, в модуле. Это нарушит правила инкапсуляции. 299 | 300 | {title="Код: src/App/index.js",lang=javascript} 301 | ~~~~~~~~ 302 | // порочная практика, не делайте так 303 | import SubmitButton from '../Buttons/SubmitButton'; 304 | ~~~~~~~~ 305 | 306 | Теперь вы знаете, как можно отрефакторить исходный код в модулях с ограничениями инкапсуляции. Как я уже сказал, ради сохранения простоты в книге я не буду использовать описанные выше изменения. Но вам стоит заняться рефакторингом после прочтения книги. 307 | 308 | ### Упражнения: 309 | 310 | * займитесь рефакторингом файла *src/App.js*, разделив его на несколько модулей-компонентов, когда закончите книгу 311 | 312 | ## Тестирование снимками с помощью Jest 313 | 314 | Данная книга не будет глубоко погружаться в тему тестирования, но это не значит, что её следует игнорировать. Тестирование кода в программировании имеет важное значение и должно рассматриваться в качестве обязательного требования, если вы хотите, чтобы качество вашего кода было высоким и всё работало как надо. 315 | 316 | Возможно, вы слышали о пирамиде тестирования. Существуют сквозные (end-to-end), интеграционные (integration) и модульные (unit) тесты. Если вы не знакомы с ними, то в книге приводится быстрый и краткий обзор. Модульный тест служит для проверки изолированного и небольшого куска кода. Это может быть всего лишь одна функция, проверяемая модульным тестом. Тем не менее, иногда модульные тесты работают хорошо в изоляции, но не работают в сочетании с другими модульными тестами. Поэтому их нужно тестировать группой из модульных тестов. Вот где интеграционные тесты могут помочь, учитывая, что они неплохо работают вместе. И последнее, но не менее важное: сквозной тест — это имитация реального использования приложения пользователем. Это может быть автоматическая настройка в браузере, имитирующая процесс входа пользователя в приложение. Если модульные тесты быстро и легко писать и поддерживать, то к сквозным тестам это не относится. 317 | 318 | Сколько тестов нужно для каждого типа тестирования? Скорее всего, вы хотите, чтобы было как можно больше модульных тестов для покрытия тестами функций в изоляции. После этого вы можете провести несколько интеграционных тестов, чтобы проверить, что наиболее важные функции работают в сочетании вместе, как ожидалось. И наконец, что немаловажно, вам может потребоваться только несколько сквозных тестов для симулирования критических сценариев использования приложения. На этом общая экскурсия в мир тестирования заканчивается. 319 | 320 | Итак, как можно применить полученные знания для тестировании своего приложения на React? Основа тестирования в React — это тесты компонентов, которые можно тестировать модульными тестами, а часть из них — тестированием снимками (snapshot). Вы напишите модульные тесты для своих компонентов в следующей главе, используя библиотеку Enzyme. В этой главе вы остановитесь на другом типе тестов — тестов снимками. Для данного тестирования применяется библиотека под названием Jest. 321 | 322 | [Jest](https://jestjs.io/) — это платформа для тестирования JavaScript, используемая и разработанная в Facebook. В сообществе React она используется для тестирования React-компонентов. К счастью, *create-react-app* уже поставляется с Jest, поэтому вам не нужно дополнительно его настраивать. 323 | 324 | Давайте начнём тестировать ваши первые компоненты. Прежде чем приступить к этому, вам нужно экспортировать компоненты, которые вы собираетесь тестировать, из файла *src/App.js*. После этого вы можете протестировать их в другом файле. Об этом вы узнали в разделе про организацию кода. 325 | 326 | {title="src/App.js",lang=javascript} 327 | ~~~~~~~~ 328 | ... 329 | 330 | class App extends Component { 331 | ... 332 | } 333 | 334 | ... 335 | 336 | export default App; 337 | 338 | # leanpub-start-insert 339 | export { 340 | Button, 341 | Search, 342 | Table, 343 | }; 344 | # leanpub-end-insert 345 | ~~~~~~~~ 346 | 347 | В файле *App.test.js* вы найдёте ваш первый тест, который появился при инициализации *create-react-app*. Он проверяет, что компонент App отображается без каких-либо ошибок. 348 | 349 | {title="src/App.test.js",lang=javascript} 350 | ~~~~~~~~ 351 | import React from 'react'; 352 | import ReactDOM from 'react-dom'; 353 | import App from './App'; 354 | 355 | it('отрисовывает без ошибки', () => { 356 | const div = document.createElement('div'); 357 | ReactDOM.render(, div); 358 | ReactDOM.unmountComponentAtNode(div); 359 | }); 360 | ~~~~~~~~ 361 | 362 | Блок «it» представляет собой один тест. Этот блок принимает описание теста и собственно код теста, который может либо пройти успешно, либо потерпеть неудачу (провалиться). Кроме того, вы можете обернуть его в блок «describe», который определяет набор тестов. Набор тестов может включать в себя множество блоков «it» для одного конкретного компонента. Мы рассмотрим блоки «describe» чуть позже. Оба этих блока используются для разделения и организации ваших тестов. 363 | 364 | Обратите внимание, что функция `it` считается в сообществе JavaScript функцией, в которой выполняется один тест. Однако в Jest часто встречается псевдоним `test` для функции тестирования. 365 | 366 | Вы можете запускать свои тесты с помощью интерактивного скрипта тестирования приложения, предоставляемого *create-react-app* в командной строке. Вам будет показан вывод по каждому тесту в терминале. 367 | 368 | {title="Командная строка",lang="text"} 369 | ~~~~~~~~ 370 | npm test 371 | ~~~~~~~~ 372 | 373 | Теперь Jest позволяет создавать снимки. Эти тесты делают снимок вашего отрисованного компонента и сравнивает этот снимок с будущими снимками. При изменении будущих снимков, будут отображаться соответствующие уведомления в тесте. Вы можете принять изменение снимка, потому что вы специально изменили реализацию компонента, либо отказать в изменении снимка, что означает, что была допущена ошибка, которую нужно исправить. Jest очень хорошо дополняет модульные тесты, потому что проверяются только различия в отрисованных компонентов в разные моменты времени. Кроме того, это не добавляет больших затрат на поддержку таких тестов, потому что вы можете просто принимать изменённые снимки, когда вы намеренно что-то изменили, что повлияло на отрисовку компонента. 374 | 375 | Jest хранит снимки в каталоге `snapshots`. Только таким образом он может проверить различия с будущим снимком. Кроме того, снимками можно обмениваться между командами, если они находятся в одной папке. 376 | 377 | Перед написанием первого теста снимка с помощью Jest вам необходимо установить следующую библиотеку. 378 | 379 | {title="Командная строка",lang="text"} 380 | ~~~~~~~~ 381 | npm install --save-dev react-test-renderer 382 | ~~~~~~~~ 383 | 384 | Теперь вы можете расширить тест компонента App с помощью своего первого снимка. Во-первых, импортируйте новую функциональность из node-пакета и поместите свой тестируемый код компонента App в блок «it», а его в свою очередь в блок «describe». В данном случае тестовый набор предназначен только для компонента App. 385 | 386 | {title="src/App.test.js",lang=javascript} 387 | ~~~~~~~~ 388 | import React from 'react'; 389 | import ReactDOM from 'react-dom'; 390 | # leanpub-start-insert 391 | import renderer from 'react-test-renderer'; 392 | # leanpub-end-insert 393 | import App from './App'; 394 | 395 | # leanpub-start-insert 396 | describe('App', () => { 397 | 398 | # leanpub-end-insert 399 | it('отрисовывает без ошибки', () => { 400 | const div = document.createElement('div'); 401 | ReactDOM.render(, div); 402 | ReactDOM.unmountComponentAtNode(div); 403 | }); 404 | # leanpub-start-insert 405 | 406 | }); 407 | # leanpub-end-insert 408 | ~~~~~~~~ 409 | 410 | Теперь вы можете написать свой первый тест снимком с помощью блока «test». 411 | 412 | {title="src/App.test.js",lang=javascript} 413 | ~~~~~~~~ 414 | import React from 'react'; 415 | import ReactDOM from 'react-dom'; 416 | import renderer from 'react-test-renderer'; 417 | import App from './App'; 418 | 419 | describe('App', () => { 420 | 421 | it('отрисовывает без ошибки', () => { 422 | const div = document.createElement('div'); 423 | ReactDOM.render(, div); 424 | ReactDOM.unmountComponentAtNode(div); 425 | }); 426 | 427 | # leanpub-start-insert 428 | test('есть корректный снимок', () => { 429 | const component = renderer.create( 430 | 431 | ); 432 | const tree = component.toJSON(); 433 | expect(tree).toMatchSnapshot(); 434 | }); 435 | 436 | # leanpub-end-insert 437 | }); 438 | ~~~~~~~~ 439 | 440 | Запустите ваши тесты снова и посмотрите, какие из них были выполнены успешно или неудачно. Все они должны пройти. Если вы измените вывод блока отрисовки в компоненте App, тест снимками должен завершиться неудачно. Затем вы можете решить, принять обновление снимка или выяснить причину ошибки в компоненте App. 441 | 442 | В основном функция `renderer.create()` создаёт снимок вашего компонента App. Она отрисовывает компонент виртуально и сохраняет полученный DOM в снимок. После этого снимок, как ожидается, будет соответствовать предыдущему снимку с момента последнего запуска проверки снимков. Таким образом, вы можете утверждать, что ваш DOM остался таким же и ничего не поменялось по ошибке. 443 | 444 | Давайте добавим больше тестов для наших независимых компонентов. Во-первых, для компонента Search: 445 | 446 | {title="src/App.test.js",lang=javascript} 447 | ~~~~~~~~ 448 | import React from 'react'; 449 | import ReactDOM from 'react-dom'; 450 | import renderer from 'react-test-renderer'; 451 | # leanpub-start-insert 452 | import App, { Search } from './App'; 453 | # leanpub-end-insert 454 | 455 | ... 456 | 457 | # leanpub-start-insert 458 | describe('Search', () => { 459 | 460 | it('отрисовывает без ошибки', () => { 461 | const div = document.createElement('div'); 462 | ReactDOM.render(Поиск, div); 463 | ReactDOM.unmountComponentAtNode(div); 464 | }); 465 | 466 | test('есть корректный снимок', () => { 467 | const component = renderer.create( 468 | Поиск 469 | ); 470 | const tree = component.toJSON(); 471 | expect(tree).toMatchSnapshot(); 472 | }); 473 | 474 | }); 475 | # leanpub-end-insert 476 | ~~~~~~~~ 477 | 478 | У компонента Search два теста, аналогичных компоненту App. Первый тест просто отрисовывает компонент Search в DOM и проверяет, что во время процесса отрисовки не произошла ошибка. Если ошибка возникла, тест провалится, даже если в тестовом блоке нет никакого утверждения (например, `expect`, `match`, `equal`). Второй — тест снимком — используется для хранения снимка отрисованного компонента и сравнивает его с предыдущего снимком. Этот тест закончится неудачей, когда снимок изменится. 479 | 480 | Во-вторых, вы можете протестировать компонент Button, так как применяются те же правила тестирования, что и в компоненте Search. 481 | 482 | {title="src/App.test.js",lang=javascript} 483 | ~~~~~~~~ 484 | ... 485 | # leanpub-start-insert 486 | import App, { Search, Button } from './App'; 487 | # leanpub-end-insert 488 | 489 | ... 490 | 491 | # leanpub-start-insert 492 | describe('Button', () => { 493 | 494 | it('отрисовывает без ошибки', () => { 495 | const div = document.createElement('div'); 496 | ReactDOM.render(, div); 497 | ReactDOM.unmountComponentAtNode(div); 498 | }); 499 | 500 | test('есть корректный снимок', () => { 501 | const component = renderer.create( 502 | 503 | ); 504 | const tree = component.toJSON(); 505 | expect(tree).toMatchSnapshot(); 506 | }); 507 | 508 | }); 509 | # leanpub-end-insert 510 | ~~~~~~~~ 511 | 512 | И последнее, но не менее важное: компоненту Table вы можете передать много первоначальных свойств, чтобы отрисовать с демонстрационном списком. 513 | 514 | {title="src/App.test.js",lang=javascript} 515 | ~~~~~~~~ 516 | ... 517 | # leanpub-start-insert 518 | import App, { Search, Button, Table } from './App'; 519 | # leanpub-end-insert 520 | 521 | ... 522 | 523 | # leanpub-start-insert 524 | describe('Table', () => { 525 | 526 | const props = { 527 | list: [ 528 | { title: '1', author: '1', num_comments: 1, points: 2, objectID: 'y' }, 529 | { title: '2', author: '2', num_comments: 1, points: 2, objectID: 'z' }, 530 | ], 531 | }; 532 | 533 | it('отрисовывает без ошибки', () => { 534 | const div = document.createElement('div'); 535 | ReactDOM.render(, div); 536 | }); 537 | 538 | test('есть корректный снимок', () => { 539 | const component = renderer.create( 540 |
541 | ); 542 | const tree = component.toJSON(); 543 | expect(tree).toMatchSnapshot(); 544 | }); 545 | 546 | }); 547 | # leanpub-end-insert 548 | ~~~~~~~~ 549 | 550 | Тесты снимком обычно остаются довольно простыми. Вам нужно лишь заметить, что компонент не изменил свой отрисованный вывод. После изменения вывода вы должны решить, принимаете ли вы эти изменения или нет. В противном случае вам нужно исправить компонент, если его вывод отрисовки не такой, как ожидался. 551 | 552 | ### Упражнения: 553 | 554 | * посмотрите, как тест снимком завершится неудачно после изменения возвращаемого значения компонента в методе `render()` 555 | * и либо примите, либо отклоните изменение снимка 556 | * обновите свои снимки при обновлении компонентов в следующих главах 557 | * узнайте подробнее про использование [Jest в React](https://jestjs.io/docs/en/tutorial-react) 558 | 559 | ## Модульное тестирование с помощью Enzyme 560 | 561 | [Enzyme](https://github.com/airbnb/enzyme) — это утилита тестирования от Airbnb для проверки утверждений, манипулирования и навигации по компонентам React. Вы можете использовать её для проведения модульных тестов в дополнение к вашим снимкам в React. 562 | 563 | Давайте посмотрим, как вы можете использовать Enzyme. Сначала нужно установить его, поскольку он не поставляется по умолчанию в *create-react-app*. У Enzyme также есть расширение для использования в React. 564 | 565 | {title="Командная строка",lang="text"} 566 | ~~~~~~~~ 567 | npm install --save-dev enzyme react-addons-test-utils enzyme-adapter-react-16 568 | ~~~~~~~~ 569 | 570 | Во-вторых, вам нужно подключить Enzyme и инициализировать его адаптер для использования в React. 571 | 572 | {title="src/App.test.js",lang=javascript} 573 | ~~~~~~~~ 574 | import React from 'react'; 575 | import ReactDOM from 'react-dom'; 576 | import renderer from 'react-test-renderer'; 577 | # leanpub-start-insert 578 | import Enzyme from 'enzyme'; 579 | import Adapter from 'enzyme-adapter-react-16'; 580 | # leanpub-end-insert 581 | import App, { Search, Button, Table } from './App'; 582 | 583 | # leanpub-start-insert 584 | Enzyme.configure({ adapter: new Adapter() }); 585 | # leanpub-end-insert 586 | ~~~~~~~~ 587 | 588 | Теперь вы можете написать свой первый модульный тест для компонента Table в блоке «describe». Вы будете использовать `shallow()` для отрисовки вашего компонента и проверять, что у отрисованного компонента Table два элемента, так как ему было передано два элемента списка. Утверждение просто проверяет, есть у элемента таблицы два дочерних элемента с классом `table-row`. 589 | 590 | {title="src/App.test.js",lang=javascript} 591 | ~~~~~~~~ 592 | import React from 'react'; 593 | import ReactDOM from 'react-dom'; 594 | import renderer from 'react-test-renderer'; 595 | # leanpub-start-insert 596 | import Enzyme, { shallow } from 'enzyme'; 597 | # leanpub-end-insert 598 | import Adapter from 'enzyme-adapter-react-16'; 599 | import App, { Search, Button, Table } from './App'; 600 | 601 | // ... 602 | 603 | describe('Table', () => { 604 | 605 | const props = { 606 | list: [ 607 | { title: '1', author: '1', num_comments: 1, points: 2, objectID: 'y' }, 608 | { title: '2', author: '2', num_comments: 1, points: 2, objectID: 'z' }, 609 | ], 610 | }; 611 | 612 | ... 613 | 614 | # leanpub-start-insert 615 | it('shows two items in list', () => { 616 | const element = shallow( 617 |
618 | ); 619 | 620 | expect(element.find('.table-row').length).toBe(2); 621 | }); 622 | # leanpub-end-insert 623 | 624 | }); 625 | ~~~~~~~~ 626 | 627 | Функция `shallow` отрисовывает компонент без его дочерних компонентов. Таким образом, вы можете написать тест, проверяющий непосредственно сам компонент. 628 | 629 | У Enzyme есть три общих механизма отрисовки, доступные в API. Вы уже знаете `shallow()`, но также существуют `mount()` и `render()`. Они оба создают экземпляры родительского компонента и всех его дочерних компонентов. Кроме того `mount()` предоставляет вам доступ к методам жизненного цикла компонента. Но какой механизм отрисовки использовать? Существует следующие проверенные на практике правила: 630 | 631 | * Всегда начинайте с поверхностного (shallow) теста 632 | * Если необходимо проверить `componentDidMount()` или `componentDidUpdate()`, используйте `mount()` 633 | * Если хотите протестировать жизненный цикл компонентов и поведение дочерних элементов, используйте `mount()` 634 | * Если хотите протестировать отрисовку дочерних элементов компонента с меньшими накладными расходами, чем `mount()`, и вам не интересны методы жизненного цикла, используйте `render()` 635 | 636 | Вы можете продолжить тестирование своих компонентов. Но следите за тем, чтобы тесты были простыми и удобными для дальнейшей поддержки. В противном случае вам придётся рефакторить их, как только вы измените свои компоненты. Именно поэтому Facebook в первую очередь представил инструмент для тестирования снимками Jest. 637 | 638 | ### Упражнения: 639 | 640 | * напишите модульный тест с использованием Enzyme для компонента Button 641 | * постоянно выполняйте и актуализируйте при необходимости свои модульные тесты в следующих главах 642 | * узнайте больше про [Enzyme и о его API отрисовки](https://github.com/airbnb/enzyme) 643 | 644 | ## Интерфейс компонента с помощью PropTypes 645 | 646 | Возможно вы знаете [TypeScript](https://www.typescriptlang.org/) или [Flow](https://flowtype.org/) для того, чтобы ввести интерфейс типа к JavaScript. Типизированный язык менее подвержен ошибкам, поскольку код проверяется на основе его текста программы. Редакторы и другие утилиты могут поймать эти ошибки до запуска программы. Это делает вашу программу более надёжной. 647 | 648 | В книге мы не будем использовать Flow или TypeScript, а вместо этого представим ещё один аккуратный способ проверить свои типы в компонентах. React поставляется со встроенным инструментом проверки типов для предотвращения ошибок. Вы можете использовать PropTypes для описания интерфейса вашего компонента. Все свойства, которые передаются от родительского компонента к дочернему компоненту, проверяются на основе интерфейса PropTypes, назначенному дочернему компоненту. 649 | 650 | В разделе этой главы показывается, как вы можете сделать компоненты безопасно типизированными с помощью PropTypes. Я пропускаю данные изменения для следующих глав, потому что они добавляют ненужные улучшения кода. Но вы должны сохранить и обновить их, чтобы сохранить тип интерфейса вашего компонента безопасным. 651 | 652 | Во-первых, вам нужно установить отдельный пакет для React. 653 | 654 | {title="Командная строка",lang="text"} 655 | ~~~~~~~~ 656 | npm install prop-types 657 | ~~~~~~~~ 658 | 659 | Теперь вы можете импортировать PropTypes. 660 | 661 | {title="src/App.js",lang=javascript} 662 | ~~~~~~~~ 663 | import React, { Component } from 'react'; 664 | import axios from 'axios'; 665 | # leanpub-start-insert 666 | import PropTypes from 'prop-types'; 667 | # leanpub-end-insert 668 | ~~~~~~~~ 669 | 670 | Давайте установим типы для свойств в компонентах: 671 | 672 | {title="src/App.js",lang=javascript} 673 | ~~~~~~~~ 674 | const Button = ({ 675 | onClick, 676 | className = '', 677 | children, 678 | }) => 679 | 686 | 687 | # leanpub-start-insert 688 | Button.propTypes = { 689 | onClick: PropTypes.func, 690 | className: PropTypes.string, 691 | children: PropTypes.node, 692 | }; 693 | # leanpub-end-insert 694 | ~~~~~~~~ 695 | 696 | В принципе, вот и всё. Вы выбираете каждый аргумент из определения функции и присваиваете ему PropType. Основные PropTypes для примитивных типов данных и сложных объектов перечислены ниже: 697 | 698 | * PropTypes.array 699 | * PropTypes.bool 700 | * PropTypes.func 701 | * PropTypes.number 702 | * PropTypes.object 703 | * PropTypes.string 704 | 705 | Однако, у вас есть ещё два PropTypes для определения отрисованного фрагмента (узла), например, строка и элемент React: 706 | 707 | * PropTypes.node 708 | * PropTypes.element 709 | 710 | Вы уже использовали PropType `node` для компонента Button. В целом есть больше определений PropType, про которые вы можете узнать в официальной документации React. 711 | 712 | На данный момент все определённые PropTypes для Button — необязательные. Параметры могут быть `null` или `undefined`. Но для нескольких свойств вы можете определить их обязательными. Вы можете установить требование, чтобы эти свойства были переданы компоненту. 713 | 714 | {title="src/App.js",lang=javascript} 715 | ~~~~~~~~ 716 | Button.propTypes = { 717 | # leanpub-start-insert 718 | onClick: PropTypes.func.isRequired, 719 | # leanpub-end-insert 720 | className: PropTypes.string, 721 | # leanpub-start-insert 722 | children: PropTypes.node.isRequired, 723 | # leanpub-end-insert 724 | }; 725 | ~~~~~~~~ 726 | 727 | `className` не обязателен, поскольку по умолчанию может быть пустая строка. Далее вы определяете интерфейс PropType для компонента Table: 728 | 729 | {title="src/App.js",lang=javascript} 730 | ~~~~~~~~ 731 | # leanpub-start-insert 732 | Table.propTypes = { 733 | list: PropTypes.array.isRequired, 734 | onDismiss: PropTypes.func.isRequired, 735 | }; 736 | # leanpub-end-insert 737 | ~~~~~~~~ 738 | 739 | Вы можете определить содержимое массива PropType более явно: 740 | 741 | {title="src/App.js",lang=javascript} 742 | ~~~~~~~~ 743 | Table.propTypes = { 744 | list: PropTypes.arrayOf( 745 | PropTypes.shape({ 746 | objectID: PropTypes.string.isRequired, 747 | author: PropTypes.string, 748 | url: PropTypes.string, 749 | num_comments: PropTypes.number, 750 | points: PropTypes.number, 751 | }) 752 | ).isRequired, 753 | onDismiss: PropTypes.func.isRequired, 754 | }; 755 | ~~~~~~~~ 756 | 757 | Требуется только `objectID`, потому что вы знаете, что от этого зависит какой-то ваш код. Другие свойства только отображаются, поэтому они не обязательны. Кроме того, вы не можете быть уверены, что API Hacker News всегда имеет определённое свойство для каждого объекта в массиве. 758 | 759 | Это для PropTypes. Но есть ещё один аспект. Вы можете определить свойства по умолчанию в своём компоненте. Давайте снова вернёмся к компоненту Button. У свойства `className` есть значение по умолчанию в объявлении компонента. 760 | 761 | {title="src/App.js",lang=javascript} 762 | ~~~~~~~~ 763 | const Button = ({ 764 | onClick, 765 | className = '', 766 | children 767 | }) => 768 | ... 769 | ~~~~~~~~ 770 | 771 | Вы можете заменить его внутренним свойством по умолчанию в React: 772 | 773 | {title="src/App.js",lang=javascript} 774 | ~~~~~~~~ 775 | # leanpub-start-insert 776 | const Button = ({ 777 | onClick, 778 | className, 779 | children 780 | }) => 781 | # leanpub-end-insert 782 | 789 | 790 | # leanpub-start-insert 791 | Button.defaultProps = { 792 | className: '', 793 | }; 794 | # leanpub-end-insert 795 | ~~~~~~~~ 796 | 797 | Так же, как и параметр по умолчанию в ES6, свойство по умолчанию гарантирует, что для свойства установлено значение по умолчанию, если родительский компонент не указал его. Проверка типа PropType происходит после вычисления свойства по умолчанию. 798 | 799 | Если вы снова запустите свои тесты, вы увидите ошибки PropType по вашим компонентам в командной строке. Это может произойти, потому что вы не определили все свойства для своих компонентов в тестах, которые определены в соответствии с вашим определением PropType. Однако сами тесты проходят правильно. Вы можете передать все необходимые свойства компонентам в своих тестах, чтобы избежать этих ошибок. 800 | 801 | ### Упражнения: 802 | 803 | * определите интерфейс PropType для компонента Search 804 | * добавьте и обновите интерфейсы PropType при добавлении и обновлении компонентов в последующих главах 805 | * узнайте больше про [PropTypes в React](https://ru.reactjs.org/docs/typechecking-with-proptypes.html) 806 | 807 | ## Отладка с помощью инструментов разработчика React 808 |   809 | В этом последнем разделе представлен полезный инструмент, обычно используемый для исследования и отладки React-приложений. [React Developer Tools](https://github.com/facebook/react-devtools) позволяет вам изучать иерархию, свойства и состояние React-компонентов. Он распространяется в виде расширения для браузера (для Chrome и Firefox) и как автономное приложение (которое работает с другими окружениями). После установки на сайтах, разработанных с помощью React, загорится значок расширения. На таких страницах вы увидите вкладку «React» в инструментах разработчика браузера. 810 |   811 | Давайте попробуем это расширение в вашем приложении Hacker News. В большинстве браузеров быстрый способ загрузить его в *инструменты разработчика* — щёлкнуть правой кнопкой мыши на странице, а затем нажать «Inspect». Сделайте это, когда ваши приложения загружены, затем нажмите вкладку «React». Вы увидите его иерархию элементов с корневым элементом ``. Если вы раскроете его, вы найдёте экземпляры ваших компонентов ``, `
` и ` 42 | 43 | # leanpub-start-insert 44 | ); 45 | } 46 | } 47 | # leanpub-end-insert 48 | ~~~~~~~~ 49 | 50 | Объект `this` ES6-класса компонента помогает нам ссылаться на DOM-узел с атрибутом `ref`. 51 | 52 | {title="src/App.js",lang=javascript} 53 | ~~~~~~~~ 54 | class Search extends Component { 55 | render() { 56 | const { 57 | value, 58 | onChange, 59 | onSubmit, 60 | children 61 | } = this.props; 62 | 63 | return ( 64 | 65 | { this.input = node; }} 71 | # leanpub-end-insert 72 | /> 73 | 76 | 77 | ); 78 | } 79 | } 80 | ~~~~~~~~ 81 | 82 | Теперь вы можете сфокусировать поле ввода, когда компонент монтируется с использованием объекта `this`, соответствующего метода жизненного цикла и API DOM. 83 | 84 | {title="src/App.js",lang=javascript} 85 | ~~~~~~~~ 86 | class Search extends Component { 87 | # leanpub-start-insert 88 | componentDidMount() { 89 | if (this.input) { 90 | this.input.focus(); 91 | } 92 | } 93 | # leanpub-end-insert 94 | 95 | render() { 96 | const { 97 | value, 98 | onChange, 99 | onSubmit, 100 | children 101 | } = this.props; 102 | 103 | return ( 104 | 105 | { this.input = node; }} 110 | /> 111 | 114 | 115 | ); 116 | } 117 | } 118 | ~~~~~~~~ 119 | 120 | Поле ввода должно быть сфокусировано при отрисовке приложения. В основном это для использования атрибута `ref`. 121 | 122 | Но как бы вы получили доступ к `ref` в функциональном компоненте, не имеющем состояния, без использования объекта `this`? Следующий пример демонстрирует такой случай. 123 | 124 | {title="src/App.js",lang=javascript} 125 | ~~~~~~~~ 126 | const Search = ({ 127 | value, 128 | onChange, 129 | onSubmit, 130 | children 131 | }) => { 132 | # leanpub-start-insert 133 | let input; 134 | # leanpub-end-insert 135 | return ( 136 | 137 | input = node} 143 | # leanpub-end-insert 144 | /> 145 | 148 | 149 | ); 150 | } 151 | ~~~~~~~~ 152 | 153 | Теперь вы сможете получить доступ к элементу ввода DOM. В примере использования c фокусировкой в поле ввода это не поможет вам, потому что у вас нет метода жизненного цикла в функциональном компоненте без состояния, чтобы установить фокус на элементе. Но в будущем вы можете столкнуться с другими вариантами использования, когда имеет смысл использовать функциональный компонент без состояния с атрибутом `ref`. 154 | 155 | ### Упражнения 156 | 157 | * узнайте подробнее про [использование атрибута ref в React](https://www.robinwieruch.de/react-ref-attribute-dom-node/) 158 | * узнайте подробнее в целом про [атрибут ref в React](https://ru.reactjs.org/docs/refs-and-the-dom.html) 159 | 160 | ## Загрузка ... 161 | 162 | Теперь вернёмся к приложению. Возможно, вам понадобится показать индикатор загрузки при отправке поискового запроса к API Hacker News. Поскольку запрос является асинхронным, вы, возможно, захотите дать своему пользователю обратную связь, что сейчас коё-что произойдёт. Давайте определим повторно используемый компонент Loading в файле *src/App.js*. 163 | 164 | {title="src/App.js",lang=javascript} 165 | ~~~~~~~~ 166 | const Loading = () => 167 |
Загрузка ...
168 | ~~~~~~~~ 169 | 170 | Теперь вам нужно свойство для хранения состояния загрузки. В зависимости от значения состояния загрузки вы можете позднее отобразить компонент Loading. 171 | 172 | {title="src/App.js",lang=javascript} 173 | ~~~~~~~~ 174 | class App extends Component { 175 | _isMounted = false; 176 | 177 | constructor(props) { 178 | super(props); 179 | 180 | this.state = { 181 | results: null, 182 | searchKey: '', 183 | searchTerm: DEFAULT_QUERY, 184 | error: null, 185 | # leanpub-start-insert 186 | isLoading: false, 187 | # leanpub-end-insert 188 | }; 189 | 190 | ... 191 | } 192 | 193 | ... 194 | 195 | } 196 | ~~~~~~~~ 197 | 198 | Начальное значение такого свойства `isLoading` устанавливается в значение `false`. Вы не загружаете ещё что-либо перед тем, как компонент App смонтирован. 199 | 200 | Когда вы делаете запрос, то устанавливаете состояние загрузки в значение `true`. В конце концов, когда запрос будет успешным, вы можете установить состояние загрузки обратно на значение `false`. 201 | 202 | {title="src/App.js",lang=javascript} 203 | ~~~~~~~~ 204 | class App extends Component { 205 | 206 | ... 207 | 208 | setSearchTopStories(result) { 209 | ... 210 | 211 | this.setState({ 212 | results: { 213 | ...results, 214 | [searchKey]: { hits: updatedHits, page } 215 | }, 216 | # leanpub-start-insert 217 | isLoading: false 218 | # leanpub-end-insert 219 | }); 220 | } 221 | 222 | fetchSearchTopStories(searchTerm, page = 0) { 223 | # leanpub-start-insert 224 | this.setState({ isLoading: true }); 225 | # leanpub-end-insert 226 | 227 | axios(`${PATH_BASE}${PATH_SEARCH}?${PARAM_SEARCH}${searchTerm}&${PARAM_PAGE}${page}&${PARAM_HPP}${DEFAULT_HPP}`) 228 | .then(result => this._isMounted && this.setSearchTopStories(result.data)) 229 | .catch(error => this._isMounted && this.setState({ error })); 230 | } 231 | 232 | ... 233 | 234 | } 235 | ~~~~~~~~ 236 | 237 | На последнем шаге вы будете использовать компонент Loading в App. Условная отрисовка на основе значения состоянии загрузки будет определять, отображать ли компонент Loading или компонент Button. Последний компонент — это кнопка для получения дополнительных (больше) данных. 238 | 239 | {title="src/App.js",lang=javascript} 240 | ~~~~~~~~ 241 | class App extends Component { 242 | 243 | ... 244 | 245 | render() { 246 | const { 247 | searchTerm, 248 | results, 249 | searchKey, 250 | error, 251 | # leanpub-start-insert 252 | isLoading 253 | # leanpub-end-insert 254 | } = this.state; 255 | 256 | ... 257 | 258 | return ( 259 |
260 | ... 261 |
262 | # leanpub-start-insert 263 | { isLoading 264 | ? 265 | : 270 | } 271 | # leanpub-end-insert 272 |
273 |
274 | ); 275 | } 276 | } 277 | ~~~~~~~~ 278 | 279 | Первоначально компонент Loading будет отображаться при запуске приложения, потому что вы совершаете запрос в `componentDidMount()`. Компонент Table отсутствует, потому что список пуст. Когда ответ приходит от API Hacker News, показывается результат, состояние загрузки устанавливается на значение `false`, а компонент Loading исчезает. Вместо этого отображается кнопка «Больше историй» для получения ещё одной порции данных. Когда вы отправите запрос для получения дополнительных данных, кнопка снова исчезнет и вместо неё отобразится компонент Loading. 280 | 281 | ### Упражнения: 282 | 283 | * используйте библиотеку, такую как [Font Awesome](https://fontawesome.io/), чтобы показать иконку загрузки вместо надписи "Загрузка ..." 284 | 285 | ## Компоненты высшего порядка 286 | 287 | Компоненты высшего порядка (Higher-order components, HOC) — продвинутая концепция React. Компоненты высшего порядка эквивалентны функциям высшего порядка. Они принимают любые входные данные — чаще всего компонент, но ещё необязательные аргументы — и возвращают компонент в качестве возвращаемого значения. Возвращаемый компонент является расширенной версией переданного в HOC компонента и может использоваться в JSX. 288 | 289 | Компоненты высшего порядка используются в разных случаях. Они могут подготавливать свойства, управлять состоянием или изменять представление компонента. Одним из вариантов использования может быть использование HOC в качестве помощника для условной отрисовки. Представьте, что у вас есть компонент List, отображающий список элементов или вообще ничего, потому что список пуст или равняется `null`. С использованием HOC можно ничего не отрисовывать, если нет списка. С другой стороны, компоненту простого List больше не нужно беспокоиться о несуществующем списке. Его задача только отрисовать список. 290 | 291 | Давайте создадим простой HOC, который принимает компонент и возвращает компонент. Вы можете поместить его в файл *src/App.js*. 292 | 293 | {title="src/App.js",lang=javascript} 294 | ~~~~~~~~ 295 | function withFoo(Component) { 296 | return function(props) { 297 | return ; 298 | } 299 | } 300 | ~~~~~~~~ 301 | 302 | Есть одно чистое соглашение — префикс имени HOC начинается с `with`. Поскольку вы используете JavaScript ES6, вы можете более кратко сделать HOC с помощью стрелочных функций из ES6. 303 | 304 | {title="src/App.js",lang=javascript} 305 | ~~~~~~~~ 306 | const withFoo = (Component) => (props) => 307 | 308 | ~~~~~~~~ 309 | 310 | В этом примере входной компонент будет оставаться таким же, как и выходной компонент. Ничего интересного не происходит. Он отрисовывает один и тот же экземпляр компонента и передаёт все свойства возвращаемому компоненту. Но это бесполезно. Давайте улучшим получаемый из HOC компонент. Компонент на выходе должен показывать компонент Loading, когда значение состояния загрузки равняется `true`, в противном случае он должен отображать переданный компонент. Отрисовка по условию — отличный вариант использования HOC. 311 | 312 | {title="src/App.js",lang=javascript} 313 | ~~~~~~~~ 314 | # leanpub-start-insert 315 | const withLoading = (Component) => (props) => 316 | props.isLoading 317 | ? 318 | : 319 | # leanpub-end-insert 320 | ~~~~~~~~ 321 | 322 | В зависимости от значения свойства `isLoading` вы можете применить условную отрисовку. Функция вернёт компонент Loading или входной компонент. 323 | 324 | В общем случае может быть очень эффективно использовать оператор расширения для объекта, например, для объекта `props` из предыдущего примера, при передаче его на вход компонента. Смотрите разницу в следующем фрагменте кода. 325 | 326 | {title="Код",lang="javascript"} 327 | ~~~~~~~~ 328 | // деструктуризация свойств перед их передачей 329 | const { foo, bar } = props; 330 | 331 | 332 | // но можно использовать оператор расширения для передачи всех свойств объекта 333 | 334 | ~~~~~~~~ 335 | 336 | Есть одна маленькая деталь, которую следует избегать: сейчас вы передаёте все свойства, включая `isLoading`, путём расширения объекта во входной компонент. Однако входной компонент может не поддерживать свойство `isLoading`, тогда вы можете использовать деструктуризацию оставшихся свойств из ES6 для решения этой проблемы. 337 | 338 | {title="src/App.js",lang=javascript} 339 | ~~~~~~~~ 340 | # leanpub-start-insert 341 | const withLoading = (Component) => ({ isLoading, ...rest }) => 342 | isLoading 343 | ? 344 | : 345 | # leanpub-end-insert 346 | ~~~~~~~~ 347 | 348 | Он выбирает одно свойство из объекта, но сохраняет оставшиеся свойства в объекте. Он также поддерживает работу с несколькими свойствами. Возможно, вы уже прочитали об этом в [деструктурирующем присваивании](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment). 349 | 350 | Теперь вы можете использовать HOC в своём JSX. В приложении может показываться либо кнопка «Больше историй», либо компонент Loading. Компонент Loading уже инкапсулирован в HOC, но отсутствует передаваемый компонент. В случае показа компонента Button или компонента Loading, компонент Button будет входным компонентом для HOC. Улучшенный выходной компонент — компонент ButtonWithLoading. 351 | 352 | {title="src/App.js",lang=javascript} 353 | ~~~~~~~~ 354 | const Button = ({ 355 | onClick, 356 | className = '', 357 | children, 358 | }) => 359 | 366 | 367 | const Loading = () => 368 |
Загрузка ...
369 | 370 | const withLoading = (Component) => ({ isLoading, ...rest }) => 371 | isLoading 372 | ? 373 | : 374 | 375 | # leanpub-start-insert 376 | const ButtonWithLoading = withLoading(Button); 377 | # leanpub-end-insert 378 | ~~~~~~~~ 379 | 380 | Теперь всё установлено. В качестве последнего шага вам необходимо использовать компонент ButtonWithLoading, который получает состояние загрузки в виде дополнительного свойства. В то время как HOC использует свойство загрузки, все другие свойства передаются компоненту Button. 381 | 382 | {title="src/App.js",lang=javascript} 383 | ~~~~~~~~ 384 | class App extends Component { 385 | 386 | ... 387 | 388 | render() { 389 | ... 390 | 391 | return ( 392 |
393 | ... 394 |
395 | # leanpub-start-insert 396 | this.fetchSearchTopStories(searchKey, page + 1)} 399 | > 400 | Больше историй 401 | 402 | # leanpub-end-insert 403 |
404 |
405 | ); 406 | } 407 | } 408 | ~~~~~~~~ 409 | 410 | Когда вы снова запустите свои тесты, вы заметите, что ваш тест снимком компонента App завершился неудачей. В командной строке различия могут выглядеть с помощью унифицированного формата diff следующим образом: 411 | 412 | {title="Командная строка",lang="text"} 413 | ~~~~~~~~ 414 | - 421 | +
422 | + Загрузка ... 423 | +
424 | ~~~~~~~~ 425 | 426 | Вы можете либо исправить компонент сейчас, когда вам кажется, что в нём что-то не так, либо можете принять новый снимок. Поскольку вы реализовали компонент Loading в этой главе, вы можете принять изменённый снимок в командной строке в интерактивном тесте. 427 | 428 | Компоненты высшего порядка — продвинутая методика в React. У неё есть несколько целей, таких как улучшенная возможность повторного использования компонентов, более сильная абстракция, композиция (компоновка) компонентов и управлениями свойствами, состоянием и представлением. Не переживайте, если не сразу понимаете всё это. Потребуется время, чтобы привыкнуть к этому всему. 429 | 430 | Я призываю вас прочитать [простое введение в компоненты высшего порядка](https://www.robinwieruch.de/gentle-introduction-higher-order-components/). Статья даёт вам ещё один подход для их изучения, показывает элегантный способ использования их в функциональном программировании и, в частности, решает проблему условной отрисовки с компонентами высшего порядка. 431 | 432 | ### Упражнения: 433 | 434 | * прочитайте [лёгкое введение в компоненты высшего порядка (HOC)](https://www.robinwieruch.de/gentle-introduction-higher-order-components/) 435 | * поэкспериментируйте с HOC, который вы создали 436 | * подумайте о случае использования, где другой HOC будет иметь смысл 437 | * реализуйте такой HOC, если в этом есть необходимость 438 | 439 | ## Продвинутая сортировка 440 | 441 | Вы уже реализовали поиск на стороне клиента и сервера. Поскольку у вас есть компонент Table, имеет смысл улучшить таблицу дополнительными возможностями. Как насчёт внедрения функциональности сортировки по каждому столбцу при нажатии на заголовки столбцов компонента Table? 442 | 443 | Можно было бы написать свою собственную функцию сортировки, но лично я предпочитаю использовать вспомогательную библиотеку как раз для таких случаев. [Lodash](https://lodash.com/) — одна из таких утилитарных библиотек, но вы можете использовать любую другую библиотеку, которая вам подходит. Давайте установим Lodash и воспользуемся её функциональностью сортировки. 444 | 445 | {title="Командная строка",lang="text"} 446 | ~~~~~~~~ 447 | npm install lodash 448 | ~~~~~~~~ 449 | 450 | Теперь вы можете импортировать Lodash в файле *src/App.js* . 451 | 452 | {title="src/App.js",lang=javascript} 453 | ~~~~~~~~ 454 | import React, { Component } from 'react'; 455 | import axios from 'axios'; 456 | # leanpub-start-insert 457 | import { sortBy } from 'lodash'; 458 | # leanpub-end-insert 459 | import './App.css'; 460 | ~~~~~~~~ 461 | 462 | У вас есть несколько столбцов в компоненте Table. Есть столбцы для заголовков, авторов, комментариев и очков. Вы можете определить функции сортировки, где каждая функция принимает список и возвращает список элементов, отсортированных по определённому свойству. Кроме того, вам понадобится одна функция сортировки по умолчанию, которая не сортирует, а возвращает обычный (неотсортированный) список. Это будет вашим первоначальным состоянием. 463 | 464 | {title="src/App.js",lang=javascript} 465 | ~~~~~~~~ 466 | ... 467 | 468 | # leanpub-start-insert 469 | const SORTS = { 470 | NONE: list => list, 471 | TITLE: list => sortBy(list, 'title'), 472 | AUTHOR: list => sortBy(list, 'author'), 473 | COMMENTS: list => sortBy(list, 'num_comments').reverse(), 474 | POINTS: list => sortBy(list, 'points').reverse(), 475 | }; 476 | # leanpub-end-insert 477 | 478 | class App extends Component { 479 | ... 480 | } 481 | ... 482 | ~~~~~~~~ 483 | 484 | Как вы можете увидеть сами, две функции сортировки возвращают список в обратном порядке. Это из-за того, что нужно отображение элементов с наибольшим количеством комментариев и очков вместо элементов с наименьшим количеством при сортировке списка в первый раз. 485 | 486 | Объект `SORTS` позволяет вам ссылаться на любую функцию сортировки. 487 | 488 | Снова ваш компонент App отвечает за сохранение состояния сортировки. Первоначальным состоянием будет функция сортировки по умолчанию, которая не сортирует элементы вообще, а возвращает входной список в качестве вывода. 489 | 490 | {title="src/App.js",lang=javascript} 491 | ~~~~~~~~ 492 | this.state = { 493 | results: null, 494 | searchKey: '', 495 | searchTerm: DEFAULT_QUERY, 496 | error: null, 497 | isLoading: false, 498 | # leanpub-start-insert 499 | sortKey: 'NONE', 500 | # leanpub-end-insert 501 | }; 502 | ~~~~~~~~ 503 | 504 | После выбора другого ключа сортировка `sortKey`, скажем, `AUTHOR`, список будет отсортирован соответствующей функцией сортировки из объекта `SORTS`. 505 | 506 | Теперь вы можете определить новый метод класса в компоненте App, который просто устанавливает `sortKey` в локальном состоянии компонента. После этого `sortKey` можно использовать для получения функции сортировки для её применения в вашем списке. 507 | 508 | {title="src/App.js",lang=javascript} 509 | ~~~~~~~~ 510 | class App extends Component { 511 | _isMounted = false; 512 | 513 | constructor(props) { 514 | 515 | ... 516 | 517 | this.needsToSearchTopStories = this.needsToSearchTopStories.bind(this); 518 | this.setSearchTopStories = this.setSearchTopStories.bind(this); 519 | this.fetchSearchTopStories = this.fetchSearchTopStories.bind(this); 520 | this.onSearchSubmit = this.onSearchSubmit.bind(this); 521 | this.onSearchChange = this.onSearchChange.bind(this); 522 | this.onDismiss = this.onDismiss.bind(this); 523 | # leanpub-start-insert 524 | this.onSort = this.onSort.bind(this); 525 | # leanpub-end-insert 526 | } 527 | 528 | ... 529 | 530 | # leanpub-start-insert 531 | onSort(sortKey) { 532 | this.setState({ sortKey }); 533 | } 534 | # leanpub-end-insert 535 | 536 | ... 537 | 538 | } 539 | ~~~~~~~~ 540 | 541 | Следующий шаг — передать метод и `sortKey` в ваш компонент Table. 542 | 543 | {title="src/App.js",lang=javascript} 544 | ~~~~~~~~ 545 | class App extends Component { 546 | 547 | ... 548 | 549 | render() { 550 | const { 551 | searchTerm, 552 | results, 553 | searchKey, 554 | error, 555 | isLoading, 556 | # leanpub-start-insert 557 | sortKey 558 | # leanpub-end-insert 559 | } = this.state; 560 | 561 | ... 562 | 563 | return ( 564 |
565 | ... 566 |
574 | ... 575 | 576 | ); 577 | } 578 | } 579 | ~~~~~~~~ 580 | 581 | Компонент Table отвечает за сортировку списка. Он принимает одну из функций из объекта `SORT` по ключу `sortKey` и передаёт список в качестве входных данных. После этого он выводит отсортированной список. 582 | 583 | {title="src/App.js",lang=javascript} 584 | ~~~~~~~~ 585 | # leanpub-start-insert 586 | const Table = ({ 587 | list, 588 | sortKey, 589 | onSort, 590 | onDismiss 591 | }) => 592 | # leanpub-end-insert 593 |
594 | # leanpub-start-insert 595 | {SORTS[sortKey](list).map(item => 596 | # leanpub-end-insert 597 |
598 | ... 599 |
600 | )} 601 |
602 | ~~~~~~~~ 603 | 604 | Теоретически список будет отсортирован одной из функций. Но сортировка по умолчанию установлена на `NONE`, поэтому элементы не сортируются. До сих пор никто не выполнял метод `onSort()` для изменения `sortKey`. Расширим компонент Table, добавив заголовки столбцов, использующие компоненты Sort в столбцах для сортировки по каждому столбцу. 605 | 606 | {title="src/App.js",lang=javascript} 607 | ~~~~~~~~ 608 | const Table = ({ 609 | list, 610 | sortKey, 611 | onSort, 612 | onDismiss 613 | }) => 614 |
615 | # leanpub-start-insert 616 |
617 | 618 | 622 | Заголовок 623 | 624 | 625 | 626 | 630 | Автор 631 | 632 | 633 | 634 | 638 | Комментарии 639 | 640 | 641 | 642 | 646 | Очки 647 | 648 | 649 | 650 | Архив 651 | 652 |
653 | # leanpub-end-insert 654 | {SORTS[sortKey](list).map(item => 655 | ... 656 | )} 657 |
658 | ~~~~~~~~ 659 | 660 | Каждый компонент Sort получает определённую функцию `sortKey` и общую функцию `onSort()`, передавая ей `sortKey` для установки конкретного ключа. 661 | 662 | {title="src/App.js",lang=javascript} 663 | ~~~~~~~~ 664 | const Sort = ({ sortKey, onSort, children }) => 665 | 668 | ~~~~~~~~ 669 | 670 | Как вы можете видеть, компонент Sort повторно использует общий компонент Button. При нажатии на кнопку каждому из компонентов Button передаётся `sortKey`, который будет установлен методом `onSort()`. Теперь есть возможность сортировать список при нажатии на заголовки столбцов. 671 | 672 | Можно сделать ещё одно небольшое улучшение внешнего вида. Пока кнопка в заголовке столбца выглядит немного нелепо. Давайте дадим кнопке в компоненте Sort собственный CSS-класс, используя `className`. 673 | 674 | {title="src/App.js",lang=javascript} 675 | ~~~~~~~~ 676 | const Sort = ({ sortKey, onSort, children }) => 677 | # leanpub-start-insert 678 | 685 | ~~~~~~~~ 686 | 687 | Теперь кнопка выглядит красиво. Следующая цель заключалась бы в реализации обратной сортировки. Список должен быть отсортирован в обратном порядке по двойному нажатию на компонент Sort. Во-первых, нужно определить новое свойство для обратной сортировки с булевым значением. Сортировка может быть либо прямой, либо обратной. 688 | 689 | {title="src/App.js",lang=javascript} 690 | ~~~~~~~~ 691 | this.state = { 692 | results: null, 693 | searchKey: '', 694 | searchTerm: DEFAULT_QUERY, 695 | error: null, 696 | isLoading: false, 697 | sortKey: 'NONE', 698 | # leanpub-start-insert 699 | isSortReverse: false, 700 | # leanpub-end-insert 701 | }; 702 | ~~~~~~~~ 703 | 704 | Теперь в вашем методе сортировки вы можете вычислить, отсортирован ли список в обратном порядке. В обратном порядке, если значение состояния `sortKey` совпадает с переданным `sortKey`, а значение состояния обратного сортировки уже не равняется `true`. 705 | 706 | {title="src/App.js",lang=javascript} 707 | ~~~~~~~~ 708 | onSort(sortKey) { 709 | # leanpub-start-insert 710 | const isSortReverse = this.state.sortKey === sortKey && !this.state.isSortReverse; 711 | this.setState({ sortKey, isSortReverse }); 712 | # leanpub-end-insert 713 | } 714 | ~~~~~~~~ 715 | 716 | Снова вы можете передать свойство, указывающее на обратную сортировку в компонент Table. 717 | 718 | {title="src/App.js",lang=javascript} 719 | ~~~~~~~~ 720 | class App extends Component { 721 | 722 | ... 723 | 724 | render() { 725 | const { 726 | searchTerm, 727 | results, 728 | searchKey, 729 | error, 730 | isLoading, 731 | sortKey, 732 | # leanpub-start-insert 733 | isSortReverse 734 | # leanpub-end-insert 735 | } = this.state; 736 | 737 | ... 738 | 739 | return ( 740 |
741 | ... 742 |
751 | ... 752 | 753 | ); 754 | } 755 | } 756 | ~~~~~~~~ 757 | 758 | Теперь для вычисления данных в компоненте Table необходима стрелочная функция. 759 | 760 | {title="src/App.js",lang=javascript} 761 | ~~~~~~~~ 762 | # leanpub-start-insert 763 | const Table = ({ 764 | list, 765 | sortKey, 766 | isSortReverse, 767 | onSort, 768 | onDismiss 769 | }) => { 770 | const sortedList = SORTS[sortKey](list); 771 | const reverseSortedList = isSortReverse 772 | ? sortedList.reverse() 773 | : sortedList; 774 | 775 | return( 776 | # leanpub-end-insert 777 |
778 |
779 | ... 780 |
781 | # leanpub-start-insert 782 | {reverseSortedList.map(item => 783 | # leanpub-end-insert 784 | ... 785 | )} 786 |
787 | # leanpub-start-insert 788 | ); 789 | } 790 | # leanpub-end-insert 791 | ~~~~~~~~ 792 | 793 | Сортировка в обратном порядке должна сейчас работать. 794 | 795 | И последнее, но не менее важное: вам нужно разобраться с одним открытым вопросом улучшения пользовательского интерфейса. Может ли пользователь определить, по какому столбцу сейчас происходит сортировка? Пока это невозможно. Давайте предоставим пользователю такую визуальную возможность. 796 | 797 | Каждый компонент Sort уже получает свой конкретный `sortKey`. Его можно использовать для определения активной в данный момент сортировки. Вы можете передать `sortKey` из внутреннего состояния компонента в качестве активного ключа сортировки в свой компонент Sort. 798 | 799 | {title="src/App.js",lang=javascript} 800 | ~~~~~~~~ 801 | const Table = ({ 802 | list, 803 | sortKey, 804 | isSortReverse, 805 | onSort, 806 | onDismiss 807 | }) => { 808 | const sortedList = SORTS[sortKey](list); 809 | const reverseSortedList = isSortReverse 810 | ? sortedList.reverse() 811 | : sortedList; 812 | 813 | return( 814 |
815 |
816 | 817 | 824 | Заголовок 825 | 826 | 827 | 828 | 835 | Автор 836 | 837 | 838 | 839 | 846 | Комментарии 847 | 848 | 849 | 850 | 857 | Очки 858 | 859 | 860 | 861 | Архив 862 | 863 |
864 | {reverseSortedList.map(item => 865 | ... 866 | )} 867 |
868 | ); 869 | } 870 | ~~~~~~~~ 871 | 872 | Теперь в компоненте Sort можно узнать, основываясь на `sortKey` и` activeSortKey`, активен ли данный столбец сортировки или нет. Добавьте компоненту Sort ещё дополнительный атрибут `className`, применяемый при сортировке для визуального различия столбцов. 873 | 874 | {title="src/App.js",lang=javascript} 875 | ~~~~~~~~ 876 | # leanpub-start-insert 877 | const Sort = ({ 878 | sortKey, 879 | activeSortKey, 880 | onSort, 881 | children 882 | }) => { 883 | const sortClass = ['button-inline']; 884 | 885 | if (sortKey === activeSortKey) { 886 | sortClass.push('button-active'); 887 | } 888 | 889 | return ( 890 | 896 | ); 897 | } 898 | # leanpub-end-insert 899 | ~~~~~~~~ 900 | 901 | Подобный способ определения CSS-классов, используя константу `sortClass` немного неуклюжий, не считаете так? Существует замечательная небольшая библиотека для элегантной установки CSS-классов. Сначала нужно её установить. 902 | 903 | {title="Командная строка",lang="text"} 904 | ~~~~~~~~ 905 | npm install classnames 906 | ~~~~~~~~ 907 | 908 | И теперь нужно импортировать её в верху файла *src/App.js*. 909 | 910 | {title="src/App.js",lang=javascript} 911 | ~~~~~~~~ 912 | import React, { Component } from 'react'; 913 | import axios from 'axios'; 914 | import { sortBy } from 'lodash'; 915 | # leanpub-start-insert 916 | import classNames from 'classnames'; 917 | # leanpub-end-insert 918 | import './App.css'; 919 | ~~~~~~~~ 920 | 921 | Теперь вы можете воспользоваться ею для определения CSS-классов в зависимости от условий через всё тот же атрибут `className` компонента. 922 | 923 | {title="src/App.js",lang=javascript} 924 | ~~~~~~~~ 925 | const Sort = ({ 926 | sortKey, 927 | activeSortKey, 928 | onSort, 929 | children 930 | }) => { 931 | # leanpub-start-insert 932 | const sortClass = classNames( 933 | 'button-inline', 934 | { 'button-active': sortKey === activeSortKey } 935 | ); 936 | # leanpub-end-insert 937 | 938 | return ( 939 | # leanpub-start-insert 940 | 947 | ); 948 | } 949 | ~~~~~~~~ 950 | 951 | Теперь же, когда вы выполните тесты, то увидите неудачные снимки тестов, а также не прошедшие модульные тесты компонента Table. Поскольку вы изменили внешний вид компонентов, вы можете принять эти снимки. Но вам нужно исправить модульный тест. В вашем файле *src/App.test.js* вам необходимо указать ключи `sortKey` и `isSortReverse` с логическим значением для компонента Table. 952 | 953 | {title="src/App.test.js",lang=javascript} 954 | ~~~~~~~~ 955 | ... 956 | 957 | describe('Table', () => { 958 | 959 | const props = { 960 | list: [ 961 | { title: '1', author: '1', num_comments: 1, points: 2, objectID: 'y' }, 962 | { title: '2', author: '2', num_comments: 1, points: 2, objectID: 'z' }, 963 | ], 964 | # leanpub-start-insert 965 | sortKey: 'TITLE', 966 | isSortReverse: false, 967 | # leanpub-end-insert 968 | }; 969 | 970 | ... 971 | 972 | }); 973 | ~~~~~~~~ 974 | 975 | Ещё раз вам может потребоваться принять неудачные снимки компонента Table, потому что были расширены его свойства. 976 | 977 | Наконец, работа с расширенной сортировкой завершена. 978 | 979 | ### Упражнения: 980 | 981 | * используйте библиотеку, такую как [Font Awesome](https://fontawesome.io/), чтобы указать на применяемую (прямую или обратную) сортировку 982 | * это может быть иконка стрелки вверх или стрелки вниз рядом с каждым заголовком в компоненте Sort 983 | * узнайте подробнее про [библиотеку classnames](https://github.com/JedWatson/classnames) 984 | 985 | {pagebreak} 986 | 987 | Вы изучили продвинутые технологии компонентов React! Давайте повторим последние пройденные темы: 988 | 989 | * React 990 | * атрибут `ref` для ссылок на DOM-узлы 991 | * Компоненты высшего порядка — это распространённый способ создания продвинутых компонентов 992 | * внедрение расширенных взаимодействий в React 993 | * назначение CSS-классов по условию с помощью небольшой вспомогательной библиотеки classNames 994 | * ES6 995 | * деструктуризация оставшихся свойств для разделения объектов и массивов 996 | 997 | Исходный код можно найти в [официальном репозитории](https://github.com/the-road-to-learn-react/hackernews-client/tree/5.5). 998 | -------------------------------------------------------------------------------- /manuscript/chapter6.md: -------------------------------------------------------------------------------- 1 | # Управление состоянием в React и за его пределами 2 | 3 | Вы уже изучили основы управления состоянием в React в предыдущих главах. В данной главе мы немного углубимся в эту тему. Вы узнаете о передовых практиках, как их применять, а также как использовать стороннюю библиотеку для управления состоянием. 4 | 5 | ## Подъём состояния 6 | 7 | В вашем приложении только компонент App — классовый компонент с состоянием. Он обрабатывает все состояния в приложении и содержит много логики в своих методах. Возможно, вы заметили, что передаёте много свойств компоненту Table. Большинство из них используются только в компоненте Table. В заключение можно утверждать, что нет никакого смысла в том, что компонент App знает обо всём этом. 8 | 9 | Вся функциональность сортировки используется только в компоненте Table. Вы можете перенести её в компонент Table, потому что компонент App вообще не должен знать об этом. Процесс рефакторинга подсостояния от одного компонента к другому известен как *подъём состояния*. В вашем случае вам нужно переместить состояние, неиспользуемое в компоненте App, в компонент Table. Состояние перемещается от родительского к дочернему компоненту. 10 | 11 | Для работы с состоянием и методами класса в компоненте Table, его нужно преобразовать в компонент ES6-класса. Рефакторинг из функционального компонента без состояния в компонент класса ES6 — это просто. 12 | 13 | Компонент Table как функциональный компонент без состояния выглядит примерно так: 14 | 15 | {title="src/App.js",lang=javascript} 16 | ~~~~~~~~ 17 | const Table = ({ 18 | list, 19 | sortKey, 20 | isSortReverse, 21 | onSort, 22 | onDismiss 23 | }) => { 24 | const sortedList = SORTS[sortKey](list); 25 | const reverseSortedList = isSortReverse 26 | ? sortedList.reverse() 27 | : sortedList; 28 | 29 | return( 30 | ... 31 | ); 32 | } 33 | ~~~~~~~~ 34 | 35 | Компонент Table в виде класса ES6 компонента будет таким: 36 | 37 | {title="src/Table.js",lang=javascript} 38 | ~~~~~~~~ 39 | # leanpub-start-insert 40 | class Table extends Component { 41 | render() { 42 | const { 43 | list, 44 | sortKey, 45 | isSortReverse, 46 | onSort, 47 | onDismiss 48 | } = this.props; 49 | 50 | const sortedList = SORTS[sortKey](list); 51 | const reverseSortedList = isSortReverse 52 | ? sortedList.reverse() 53 | : sortedList; 54 | 55 | return ( 56 | ... 57 | ); 58 | } 59 | } 60 | # leanpub-end-insert 61 | ~~~~~~~~ 62 | 63 | Поскольку вам нужно работать с состоянием и методами в своём компоненте, вам нужно добавить конструктор и начальное состояние. 64 | 65 | {title="src/App.js",lang=javascript} 66 | ~~~~~~~~ 67 | class Table extends Component { 68 | # leanpub-start-insert 69 | constructor(props) { 70 | super(props); 71 | 72 | this.state = {}; 73 | } 74 | # leanpub-end-insert 75 | 76 | render() { 77 | ... 78 | } 79 | } 80 | ~~~~~~~~ 81 | 82 | Теперь вы можете переместить состояния и методы класса, относящиеся к функциональности сортировки из вашего компонента App в компонент Table. 83 | 84 | {title="src/Table.js",lang=javascript} 85 | ~~~~~~~~ 86 | class Table extends Component { 87 | constructor(props) { 88 | super(props); 89 | 90 | # leanpub-start-insert 91 | this.state = { 92 | sortKey: 'NONE', 93 | isSortReverse: false, 94 | }; 95 | 96 | this.onSort = this.onSort.bind(this); 97 | # leanpub-end-insert 98 | } 99 | 100 | # leanpub-start-insert 101 | onSort(sortKey) { 102 | const isSortReverse = this.state.sortKey === sortKey && !this.state.isSortReverse; 103 | this.setState({ sortKey, isSortReverse }); 104 | } 105 | # leanpub-end-insert 106 | 107 | render() { 108 | ... 109 | } 110 | } 111 | ~~~~~~~~ 112 | 113 | Не забудьте удалить перемещённое состояние и метод `onSort()` из компонента App. 114 | 115 | {title="src/App.js",lang=javascript} 116 | ~~~~~~~~ 117 | class App extends Component { 118 | _isMounted = false; 119 | 120 | constructor(props) { 121 | super(props); 122 | 123 | this.state = { 124 | results: null, 125 | searchKey: '', 126 | searchTerm: DEFAULT_QUERY, 127 | error: null, 128 | isLoading: false, 129 | }; 130 | 131 | this.setSearchTopStories = this.setSearchTopStories.bind(this); 132 | this.fetchSearchTopStories = this.fetchSearchTopStories.bind(this); 133 | this.onDismiss = this.onDismiss.bind(this); 134 | this.onSearchSubmit = this.onSearchSubmit.bind(this); 135 | this.onSearchChange = this.onSearchChange.bind(this); 136 | this.needsToSearchTopStories = this.needsToSearchTopStories.bind(this); 137 | } 138 | 139 | ... 140 | 141 | } 142 | ~~~~~~~~ 143 | 144 | Кроме того, вы можете сделать API компонента Table более лёгким. Удалите свойства, которые передаются ему из компонента App, потому что теперь они обрабатываются внутри компонента Table. 145 | 146 | {title="src/App.js",lang=javascript} 147 | ~~~~~~~~ 148 | class App extends Component { 149 | 150 | ... 151 | 152 | render() { 153 | # leanpub-start-insert 154 | const { 155 | searchTerm, 156 | results, 157 | searchKey, 158 | error, 159 | isLoading 160 | } = this.state; 161 | # leanpub-end-insert 162 | 163 | ... 164 | 165 | return ( 166 |
167 | ... 168 | { error 169 | ?
170 |

Что-то пошло не так.

171 |
172 | :
178 | } 179 | ... 180 | 181 | ); 182 | } 183 | } 184 | ~~~~~~~~ 185 | 186 | Теперь в компоненте Table вы можете использовать внутренний метод `onSort()` и внутреннее состояние Table. 187 | 188 | {title="src/App.js",lang=javascript} 189 | ~~~~~~~~ 190 | class Table extends Component { 191 | 192 | // ... 193 | 194 | render() { 195 | # leanpub-start-insert 196 | const { 197 | list, 198 | onDismiss 199 | } = this.props; 200 | 201 | const { 202 | sortKey, 203 | isSortReverse, 204 | } = this.state; 205 | # leanpub-end-insert 206 | 207 | const sortedList = SORTS[sortKey](list); 208 | const reverseSortedList = isSortReverse 209 | ? sortedList.reverse() 210 | : sortedList; 211 | 212 | return( 213 |
214 |
215 | 216 | 223 | Заголовок 224 | 225 | 226 | 227 | 234 | Автор 235 | 236 | 237 | 238 | 245 | Комментарии 246 | 247 | 248 | 249 | 256 | Очки 257 | 258 | 259 | 260 | Архив 261 | 262 |
263 | { reverseSortedList.map((item) => 264 | ... 265 | )} 266 |
267 | ); 268 | } 269 | } 270 | ~~~~~~~~ 271 | 272 | Ваше приложение по-прежнему должно работать, хотя вы сделали большой рефакторинг. Вы переместили функциональность и состояние ближе к другому компоненту. Другие компоненты снова стали более лёгкими. Кроме того, API компонента стал более лёгким, поскольку он внутренне выполняет функции сортировки. 273 | 274 | Процесс подъёма состояния может идти по обратному пути: от дочернего к родительскому компоненту. Это называется _подъём состояния вверх_. Представьте, что вы имеете дело с внутренним состоянием в дочернем компоненте. Теперь вы хотите удовлетворить требование по отображению состояния в своём родительском компоненте. Вам нужно будет поднять состояние к вашему родительскому компоненту. Но это идёт ещё дальше. Представьте, что вы хотите показать состояние в одноуровневом компоненте вашего дочернего компонента. Снова вам нужно будет поднять состояние до вашего родительского компонента. Родительский компонент имеет дело с внутренним состоянием, но предоставляет его для обоих дочерних компонентов. 275 | 276 | ### Упражнения: 277 | 278 | * прочитайте подробнее про [поднятие состояния в React](https://ru.reactjs.org/docs/lifting-state-up.html) 279 | * прочитайте больше про поднятие состояния в [статье про изучение React перед использованием Redux](https://www.robinwieruch.de/learn-react-before-using-redux/) 280 | 281 | ## Пересмотр: setState() 282 | 283 | До сих пор вы использовали React `setState()` для управления состоянием вашего внутреннего компонента. Вы можете передать объект функции, в которой вы можете частично обновить внутреннее состояние. 284 | 285 | {title="Код",lang="javascript"} 286 | ~~~~~~~~ 287 | this.setState({ foo: bar }); 288 | ~~~~~~~~ 289 | 290 | Но `setState()` принимает не только объект. Второй вариант использования этого метода включает передачу функции для обновления состояния. 291 | 292 | {title="Код",lang="javascript"} 293 | ~~~~~~~~ 294 | this.setState((prevState, props) => { 295 | ... 296 | }); 297 | ~~~~~~~~ 298 | 299 | Когда это может пригодиться? Существует один важный случай использования, когда имеет смысл передать функцию вместо объекта. Это когда вы обновляете состояние в зависимости от предыдущего состояния или свойства. Если вы не используете функцию, управление внутренним состоянием может вызвать баги (ошибки). 300 | 301 | Но почему это вызывает баги при использовании объекта вместо функции, когда обновление зависит от предыдущего состояния или свойства? Метод React `setState()` — асинхронный. React группирует вызовы `setState()` и выполняет их рано или поздно. Может случиться так, что предыдущее состояние или свойство изменились между тем, когда вы будете использовать его в вызове `setState()`. 302 | 303 | {title="Код",lang="javascript"} 304 | ~~~~~~~~ 305 | const { fooCount } = this.state; 306 | const { barCount } = this.props; 307 | this.setState({ count: fooCount + barCount }); 308 | ~~~~~~~~ 309 | 310 | Представьте, что `fooCount` и `barCount`, таким образом, состояние или свойство, изменяются где-то ещё асинхронно, когда вы вызываете `setState()`. В растущем приложении у вас будет более одного вызова `setState()` в приложении. Поскольку `setState()` выполняется асинхронно, вы можете использовать его в примере на устаревших значениях. 311 | 312 | С помощью функционального подхода функция в `setState()` — колбэк, который работает с состоянием и свойствами во время выполнения колбэк-функции. Даже при том, что `setState()` является асинхронным, с функцией он принимает состояние и свойства в момент его выполнения. 313 | 314 | {title="Код",lang="javascript"} 315 | ~~~~~~~~ 316 | this.setState((prevState, props) => { 317 | const { fooCount } = prevState; 318 | const { barCount } = props; 319 | return { count: fooCount + barCount }; 320 | }); 321 | ~~~~~~~~ 322 | 323 | Теперь вернёмся к вашему коду, чтобы исправить это поведение. Вместе мы исправим его для одного места, где используется `setState()` и полагается на состояние или свойства. Впоследствии вы сможете исправить это и в других местах. 324 | 325 | Метод `setSearchTopStories()` опирается на предыдущее состояние и, следовательно, является прекрасным примером использования функции вместо объекта в `setState()`. Сейчас так выглядит следующий фрагмент кода. 326 | 327 | {title="src/App.js",lang=javascript} 328 | ~~~~~~~~ 329 | setSearchTopStories(result) { 330 | const { hits, page } = result; 331 | const { searchKey, results } = this.state; 332 | 333 | const oldHits = results && results[searchKey] 334 | ? results[searchKey].hits 335 | : []; 336 | 337 | const updatedHits = [ 338 | ...oldHits, 339 | ...hits 340 | ]; 341 | 342 | this.setState({ 343 | results: { 344 | ...results, 345 | [searchKey]: { hits: updatedHits, page } 346 | }, 347 | isLoading: false 348 | }); 349 | } 350 | ~~~~~~~~ 351 | 352 | Вы извлекаете значения из состояния, но обновляете состояние в зависимости от предыдущего состояния асинхронно. Теперь вы можете использовать функциональный подход для предотвращения ошибок из-за устаревшего состояния. 353 | 354 | {title="src/App.js",lang=javascript} 355 | ~~~~~~~~ 356 | setSearchTopStories(result) { 357 | const { hits, page } = result; 358 | 359 | # leanpub-start-insert 360 | this.setState(prevState => { 361 | ... 362 | }); 363 | # leanpub-end-insert 364 | } 365 | ~~~~~~~~ 366 | 367 | Вы можете переместить весь блок, который вы уже внедрили в эту функцию. Вам нужно только изменить, что вы работаете уже с `prevState`, а не с `this.state`. 368 | 369 | {title="src/App.js",lang=javascript} 370 | ~~~~~~~~ 371 | setSearchTopStories(result) { 372 | const { hits, page } = result; 373 | 374 | this.setState(prevState => { 375 | # leanpub-start-insert 376 | const { searchKey, results } = prevState; 377 | 378 | const oldHits = results && results[searchKey] 379 | ? results[searchKey].hits 380 | : []; 381 | 382 | const updatedHits = [ 383 | ...oldHits, 384 | ...hits 385 | ]; 386 | 387 | return { 388 | results: { 389 | ...results, 390 | [searchKey]: { hits: updatedHits, page } 391 | }, 392 | isLoading: false 393 | }; 394 | # leanpub-end-insert 395 | }); 396 | } 397 | ~~~~~~~~ 398 | 399 | Это исправит проблему с устаревшим состоянием. Однако есть ещё одно улучшение. Поскольку это функция, её можно извлечь для улучшения читабельности. Это ещё одно преимущество использования функции перед объектом — функция может работать вне компонента. Но вам нужно использовать функцию высшего порядка, чтобы передать результат. В конце концов, вы хотите обновить состояние на основе полученного результата из API. 400 | 401 | {title="src/App.js",lang=javascript} 402 | ~~~~~~~~ 403 | setSearchTopStories(result) { 404 | const { hits, page } = result; 405 | this.setState(updateSearchTopStoriesState(hits, page)); 406 | } 407 | ~~~~~~~~ 408 | 409 | Функция `updateSearchTopStoriesState()` должна возвращать функцию. Это функция высшего порядка. Вы можете определить эту функцию высшего порядка вне вашего компонента App. Обратите внимание на то, как теперь меняется объявление функции. 410 | 411 | {title="src/App.js",lang=javascript} 412 | ~~~~~~~~ 413 | # leanpub-start-insert 414 | const updateSearchTopStoriesState = (hits, page) => (prevState) => { 415 | const { searchKey, results } = prevState; 416 | 417 | const oldHits = results && results[searchKey] 418 | ? results[searchKey].hits 419 | : []; 420 | 421 | const updatedHits = [ 422 | ...oldHits, 423 | ...hits 424 | ]; 425 | 426 | return { 427 | results: { 428 | ...results, 429 | [searchKey]: { hits: updatedHits, page } 430 | }, 431 | isLoading: false 432 | }; 433 | }; 434 | # leanpub-end-insert 435 | 436 | class App extends Component { 437 | ... 438 | } 439 | ~~~~~~~~ 440 | 441 | Вот и всё. Использование функции вместо объекта в `setState()` исправляет потенциальные ошибки, а также повышает читаемость и поддержку вашего кода. Кроме того, код становится тестируемым вне компонента приложения. Вы можете экспортировать его и написать тест в виде упражнения. 442 | 443 | ### Упражнение: 444 | 445 | * прочитать больше о том, как [в React правильно использовать состояние](https://ru.reactjs.org/docs/state-and-lifecycle.html#%D0%9F%D1%80%D0%B0%D0%B2%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D1%8F) 446 | * экспортировать updateSearchTopStoriesState из файла 447 | * напишите тест для него, который передаёт данные (истории, страницы) и составленное предыдущее состояние и, наконец, ожидает новое состояние 448 | * улучшите методы `setState()` для использования функции 449 | * но только тогда, когда это имеет смысл, т.е. когда есть зависимость от свойства или состояния 450 | * снова запустите свои тесты и убедитесь, что всё в порядке 451 | 452 | ## Укрощение состояния 453 | 454 | Предыдущие главы показали, что управление состоянием может быть важной темой в более крупных приложениях. В целом, не только React, но и много фреймворков SPA противостоят этому. За последние годы приложения стали более сложными. Одна из серьёзных проблем в веб-приложениях в настоящее время — укрощение и контроль состояния. 455 | 456 | По сравнению с другими решениями, React уже сделал большой шаг вперёд. Однонаправленный поток данных и простой API для управления состоянием в компоненте являются незаменимыми. Эти концепции упрощают управление состоянием и изменениями в вашем состоянии на уровне компонентов и в определённой степени на уровне приложения. 457 | 458 | В условиях роста приложения становится всё труднее размышлять об изменениях состояния. Вы можете допустить баги, работая с устаревшим состоянием при использовании объекта вместо функции в `setState()`. Вы должны поднять состояние, чтобы поделиться необходимым или скрыть ненужное состояние между компонентами. Может случиться так, что компонент должен поднять состояние, потому что его родственный (одноуровневый) компонент зависит от него. Возможно, компонент находится далеко в дереве компонентов и, следовательно, вам нужно разделить это состояние по всему дереву компонентов. И наконец, компоненты в большей степени задействованы в управлении состоянием. Но ведь основная ответственность компонентов должна представлять пользовательский интерфейс, не так ли? 459 | 460 | Из-за всех этих причин существуют независимые решения по управлению состоянием и его сохранению. Эти решения используются не только в React. Однако это то, что делает экосистему React таким мощным местом. Вы можете использовать различные решения проблем, связанных с состоянием. Чтобы решить проблему масштабирования управления состоянием, вы, возможно, слышали о библиотеках [Redux](http://redux.js.org/docs/introduction/) или [MobX](https://mobx.js.org/ ). Вы можете использовать любое из этих решений в приложении React. Они включают расширения, такие как [react-redux](https://github.com/reactjs/react-redux) и [mobx-react](https://github.com/mobxjs/mobx-react), чтобы интегрировать их в слой представления React. 461 | 462 | Redux и MobX выходят за рамки данной книги. Когда вы закончите книгу, вы получите руководство о том, как вы можете продолжать изучать React и его экосистему. Одним из путей обучения может стать изучение Redux. Прежде чем вы погрузитесь в тему внешнего управления состоянием, я могу порекомендовать прочитать [эту статью](https://www.robinwieruch.de/redux-mobx-confusion/). Она призвана дать вам лучшее понимание того, как изучать внешнее управление состоянием. 463 | 464 | ### Упражнения: 465 | 466 | * узнайте больше о [внешнем управлении состоянием и о том, как его изучать](https://www.robinwieruch.de/redux-mobx-confusion/) 467 | * ознакомьтесь с моей второй книгой про [управление состоянием в React](https://roadtoreact.com/) 468 | 469 | {pagebreak} 470 | 471 | Вы изучили некоторые продвинутые техники управления состоянием в React! Давайте повторим последние темы: 472 | 473 | * React 474 | * поднятие состояние вверх и вниз до нужных компонентов 475 | * `setState()` может использовать функцию в качестве аргумента для предотвращения ошибок, связанных с устаревшим состоянием 476 | * существующие сторонние решения, которые помогут вам приручить состояние 477 | 478 | Исходный код можно найти в [официальном репозитории](https://github.com/the-road-to-learn-react/hackernews-client/tree/5.6). 479 | -------------------------------------------------------------------------------- /manuscript/contributor.md: -------------------------------------------------------------------------------- 1 | # Об этом переводе 2 | 3 | Многие люди приложили руку к написанию и улучшению книги *Путь к изучению React (The Road to learn React)* за последнее время. В настоящее время это одна из самых скачиваемых книг по изучению React.js. Первоначально книга была написана немецким инженером-программистом [Робином Вирухом (Robin Wieruch)](https://www.robinwieruch.de/). Но все переводы книги были бы невозможны без помощи других людей. 4 | 5 | ## Небольшая предыстория 6 | 7 | Перевод этой книги первоначально затеял фронтенд-разработчик [Азат С.](https://github.com/azat-io) (к сожалению, фамилию так и не удалось выяснить) осенью 2017 года, но, добавив только файлы для перевода, ничего не перевёл и больше не проявил никакое участие в переводе. Лишь только спустя пять месяцев, в конце февраля 2018 года, в репозитории перевода появилось движение — были актуализированы файлы перевода и переведён файл [README.md](https://github.com/the-road-to-learn-react/the-road-to-learn-react-russian/blob/master/README.md), а дальше снова долгое затишье до июня 2018. Но не стоит думать, что за эти несколько месяцев ничего не переводилось, вовсе нет, но очень нехотя и медленно. И наступило жаркое южное лето, и вот с этого момента работа над переводом пошла в полную силу... 8 | 9 | Небольшое важное примечание: это мой первый крупный перевод с английского, до этого я мало работал с React, поэтому перевести книгу было вдвойне интересно — изучить React и подучить английский язык. Я решил, что летом обязательно выпущу перевод книги, в конце концов это не может продолжаться вечно, и вот 22 июля 2018 книга _The Road to learn React_ вышла в русском переводе. Наслаждайтесь! И пожалуйста, если найдёте неточность, опечатку или какую-либо ошибку, напишите мне любым предпочтительным для вас способом. 10 | 11 | ## Немного о переводчике 12 | 13 | Раздел выше написан от моего имени, а всё остальное — переведено мною. Итак, пора представиться. Меня зовут Алексей Пыльцын, я веб-разработчик, в основном использую PHP и JavaScript, большим опытом похвастаться не могу, поэтому перечислю списком то, чем я занимаюсь в настоящее время: 14 | 15 | * поддерживаю [официальную документацию по PHP на русском языке](http://docs.php.net/manual/ru/); 16 | * время от времени [перевожу](https://medium.com/@lex111/latest) статьи по веб-разработке для [devSchacht](https://medium.com/devschacht) 17 | * участвую по мере своих возможностей в разного рода опенсорс-проектах на [GitHub](https://github.com/lex111/) 18 | * перевожу книги (да-да, это точно не последний перевод книги, в котором я принимаю участие, несмотря на то, как плохо или хорошо получился этот!) 19 | 20 | ## Немного о редакторе 21 | 22 | Трудно переоценить работу редактора в любой книге, даже пусть такой небольшой как эта, — это очень дорогого стоит! Я бесконечно благодарен Екатерине Назаровой, фронтенд-разработчику и просто отличному человеку, за вычитку перевода, за все те многочисленные найденные опечатки и неточности. Поверьте, без неё перевод книги точно был бы гораздо хуже, чем он сейчас есть! Екатерина — автор проекта [GetInstance](https://getinstance.info), интернет-журнала для фронтенд-разработчиков и автор одноимённого [YouTube-канала](https://www.youtube.com/channel/UCEBHlT_L1ME6e9ixaRPp0wg), подписывайтесь на её канал, это определённо стоит того! 23 | 24 | ## Соглашения, принятые в переводе 25 | 26 | В тексте данного перевода книги используется буква «ё» и французские кавычки, а также терминология из замечательного [словаря «Веб-стандартов»](https://github.com/web-standards-ru/dictionary). 27 | 28 | И последнее, но не менее важное: ссылки на документацию React ведут на [недавно созданный сайт с переводом документации React](https://ru.reactjs.org/). Кроме перевода книги я решил также создать перевод документации по React, на момент первоначального выпуска книги переведены только те страницы, на которые ссылается книга, но я призываю всех принять участие в дальнейшем переводе документации в [репозитории react-ru](https://github.com/js-rus/react-ru). 29 | 30 | Давайте вместе переведём и будем поддерживать документацию по React на русском языке! Этот проект специально направлен на сообщество, чтобы оно непосредственно принимало в нём участие. Точнее, я имею в виду, что в рунете много вариантов с переводом документации React, но все они различаются и не совсем актуальны. И главное, все они не похожи на оригинальный сайт, хотя лицензия не запрещает клонирование оригинального сайта, поэтому почему бы не объединиться и не создать единый перевод документации? В любом случае, если вам это интересно — присоединяйтесь! 31 | 32 | ## В добрый путь 33 | 34 | Спасибо, что скачали эту книгу, а если всю её прочли — большие молодцы, я надеюсь, это было не бесполезное, а интересное и познавательное чтение! Отдельно хочется поблагодарить всех тех, кто находил разного рода ошибки в переводе и сообщал о них. Лучше всего это сделать — создать ишью в [репозитории перевода](https://github.com/the-road-to-learn-react/the-road-to-learn-react-russian) (буду признателен, если вы ещё поставите лайк-звёздочку этому репозиторию!). 35 | 36 | {pagebreak} 37 | -------------------------------------------------------------------------------- /manuscript/deployChapter.md: -------------------------------------------------------------------------------- 1 | # Заключительные шаги к развёртыванию в продакшене 2 | 3 | В последних главах будет показано, как развернуть приложение в продакшене. Вы будете использовать бесплатный хостинг Heroku. По пути развёртывания (далее — деплой) вашего приложения вы узнаете больше о *create-react-app*. 4 | 5 | ## Команда eject 6 | 7 | Следующий шаг и знание **не требуются** для деплоя приложения на продакшен. Тем не менее, я хочу объяснить это. *create-react-app* поставляется с одной возможностью, чтобы оставаться расширяемым, но и также предотвращать привязку к поставщику (vendor lock-in). Привязка к поставщику обычно происходит, когда вы покупаете, привязываетесь к технологии без лёгкой замены её использования на другую в будущем. К счастью, в *create-react-app* есть такая возможность, называемая «eject». 8 | 9 | В вашем *package.json* вы найдёте скрипты для запуска (*start*), выполнения тестов (*test*) и сборки (*build*) приложения. Но есть ещё последний скрипт — *eject*. Вы можете его попробовать, но учтите, что обратного пути уже не будет. **Это односторонняя операция. Как только вы выполните eject, пути назад не будет, это означает, что вы больше не сможете использовать *create-react-app*!** Если вы только начали изучать React, нет смысла оставлять удобное окружение, предоставляемое *create-react-app*, т.к. выполнение данной команды раскроет все зависимости и конфигурационные файлы, созданные и используемые утилитой *create-react-app*. 10 | 11 | Если вы запустите `npm run eject`, команда скопирует всю конфигурацию в новый каталог *config/*, а зависимости в файл *package.json*. Таким образом вы сконвертируете весь проект в собственную настройку для сборки вашего приложения со всеми используемыми инструментами, включая Babel и Webpack; в итоге получится так, словно вы никогда и не использовали *create-react-app*. После этого у вас будет полный контроль над всеми этими инструментами и вы будете точно знать, что использовалось под капотом *create-react-app* при сборки вашего приложения. 12 | 13 | В официальной документации говорится, что *create-react-app* подходит для малых и средних проектов. Поэтому не считайте, что вы обязательно должны использовать команду «eject». 14 | 15 | ### Упражнения: 16 | 17 | * прочитайте подробнее про команду [eject](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#npm-run-eject) 18 | 19 | ## Деплой приложения 20 | 21 | В конце концов, ни одно приложение не должно оставаться локально на компьютере. Вы хотите показать его всему миру. Heroku — это платформа как услуга (Platform as a Service, PaaS), где вы можете разместить ваше приложение. Они предлагают безболезненную интеграцию с React. В частности, приложение, созданное с помощью *create-react-app*, можно развернуть за считанные минуты. Это деплой приложения без всякой настройки конфигурации, что следует философии *create-react-app*. 22 | 23 | Перед деплоем приложения в Heroku необходимо выполнить два шага: 24 | 25 | * установите [CLI Heroku](https://devcenter.heroku.com/articles/heroku-cli) 26 | * зарегистрируйте [бесплатный аккаунт в Heroku](https://www.heroku.com/) 27 | 28 | Если у вас установлен Homebrew, вы можете установить CLI Heroku из командной строки: 29 | 30 | {title="Командная строка",lang="text"} 31 | ~~~~~~~~ 32 | brew update 33 | brew install heroku-toolbelt 34 | ~~~~~~~~ 35 | 36 | Теперь можно воспользоваться git и CLI Heroku для деплоя вашего приложения. 37 | 38 | {title="Командная строка",lang="text"} 39 | ~~~~~~~~ 40 | git init 41 | heroku create -b https://github.com/mars/create-react-app-buildpack.git 42 | git add . 43 | git commit -m "react-create-app on Heroku" 44 | git push heroku master 45 | heroku open 46 | ~~~~~~~~ 47 | 48 | Вот и всё. Надеюсь, ваше приложение запущено и работает. При возникновении проблем можно ознакомиться со следующими ресурсами: 49 | 50 | * [Основы Git и GitHub](https://www.robinwieruch.de/git-essential-commands/) 51 | * [Деплой React-приложения без какой-либо конфигурации](https://blog.heroku.com/deploying-react-with-zero-configuration) 52 | * [Пакет для сборки приложения, созданного с помощью create-react-app, на Heroku](https://github.com/mars/create-react-app-buildpack) 53 | -------------------------------------------------------------------------------- /manuscript/finalwords.md: -------------------------------------------------------------------------------- 1 | # Краткий обзор 2 | 3 | Это была последняя глава книги. Я надеюсь, что вам понравилось читать данную книгу и что она помогла вам в изучении React. Если вам понравилась книга, поделитесь ею с вашими друзьями в целях научить их React. Её следует распространять в виде небольшого подарка. Кроме того, для меня бы многое значило, если бы вы уделили несколько минут своего времени для написания отзыва о книге на [Amazon](https://www.amazon.com/dp/B077HJFCQX) или на [Goodreads](https://www.goodreads.com/book/show/37503118-the-road-to-learn-react). 4 | 5 | **Итак, что мне теперь делать после прочтения этой книги?** Вы можете либо расширить приложение по своему желанию или попробовать сделать собственный проект с использованием React. Прежде чем вы погрузитесь в другую книгу, курс или какой-либо обучающий материал, вам нужно создать собственный реальный проект на React. Сделайте его в течение одной недели, разверните где-нибудь его на продакшене, и свяжитесь со [мной](https://twitter.com/rwieruch) или другими заинтересованными людьми для демонстрации того, что вы сделали, используя React. Мне любопытно, что вы создадите после того, как прочитаете книгу. 6 | 7 | Если вы в поисках того, как ещё можно расширить своё приложение, я могу порекомендовать несколько путей обучения после того, как вы использовали только обычный React в этой книге: 8 | 9 | * **Управление состоянием:** Вы использовали `this.setState()` и `this.state` в React для получения доступа к локальному состоянию компонента и управления им. Это прекрасное начало. Однако в более крупном приложении вы непременно столкнётесь с [ограничениями локального состояния компонента React](https://www.robinwieruch.de/learn-react-before-using-redux/). Поэтому можно использовать стороннюю библиотеку управления состоянием, например [Redux или MobX](https://www.robinwieruch.de/redux-mobx-confusion/). На сайте курса [Путь к изучению React](https://roadtoreact.com/), вы найдёте курс «Укрощение состояния в React» (Taming the State in React), который учит расширенному управлению состоянием в React, используя Redux и MobX. В этом курсе есть также электронная книга, но я рекомендую кроме неё погрузиться в исходный код и просмотр скринкастов. Если вам понравилась эта книга, вы обязательно должны заказать «Укрощение состояния в React». 10 | 11 | * **Подключение к базе данных и/или аутентификация:** В растущем приложении React вы рано или поздно будете сохранять данные. Данные должны храниться в базе данных, чтобы они оставались доступными после завершения сессии браузера и могли использоваться различными пользователями вашего приложения. Самый простой способ добавить базу данных — использовать Firebase. В [этой всеобъемлющей обучающей статье](https://www.robinwieruch.de/complete-firebase-authentication-react-tutorial/) вы найдёте пошаговое руководство по использованию аутентификации Firebase (регистрация, вход, выход и т.д.) в контексте React. Кроме того, вы будете использовать базу данных Firebase в режиме реального времени для хранения пользовательских сущностей. После этого решать вам, хранить больше данных в базе данных, которые необходимы вашему приложению. 12 | 13 | * **Инструменты Webpack и Babel:** В книге вы использовали *create-react-app* для создания приложения. В какой-то момент, когда вы овладеете React, вы, возможно, захотите изучить инструменты, которые используются внутри него. Это позволит вам создать собственный проект без применения *create-react-app*. Я могу порекомендовать последовать минимальной настройке [Webpack и Babel](https://www.robinwieruch.de/minimal-react-webpack-babel-setup/). Позже, вы можете добавить больше инструментов к уже существующим. Например, вы можете [использовать ESLint](https://www.robinwieruch.de/react-eslint-webpack-babel/), чтобы придерживаться единого стиля кода в приложении. 14 | 15 | * **Синтаксис компонентов React:** Возможности и передовые практики по реализации компонентов React со временем меняются. Вы найдёте много способов написания компонентов React, особенно классовых компонентов React, на других учебных ресурсах. Вы можете посмотреть [этот репозиторий на GitHub](https://github.com/the-road-to-learn-react/react-alternative-class-component-syntax), чтобы узнать об альтернативном способе написания классов-компонентов в React. Используя объявления полей класса, вы можете писать их ещё более лаконично в будущем. 16 | 17 | * **Другие проекты:** Изучив чистый React, всегда хорошо сначала применить полученные знания в своих проектах, прежде чем начинать изучать что-то новое. Вы можете написать собственную игру в крестики-нолики или простой калькулятор на React. Есть много обучающих статей, которые используют только React, чтобы создать что-то захватывающее. Посмотрите мою статью про создание [постраничный список с бесконечной прокруткой](https://www.robinwieruch.de/react-paginated-list/), [просмотр твитов из ленты Twitter](https://www.robinwieruch.de/react-svg-patterns/) или [подключение React-приложения к Stripe для снятия денег](https://www.robinwieruch.de/react-express-stripe-payment/). Экспериментируйте с этими мини-приложениями, чтобы научиться уверенно работать с React. 18 | 19 | * **Компоненты пользовательского интерфейса (UI):** Вы совершаете ошибку, слишком рано представив библиотеку для пользовательского интерфейса компонентов в вашем проекте. Во-первых, вам нужно знать, как с нуля реализовать и использовать выпадающий список, чекбокс или диалоговое окно, используя стандартные HTML-элементы, после чего реализовать их в React. У большинства этих компонентов будет собственное локальное состояние. Например, у чекбокса будут свойства состояния, указывающие, отмечен он или нет. Таким образом, вы должны реализовать их как управляемые компоненты. После того, как у вас есть все основные реализации UI, вы можете задуматься над созданием библиотеки компонентов пользовательского интерфейса, собрав в неё чекбоксы, диалоговые окна и всё то, что вы реализовали в виде React-компонентов. 20 | 21 | * **Организация кода:** По пути изучения книги вы наткнулись на одну главу про организацию кода. Вы можете применить полученные значения сейчас, если ещё не сделали этого. Организуйте ваши компоненты в структурированные файлы и каталоги (модули). Кроме того, это поможет вам понять и изучить принципы разделения кода, повторного использования, дальнейшей поддержки и проектирования API модуля. В конце концов ваше приложение будет увеличиваться, и вам так или иначе придётся разделять его по модулям. Так что лучше начать делать это прямо сейчас. 22 | 23 | * **Тестирование:** Книга только лишь прошлась по тестированию. Если вы не знакомы с общей темой тестирования, вы можете глубже погрузиться в концепции модульного тестирования и интеграционного тестирования, особенно в контексте приложений React. Я бы порекомендовал для этого использовать такие инструменты, как Enzyme и Jest, чтобы совершенствовать ваш подход к тестированию с помощью модульных тестов и тестов снимками в React. 24 | 25 | * **Маршрутизация:** Вы можете реализовать маршрутизацию для своего приложения с помощью [react-router](https://github.com/ReactTraining/react-router). До сих пор в приложении была только одна страница. React Router поможет вам иметь несколько страниц, доступных по нескольким URL-адресам. Когда вы добавляете маршрутизацию в приложение, вам не нужны дополнительные запросы к веб-серверу для получения следующей страницы. Маршрутизатор сделает всё за вас на стороне клиента. Так что придётся замарать руки и настроить маршрутизацию в своём приложении. 26 | 27 | * **Проверка типов:** В одной главе вы использовали PropTypes в React для определения интерфейсов компонента. Это распространённая хорошая практика для предотвращения ошибок. Но PropTypes проверяются только во время выполнения. Вы можете пойти дальше и добавить статическую проверку типов, выполняемую во время компиляции. [TypeScript](https://www.typescriptlang.org/) — один из таких популярных подходов. Но в экосистеме React люди часто используют [Flow](https://flowtype.org/). Я могу порекомендовать дать шанс Flow, если вы заинтересованы в том, чтобы сделать ваше приложение более надёжным. 28 | 29 | * **React Native:** [React Native](https://facebook.github.io/react-native/) доставляет ваши приложения на мобильные устройства. Вы можете применить свои знания из React для создания приложений для iOS и Android. Кривая обучения, как только вы научились React, не должна быть слишком крутой в React Native. Обе технологии разделяют одни и те же принципы. На мобильном устройстве вы будете сталкиваться только с компонентами макета, отличными от тех, к которым вы привыкли в веб-приложениях. 30 | 31 | В общем, я приглашаю вас посетить мой [сайт](https://www.robinwieruch.de) для получения более интересных тем по веб-разработке и разработке программного обеспечения в принципе. Вы можете [подписаться на мою информационную рассылку](https://www.getrevue.co/profile/rwieruch) для получения обновлений о новых статьях, книгах и курсах. Кроме того, сайт курса [Путь к изучению React](https://roadtoreact.com) предлагает более сложные курсы, чтобы узнать об экосистеме React. Вам стоит их увидеть! 32 | 33 | И последнее, но не менее важное: я надеюсь найти больше [меценатов](https://www.patreon.com/rwieruch), у которых есть возможность поддерживать мой контент. Есть много студентов, которые не могут позволить себе оплатить образовательный контент. Вот почему я размещаю много своего контента бесплатно. Поддержите меня в моих делах в качестве моего патрона, и я смогу продолжать поддерживать эти трудозатраты, чтобы бесплатно обучать других. 34 | 35 | Напомню ещё раз, если вы получили удовольствие от книги, я хочу, чтобы вы на мгновение подумали о человеке, которому могла бы понравиться данная книга, чтобы научиться React. Познакомьтесь с этим человеком и поделитесь с ним этой книгой. Это бы много значило для меня. Книга предназначена для других. Со временем она будет улучшаться, когда больше людей прочитают её и поделятся своими отзывами со мной. Я надеюсь также увидеть ваши отклики, отзывы или оценки! 36 | 37 | Большое вам спасибо за то, что вы прочитали «Путь к изучению React». 38 | 39 | Робин 40 | -------------------------------------------------------------------------------- /manuscript/foreword.md: -------------------------------------------------------------------------------- 1 | # Предисловие 2 | 3 | _Путь к изучению React (The Road to learn React)_ научит вас основам React. В ходе изучения вы создадите реальное приложение, используя обычный React без сложного инструментария. Вам будет объяснено всё, начиная от настройки проекта и заканчивая развёртыванием его на сервере. Книга содержит дополнительные ссылки для изучения и упражнения в конце каждого раздела глав. После прочтения книги вы сможете создавать собственные приложения на React. Материал книги постоянно обновляется мною и сообществом. 4 | 5 | В книге _Путь к изучению React_ я хочу изложить основы, прежде чем вы начнёте погружение в обширную экосистему React. В книге мало внимания уделяется инструментам и внешнему управлению состоянием, но зато есть много информации о React. Она объясняет общие концепции, типовые решения и лучшие практики, используемые при разработке React-приложений для работы в реальном мире. 6 | 7 | Вы научитесь создавать собственное приложение на React, которое содержит такие возможности, как постраничная навигация, кеширование на стороне клиента и такие пользовательские взаимодействия, как поиск и сортировка. Кроме того, в процессе чтения книги вы перейдёте с JavaScript ES5 на JavaScript ES6. Я надеюсь, что эта книга отразит мой энтузиазм по поводу React и JavaScript и поможет вам начать их изучение. 8 | 9 | {pagebreak} 10 | 11 | # Об авторе 12 | 13 | Робин Вирух (Robin Wieruch) — немецкий разработчик программного обеспечения и веб-приложений, который занимается обучением и преподаванием программирования JavaScript. После получения степени магистра в области информатики, он продолжает учиться каждый день. Его опыт в мире стартапа, где он много использовал JavaScript во время работы и свободного времени. Это дало ему возможность учить других людей этим темам. 14 | 15 | В течение нескольких лет Робин тесно сотрудничал с большой командой инженеров в компании [Small improvements](https://www.small-improvements.com/) над работой крупномасштабного приложения. Компания создаёт SaaS-продукт, позволяющий клиентам создавать культуру обратной связи в своей компании. Под капотом приложение на фронтенде работало на JavaScript, в качестве бекенда использовался язык Java. На фронтенде первая итерация была написана на Java с использованием Wicket Framework и jQuery. Когда первое поколение SPA стало популярным, компания перешла на Angular 1.x для фронтенда приложения. После использования Angular в течение более двух лет стало ясно, что Angular не лучшее решение для интенсивного взаимодействия с состоянием в те дни. Именно поэтому компания сделала окончательный переход на React и Redux, что позволило приложению успешно работать в больших масштабах. 16 | 17 | Во время своей работы в компании Робин регулярно писал статьи о веб-разработке на своём сайте. Он получал отличные отзывы от людей о своих статьях, что в конечном счёте позволило улучшить его стиль письма и обучения. Статья за статьёй, Робин вырос в своей способности учить других. В его первой статье было слишком много лишнего, что усложнило её понимание для студентов, но со временем он улучшил эту статью, сосредоточив внимание на преподавании только одной темы. 18 | 19 | В настоящее время Робин занимается собственным делом, обучая других людей. Он считает своей полноценной деятельностью видеть, как его студенты процветают, давая им чёткие цели и короткий цикл обратной связи. Это то, чему бы вы научились, работая в компании, предоставляющей обратную связь, не так ли? Но без достаточной практики, он не смог бы обучать других. Именно поэтому он уделяет своё оставшееся время на программирование. Вы можете найти более подробную информацию о Робине и о способах его поддержки и работы с ним на его [сайте](https://www.robinwieruch.de/about). 20 | 21 | {pagebreak} 22 | 23 | # Отзывы 24 | 25 | Есть много [отзывов](https://roadtoreact.com/), [оценок](https://www.goodreads.com/book/show/37503118-the-road-to-learn-react) и [отзывов](https://www.amazon.com/dp/B077HJFCQX) о книге, подтверждающие её качество. Я так горжусь этим, потому что я никогда не ожидал такой мощной обратной связи. Если вам понравится данная книга, я бы с удовольствием хотел получить вашу оценку или обзор. Это помогает мне рассказать о книге. Ниже приведён краткий отрывок из этих хороших отзывов: 26 | 27 | **[Мухаммад Кашиф (Muhammad Kashif)](https://twitter.com/appsdevpk/status/848625244956901376)**: «"Путь к изучению React" — это уникальная книга, которую я рекомендую любому студенту или профессионалу, интересующемуся обучением основам React до продвинутого уровня. Она наполнена содержательными советами и методами, которые трудно найти в другом месте, и замечательное использование примеров и ссылок на примеры проблем, у меня есть 17-летний опыт разработки веб-приложений и десктопных приложений, и до чтения этой книги у меня были проблемы с обучением React, но эта книга действует как магия». 28 | 29 | **[Андре Варгас (Andre Vargas)](https://twitter.com/andrevar66/status/853789166987038720)**: «"Путь к изучению React" Робина Вируха — такая потрясающая книга! Большинство из того, что я узнал о React и даже ES6, было получено с помощью неё!» 30 | 31 | **[Николас Хант-Уокер (Nicholas Hunt-Walker), инструктор Python в школе программирования в Сиэтле](https://twitter.com/nhuntwalker/status/845730837823840256):** «Это одна из самых хорошо написанных и содержательных книг по программированию, с которой я когда-либо работал. Твёрдое введение в React и ES6». 32 | 33 | **[Остин Грин (Austin Green)](https://twitter.com/AustinGreen/status/845321540627521536)**: «Спасибо, очень понравилась книга. Идеальное сочетание для изучения React, ES6 и концепций программирования высшего уровня». 34 | 35 | **[Николь Фергюсон (Nicole Ferguson)](https://twitter.com/nicoleffe/status/833488391148822528)** : «В эти выходные я прохожу курс обучения Робина Вируха, и я почти чувствую вину за то, что так много веселья». 36 | 37 | **[Каран (Karan)](https://twitter.com/kvss1992/status/889197346344493056)**: «Закончил чтение "Путь к изучению React". Лучшая книга для новичка в мире React и JS. Элегантное ознакомление с ES. Респект! :)» 38 | 39 | **[Эрик Приоу (Eric Priou)](https://twitter.com/erixtekila/status/840875459730657283)**: «"Путь к изучению React" Робина Вируха — обязательное чтение. Чистота и краткость для изучения React и JavaScript». 40 | 41 | Начинающий разработчик: «Я только что закончил книгу как неопытный разработчик, спасибо за работу над ней. Мне было легко изучать её, и я уверен, что в ближайшие дни я начну разработку нового приложения с нуля. Книга была намного лучше официального учебного введения React.js, который я пробовал раньше (и не мог закончить из-за отсутствия подробностей). Упражнения в каждой главе были очень полезными». 42 | 43 | Студент: «Лучшая книга, чтобы начать изучать ReactJS. Разработка проекта двигается вместе с изучаемыми концепциями, которые помогают понять предмет. Я нашёл "Code and learn" как лучший способ освоить программирование, и эта книга точно следует этому. 44 | 45 | **[Томас Локни (Thomas Lockney)](https://www.goodreads.com/review/show/1880673388)**: «Довольно солидное введение в React, которое не пытается быть всеобъемлющим. Я просто хотел понять, что это такое, и данная книга дала мне именно это. Я не следил за всеми маленькими сносками, чтобы узнать о новых возможностях ES6, которые я пропустил ("Я бы не сказал, что я скучал по этому, Боб" — [отсылка](http://quotegeek.com/quotes-from-movies/office-space/4848/) из сериала "Офисное пространство") . Но я уверен, что те из вас, кто останавливался и прилежно следил за ними, вероятно, узнают намного больше, чем то, чему учит книга.». 46 | 47 | {pagebreak} 48 | 49 | # Образование для детей 50 | 51 | Эта книга должна позволить каждому изучить React. Однако не все имеют привилегии использовать эти ресурсы, потому что не все получают образование на английском языке в первую очередь. Таким образом, я хочу использовать данный проект для поддержки проектов, которые учат детей английскому языку в развивающихся странах. 52 | 53 | * 11. April to 18. April, 2017, [Giving Back, By Learning React](https://www.robinwieruch.de/giving-back-by-learning-react/) 54 | 55 | {pagebreak} 56 | 57 | # Часто задаваемые вопросы 58 | 59 | **Как получать обновления** У меня есть два канала связи, где я делюсь обновлениями о моём контенте. Вы можете [подписаться на обновления по электронной почте](https://www.getrevue.co/profile/rwieruch) или [следовать за мной в Twitter](https://twitter.com/rwieruch). Независимо от канала, моя цель состоит в том, чтобы делиться только качественным контентом. Вы никогда не получите спама. Как только вы получите обновление об изменении книги, вы можете загрузить её новую версию. 60 | 61 | **Используется ли последняя версия React?** Книга всегда актуализируется при обновлении версии React. Обычно книги устаревают вскоре после их выхода. Поскольку эта самоизданная книга, я могу обновлять её, когда захочу. 62 | 63 | **Рассматривается ли Redux?** Нет. Поэтому я написал вторую книгу. «Путь к изучению React» должна дать вам прочную основу, прежде чем вы погрузитесь в продвинутые темы. Реализация примера приложения в книге покажет, что Redux не требуется в создании приложения в React. После того, как вы прочтёте книгу, вы сможете реализовать надёжное приложение без Redux. Затем вы можете прочитать мою вторую книгу, чтобы узнать [Redux](https://roadtoreact.com/course-details?courseId=TAMING_THE_STATE). 64 | 65 | **Используется ли JavaScript ES6?** Да. Но не волнуйтесь. Всё будет хорошо, если вы знакомы только с JavaScript ES5. Все возможности JavaScript ES6, которые я описываю во время изучения React, я иллюстрирую примерами на ES5 в книге. Объясняется каждая особенность по ходу дела. Эта книга не только учит React, но и всем полезным возможностям JavaScript ES6 в контексте React. 66 | 67 | **Как получить доступ к исходному кода проектов и сериям скринкастов?** Если вы купили один из расширенных пакетов, который даёт вам доступ к исходному коду проектов, серии скринкастов или любой другой пакет, вы должны найти их в своей [панели курсов](https://roadtoreact.com/my-courses). Если вы купили курс где-то ещё, отличной от [официальной «Road to React»](https://roadtoreact.com), вам необходимо создать учётную запись на данной платформе, перейти на страницу администратора и обратиться ко мне с одним из шаблонов электронной почты. После этого я могу зачислить вас на курс. Если вы не купили один из расширенных пакетов, вы можете в любое время связаться с ним для обновления, чтобы получить доступ к исходному коду проектов и сериям скринкастов. 68 | 69 | **Как я могу получить помощь во время чтения книги?** У книги есть [канал в Slack](https://slack-the-road-to-learn-react.wieruch.com/) для людей, которые читают её. Вы можете присоединиться к каналу, чтобы получить помощь или помочь другим. В конце концов, помощь другим может улучшить ваши знания. 70 | 71 | **Есть ли площадка для решения проблем?** Если у вас возникли проблемы, присоединитесь к каналу Slack. Кроме того, вы можете посмотреть на [открытые ишью на GitHub](https://github.com/rwieruch/the-road-to-learn-react/issues). Возможно, ваша проблема уже встречалась, и вы можете найти её решение. Если ваша проблема не была упомянута, не стесняйтесь открывать новую проблему, где вы можете объяснить её суть, необязательно приложить скриншот и предоставить другие подробности (например, страница книги, версия node). В конце концов, я пытаюсь внести все исправления в следующие выпуски книги. 72 | 73 | **Есть ли гарантия возврата денег?** Да, есть 100% гарантия возврата денег в течение двух месяцев, если вы не считаете, что курс обучения вам подходит. Просьба связаться со мной, чтобы получить возврат средств. 74 | 75 | **Как поддержать проект?** Если вам нравится мой контент, вы можете [поддержать меня](https://www.robinwieruch.de/about/). Кроме того, я был бы признателен, если бы вы распространили информацию об этой книге после того, как прочитаете и полюбите её. А ещё я с удовольствием хотел бы видеть вас моим [патроном в Patreon](https://www.patreon.com/rwieruch). 76 | 77 | **Какова ваша мотивация для написания книги?** Я хочу учить данной теме в единообразной форме. Вы часто находите материал в интернете, который больше не обновляется с тех пор, как был опубликован, или обучает только небольшой части темы. Когда вы узнаете что-то новое, люди изо всех сил пытаются найти последовательные и актуальные ресурсы для учёбы. Я хочу дать вам этот систематический и современный опыт обучения. Кроме того, я надеюсь, что смогу поддержать меньшинства в своих проектах, предоставив им контент бесплатно или [оказывая другого рода воздействия](https://www.robinwieruch.de/giving-back-by-learning-react/). Кроме того, в последнее время я обнаружил, что доволен собой, когда обучаю других программированию. Для меня это важная деятельность, поэтому я предпочитаю её любой другой работе от 9 до 5 в любой компании. Вот почему я надеюсь продолжить этот путь в будущем. 78 | 79 | **Есть призыв к действию?** Да. Я хочу, чтобы вы на мгновение подумали о человеке, который хорошо бы разбирался в React. Такой человек мог бы уже показать свой интерес, а может быть, в середине изучения React или, возможно, ещё изъявил о желании узнать React. Познакомьтесь с этим человеком и поделитесь книгой. Это много значило бы для меня. Эта книга предназначена для других. 80 | 81 | {pagebreak} 82 | 83 | # Журнал изменений 84 | 85 | **10 января 2017:** 86 | 87 | * [пулреквест со второй версией книги](https://github.com/rwieruch/the-road-to-learn-react/pull/18) 88 | * более дружелюбный для начинающих 89 | * На 37% больше контента 90 | * 30% улучшенного контента 91 | * 13 улучшенных и новых тем 92 | * 140 страниц учебного материала 93 | * [+ интерактивный курс книги на educative.io](https://www.educative.io/collection/5740745361195008/5676830073815040) 94 | 95 | **8 марта 2017:** 96 | 97 | * [пулреквест с третьей версией книги](https://github.com/rwieruch/the-road-to-learn-react/pull/34) 98 | * На 20% больше контента 99 | * 25% улучшенного контента 100 | * 9 новых тем 101 | * 170 страниц учебного материала 102 | 103 | **15 апреля 2017:** 104 | 105 | * обновление до React 15.5 106 | 107 | **5 июля 2017:** 108 | 109 | * обновление до node 8.1.3 110 | * обновление до npm 5.0.4 111 | * обновление до create-react-app 1.3.3 112 | 113 | **17 октября 2017:** 114 | 115 | * обновление до node 8.3.0 116 | * обновление до npm 5.5.1 117 | * обновление до create-react-app 1.4.1 118 | * обновление до React 16 119 | * [пулреквест с четвёртой версией книги](https://github.com/rwieruch/the-road-to-learn-react/pull/72) 120 | * На 15% больше контента 121 | * 15% улучшенного контента 122 | * 3 новые темы (Привязки, Обработки событий, Обработка ошибок) 123 | * 190+ страниц учебного материала 124 | * [более 9 проектов с исходным кодом](https://roadtoreact.com/course-details?courseId=THE_ROAD_TO_LEARN_REACT) 125 | 126 | **17 февраля 2018:** 127 | 128 | * обновление до node 8.9.4 129 | * обновление до npm 5.6.0 130 | * обновление до create-react-app 1.5.1 131 | * [пулреквест с пятой версией книги](https://github.com/the-road-to-learn-react/the-road-to-learn-react/pull/105) 132 | * больше путей для обучения 133 | * дополнительный материал для чтения 134 | * 1 новая тема (Axios вместо Fetch) 135 | 136 | {pagebreak} 137 | 138 | # Как читать книгу? 139 | 140 | Книга — это моя попытка научить React, пока вы будете писать приложение. Это практическое руководство по изучению React, а не справочник по React. Вы напишите приложение Hacker News, которое взаимодействует с реальным API. Среди нескольких интересных тем, эта книга охватывает управление состоянием в React, кеширование и взаимодействие (сортировка и поиск). В процессе вы узнаете лучшие практики и шаблоны в React. 141 | 142 | Кроме того, в книге представлен переход от использования JavaScript ES5 в пользу JavaScript ES6. React охватывает множество возможностей JavaScript ES6, и я хочу показать вам, как вы можете их использовать. 143 | 144 | В целом, каждая глава книги будет основываться на предыдущей главе. Каждая глава научит вас чему-то новому. Не торопитесь изучать материал книги. Лучше пошагово усваивать новый материал. Вы можете использовать свои собственные реализации и узнать больше о теме. В конце каждой главы я даю дополнительные ресурсы для чтения и упражнения. Если вы действительно хотите изучить React, я настоятельно рекомендую прочитать дополнительные ресурсы для изучения и попрактиковаться на упражнениях. После того, как вы прочтёте главу, возьмите паузу в обучении, прежде чем снова продолжить. 145 | 146 | В конце книги, у вас будет законченное приложение на React, которое можно использовать в продакшене. Я очень хочу увидеть ваши результаты, поэтому, пожалуйста, напишите мне, когда закончите изучение книги. Последняя глава книги предоставит вам несколько вариантов продолжения вашего путешествия по React. В общем, вы найдёте много соответствующих тем, связанных с React на [моём личном сайте](https://www.robinwieruch.de/). 147 | 148 | Так как вы читаете книгу, я думаю, вы новичок в React. Это прекрасно. В конце концов, я надеюсь получить ваши отзывы для дальнейшего улучшения материала, чтобы каждый мог изучить React. Вы можете оказать непосредственное участие на [GitHub](https://github.com/rwieruch/the-road-to-learn-react) или написать мне в [Twitter](https://twitter.com/rwieruch). 149 | 150 | {pagebreak} 151 | 152 | # Вызов 153 | 154 | Лично я много пишу о своем обучении. Вот как я стал тем, кто я есть. Вы преподаете тему во всей красе, только когда сами изучили её. Поскольку преподавание помогло мне в моей карьере, я хочу, чтобы вы испытали тот же эффект. Но сначала вам следует сформировать привычку учиться. Мой вызов для этой книги заключается в следующем: учите других тому, чему вы учитесь, читая данную книгу. Несколько советов, как вы могли бы достичь этого: 155 | 156 | * Напишите в блоге о конкретной теме из книги. Речь идет не о копировании и вставке материала, найдите собственные слова, чтобы объяснить рассматриваемую тему, а затем берите новую проблему и решайте её, и погружайтесь еще больше в тему, пока не поймете каждую деталь. Затем научите этому других посредством этой статьи. Вы увидите, как заполнились знания в ваших пробелах и как написание обучающих статей открывает двери для вашей карьеры в долгосрочной перспективе. 157 | 158 | * Если вы активны в социальных сетях, то поделитесь со своими друзьями тем, что узнали во время чтения книги. Например, вы можете написать в Твиттере о своем последнем уроке из книги, который может быть интересен и для других. Просто сделайте скриншот отрывка из книги или даже лучше — напишите об этом своими словами. Вот как вы можете начать обучать других, не вкладывая много времени. 159 | 160 | * Если вы чувствуете себя уверенно для записи вашего процесса обучения во время чтения, поделитесь этим с другими, используя Facebook Live, YouTube Live или Twitch. Это поможет вам оставаться сосредоточенным и прокладывать себе путь через книгу. Даже если, что у вас не так много людей во время прямой трансляции, вы всегда можете позже загрузить запись на YouTube. Кроме того, это отличный способ рассказать о своих проблемах и как вы собираетесь их решить. 161 | 162 | Мне бы очень хотелось, чтобы люди занимались последним пунктом: записывали себя во время чтения этой книги, во время разработки своих приложений и выполнения упражнений и выкладывали окончательную версию видеоролика на YouTube. Если части между записью занимают больше времени, просто вырежьте видео или используйте эффект «таймлапс». Если вы застряли и вам нужно исправить ошибку, не останавливайте запись, потому что такие отрывки видео могут быть ценны для вашей аудитории, которая может столкнуться с теми же проблемами. Я считаю, что важно сохранить подобные части в вашем видео. Несколько советов по видео: 163 | 164 | * Записывайте на своем родном языке, но, возможно, и на английском, если вам это удобно. 165 | 166 | * Как можно подробнее и яснее объясняйте свои мысли — то, что вы делаете или проблемы, с которыми вы сталкиваетесь. Наличие видео — это только одна часть задачи, другая — сам рассказ о реализации. Повествование не обязательно должно быть идеальным, вместе этого он должен чувствоваться естественным и не таким безупречным, как и все другие видеокурсы в интернете, где никто не сталкивается с проблемами, а показывается только само решение без ясных объяснений. 167 | 168 | * Если вы столкнетесь с багами, попытайтесь их исправить. Делайте это самостоятельно, не сдавайтесь и расскажите о проблеме и о том, как вы пытаетесь ее решить. Это поможет другим отслеживать ваш мыслительный процесс. Как я уже говорил, нет смысла подражать отточенным другим видеокурсам, где преподаватель никогда не сталкивается с проблемами. Самое ценная часть — это видеть, как кто-то еще исправляет ошибку в исходном коде, и заодно объясняет в в чём была её причина. 169 | 170 | * Несколько слов о технической стороне записи: проверьте аудиотехнику перед записью длинного видеоролика. Громкость и качество должны быть в порядке. Что касается вашего редактора/IDE/терминала — не забудьте увеличить размер шрифта. Возможно, стоит разместить редактор с кодом и браузер рядом, чтобы их вместе было видно. Если не получается или это вам не подходит, сделайте их полноэкранными и переключайтесь между ними (например, в MacOS это комбинация клавиш `CMD` и `Tab`). 171 | 172 | * Отредактируйте видео самостоятельно, прежде чем загрузить его на YouTube. Оно не обязательно должно быть высокого качества, но вам следует стараться держать его кратким по длительности (например, опуская отрывки для чтения и скорее кратко формулировать шаги своими словами). 173 | 174 | В конце концов, вы можете обратиться ко мне для продвижения видео. Если видео получится удачным, я бы также хотел включить его в эту книгу в качестве официального дополнительного материала. Просто свяжись со мной, как только вы закончите работу над ним. В конце концов, я надеюсь, что вы примете этот вызов, чтобы улучшить свой опыт обучения во время чтения книги. Я желаю вам всего наилучшего! 175 | 176 | {pagebreak} 177 | --------------------------------------------------------------------------------