Вы находитесь на странице: 1из 452

Первый контакт .............................................................................................................................................

3
Первая программа ....................................................................................................................................... 20
8. Переменные и константы ................................................................................................................... 24
9. Экспорт и программы из нескольких файлов ............................................................................ 29
10. Функции ................................................................................................................................................. 32
11. Объекты и Массивы ............................................................................................................................ 37
12.Принимаем решение .............................................................................................................................. 41
13. Циклы while и for ............................................................................................................................ 44
14. Строки .................................................................................................................................................... 47
15. Продвинутые функции........................................................................................................................ 54
16. Таймауты и интервалы ..................................................................................................................... 59
17. Удобные сокращения .......................................................................................................................... 62
18. Продвинутые циклы ............................................................................................................................ 65
19. Обработка ошибок .............................................................................................................................. 69
Бекэнд, фронтэнд и сервер ................................................................................................................... 77
IP адрес ........................................................................................................................................................ 79
103. Первый сервер на Express.js .................................................................................................... 81
104. Middleware и внешний доступ .................................................................................................... 83
105-107. JSON ............................................................................................................................................. 87
108. Промисы ............................................................................................................................................... 91
109.Цепочки промисов .............................................................................................................................. 96
110. Async/await ....................................................................................................................................... 99
201.Браузер и текстовый редактор .................................................................................................. 101
202. Введение в HTML ............................................................................................................................ 105
203. Строчные теги, вложенность и комментарии ...................................................................... 108
204. Ссылки и атрибуты ........................................................................................................................ 111
205. Списки ............................................................................................................................................... 115
206. Таблицы ............................................................................................................................................. 119
207. Структура HTML документа ......................................................................................................... 125
208. Основы CSS ....................................................................................................................................... 128
209. Внутренние стили .......................................................................................................................... 130
210. Внешние стили ................................................................................................................................ 134
211. Идентификаторы и классы ........................................................................................................... 137
212. Свойства текста ............................................................................................................................ 141
213. Свойства шрифта ............................................................................................................................ 147
214. Задний фон ....................................................................................................................................... 152
215. Границы ............................................................................................................................................. 156
216. Поля и отступы .............................................................................................................................. 161
217. Размеры и позиционирование .................................................................................................... 170
218. Ссылки и псевдо-классы....................................................................................................................... 174
219. Блочная модель и расположение HTML элементов на странице ................................... 182
220. Flexbox макет ................................................................................................................................ 188
221. Grid макет ....................................................................................................................................... 203
222. Селекторы и комбинаторы ........................................................................................................... 226
223. Семантический HTML...................................................................................................................... 237
224. Адаптивная верстка...................................................................................................................... 244
Библиотеки и фреймворки...................................................................................................................... 251
JSX и элементы ......................................................................................................................................... 256
RJS React компоненты ............................................................................................................................ 264
RJS Классовые компоненты и состояние ......................................................................................... 272
RJS Жизненный цикл React компонента ............................................................................................ 280
RJS Обработка событий .......................................................................................................................... 289
RJS Формы в React................................................................................................................................... 300
RJS Условный рендеринг ........................................................................................................................ 310
RJS Списки и фрагменты ........................................................................................................................ 322
RJS Дочерние компоненты и PropTypes ............................................................................................ 335
RJS Стили в React................................................................................................................................... 347
RJS React хуки ......................................................................................................................................... 355
350 RN: React Native и Expo ............................................................................................................. 371
351 RN: Flex View и стили ................................................................................................................. 375
352 RN: Изображения и Размеры ......................................................................................................... 381
353 RN: Поля ввода и кнопка ............................................................................................................. 388
354 RN: Списки и SafeArea ................................................................................................................. 400
355 RN: Введение и Стек ................................................................................................................... 412
356 RN: Хедер и навигатор вкладок .............................................................................................. 421
357 RN: Модальное окно и Alert, Drawer навигатор................................................................ 434
Сделай сам .................................................................................................................................................. 442
По одному биту за раз .......................................................................................................................... 443
Сортировка .................................................................................................................................................. 447
Оценка эффективности алгоритмов .................................................................................................... 450

Первый контакт
«Добро пожаловать в прекрасный мир будущего!»

Я лежал в каком-то помещении. Я спал? Где я? Надпись, сообщавшая про мир


будущего, алела надо мною, там, наверху. От выложенных из неизвестного
материала букв отвалились крошки и целые куски, обнажив под алым пористую
белую основу. Там были ещё лампы в заросших паутиной зеркальных
квадратах. Светили эти бородатые лампы мутно, потрескивали и мигали.
Глаза побаливали. Значит, там потолок. Кто догадался прилепить буквы на
потолок, для кого? Мысли мои обрывались, путались.

Я приподнял правую руку. Левую. От движений накатила слабость. Пальцы


коснулись бортиков. Я лежал в открытом длинном ящике, чьи стенки
показались мне холодными. Воздух в помещении был, в общем-то, тёплым.
Кто-то укрыл меня простынёй. Или саваном? Прекрасный мир? Я что, в гробу?
Я осторожно повернул голову. Туда, сюда. Стенки гроба были невысоки.
Увидел, впрочем, я немного. Облезлые пласты краски на стенах,
перевёрнутые столы. Самое время затеять капитальный ремонт. Шаг, ещё шаг.
Поступь мягкая, шуршащая. Человек? Зверь?
— Очнулся, — сказал незнакомый мужской голос. Сказал спокойно, деловито.
— Где я?

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

— Не переживай, скоро ты будешь говорить нормально. Ты в бывшем


медицинском центре. Полежи немного. Мы уже сделали тебе необходимые
инъекции. Контроль над телом вернётся к тебе через пару минут. Потерпи.

Теперь он стоял рядом. Врач? Автомеханик? Мужчина в комбинезоне.


Джинсовом, но необычном, с широченными простроченными полосами на синей
ткани. Будто кто-то сшил суперпрочную одежду на десятилетия, на всю
жизнь. Спереди на комбинезоне белел прямоугольник, тоже пришитый крепко-
накрепко. Там светилось синим цветом единственное слово: «Техник».
Нашивка на рукаве дублировала надпись.
— Что?.. Произошло?..

Я произнёс всего два слова. Однако на каждое потратил изрядное усилие.


Оба слова, растянувшиеся во времени, дались мне с трудом: челюсть
налилась тяжестью, точно у бронзовой статуи, а язык казался распухшим и
бесчувственным, будто туда вкололи ультракаин.

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


одновременно понимающей. Такой, словно он улыбался так уже многим, и я
был далеко не первым в его списке. Техник поднял палец в синей перчатке и
сказал:
— Спрашиваешь, что произошло? Много чего. Я бы на твоём месте не спешил с
вопросами. Понимаешь, ты вроде как потерял ориентир. Ты вообще как, в
порядке? Готов выслушать меня?
— Лучше знать, чем пребывать в неизвестности. Я уже могу двигаться?.
— Потихоньку, полегоньку.

Опираясь на руки, я уселся в саркофаге и только тут обнаружил, что под


простынёй я голый.
— Нервная система в порядке? — спросил Техник.
— Наверное. Не знаю.

Что я, кстати, знаю?


— Тогда слушай. Ты провёл в анабиозе больше трёхсот лет.
— Больше трёхсот чего?..

Говорил я вполне сносно. Облизал зубы: все целы. Поморгал. Подышал носом.
Приложил ладонь к сердцу. Тук-тук. Организм работает.

— Ты не волнуйся, Герой. Могло и похуже выйти. Тебе ведь сказочно


повезло. Другим — нет. Тебя нашли. Мы нашли, — уточнил Техник и опустил
синий палец. — До вечера тебя введут в курс дела. Это нужно не только
тебе, это нам всем нужно… Сигма, ты где там? Мы укладываемся в график?
Из-за спины Техника, точно он внезапно раздвоился, выскочила девушка. В
моё время такие красотки снимались в фильмах. Лёгкая и вместе с тем
спортивная фигурка, в которой гармонично сочетались сила, ловкость и
грация. Внимательно, неотступно глядящие глаза. Умные глаза. Она
повернулась. Длинные пшеничные волосы колыхнулись волною. Я не знал,
можно ли в неё влюбиться, сколько у неё поклонников и вправе ли
влюбляться в девчонок дремучий старикан вроде меня, проживший чёртову
уйму лет. Сколько мне стукнуло? Это ж надо: три с лишним сотни лет
продрыхнуть в заморозке!.. Кто поставил надо мной эксперимент?
— Всё идёт по плану. — Красотка сказала это не мне, а Технику. —
Хранители ждут нас на базе.
— Отлично. Я оставлю вас ненадолго.

Техник скрылся. Клянусь, красота Сигмы согревала меня. Мои щёки пылали,
точно две звезды. При ней я уже не чувствовал себя обитателем гроба
хладного.

— Держи.

Она подала мне свёрток, похожий на почтовый пакет. Я стыдливо поправил


свою простынку. Пакет был мягким на ощупь. То ли пластик, то ли ткань, не
разберёшь. Вместо адреса и имени на пакет прилепили эмблему-аббревиатуру:
JS. Это что, мои инициалы? Джон Смит?

Тут в мозгу, как по команде, проявилось имя. Моё собственное. Я его сразу
узнал. Вовсе не Джон. И не Смит.

— Герой, как слышно?

Я осмотрелся, но никого не было рядом. Привычный интерьер, бубнящие


соседи. 2330? Похоже приснилось.

— Cоберись! У нас нет лишнего времени! - произнес голос в моей голове.

Кажется, я схожу с ума.


— Техник, оставь Героя в покое. Через минуту он сам все вспомнит.
— Вы можете читать мои мысли?

— Обсудим это позже. Сейчас нам нужно определиться с твоей


профпригодностью.
— Ок.

— Осмотрись вокруг, и найди компьютер.

Особого труда это не составило и через минуту я уже сидел перед


светящимся экраном.
Поздравляем, Ты успешно вошел в систему как dayfireacad@gmail.com

— Отлично, Герой! Осталось определиться с типом твоей операционной


системы и можно перейти к делу.
Coderslang Extension в Visual Studio Code успешно настроен
Я снова оказался в обшарпанном зале медицинского центра. Передо мной
шептались Техник и Сигма.
— Техник, я же говорила тебе. Это ОН! Еще никому не удавалось так быстро
пройти первую проверку.
— Да... Интересный экспонат…. Похоже они меня не замечали.
— О, Герой, ты уже тут! - хором вскрикнули мои новые друзья и
рассмеялись.
— У нас для тебя отличные новости! Ты готов к тому чтобы вернуться в
прошлое, стать профессиональным JS программистом и спасти мир!
— Выдвигаемся на Базу!

Мир будущего оказался больше похоже на заповедник, чем на


постапокалипсис. Я шел и думал о том, как быть дальше. Я не сказал об
этом Технику и Сигме, но ведь я уже пробовал научиться программировать в
"прошлой жизни".

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


JavaScript до Full Stack разработки состоит из пяти уровней:
o Новичок — введение в программирование. Фундамент. Основы алгоритмического
мышления и синтаксис JavaScript.

o Ученик - бэкэнд. Node.js, npm, Linux, Docker.

o Студент — фронтэнд, веб-приложения. HTML/CSS, React.js.

o Интерн — мобильная разработка. React Native, Git, AWS.

o Junior - подготовка к собеседованию. Алгоритмы и структуры данных.

Когда мы прибыли на базу мне представили моих учителей:


o Сигма (Sigma) (ж) - основы JS, графические интерфейсы, мобильная
разработка
o Профессор (Professor) (м) - устройство компьютеров и сети Интернет

o Фей (Fei) (ж) - подготовка к собеседованию, работа в команде, HR

o Робот С2H5 (-) - алгоритмы и структуры данных

o Техник (Technic) (м) - бекэнд разработка, работа в команде

Алгоритмическое мышление и псевдокод

— С прибытием! Мы давно тебя искали, — сказал седой мужчина невысокого


роста. На нем были квадратные очки, красный шарф и зеленые штаны.
Несмотря на почтенный возраст, взгляд его был сосредоточен и чист.

Меня зовут Профессор. Я программирую, сколько себя помню. С 2267 года,


если быть точным. Тогда мне было 11 лет и родители подарили мне первый
компьютер. Прошло уже больше 50 лет. За это время я научил
программировать тысячи человек и точно знаю рецепты преодолениях все
трудностей с которыми ты столкнешься.

Тебе не нужно быть гением, чтобы стать программистом. Удивительно, но в


2022 большинство людей думало, что для этого обязательно нужен диплом или
хотя бы "математический склад ума". Это конечно, полезно. Но правда в
том, что у большинства моих учеников нет ученой степени и они не
выигрывали математические олимпиады. И все научились программировать.

Глобальные перспективы

Я не знаю кем ты будешь, когда вернешься. Я не знаю, куда ты попадешь. Я


не знаю как далеко от тебя будет находится штаб квартира ККНН. Но я знаю
точно, что в 2022 — программирование — это уникальная возможность
внедрится в любую компанию мира находясь за тысячи километров от ее
офиса.
Нет, тебе не нужно будет взламывать их серверы. Ты не будешь заниматься
спамом. Просто спрос на разработчиков так велик, что их готовы нанимать
по всему миру.

Все что тебе нужно — отвечать требованиям отдела кадров (HR) и ты


получишь работу.

Высокие зарплаты и свободный график

Глобализация — это шанс. Тебе не обязательно жить в Нью-Йорке, чтобы


получать местную зарплату. Ты не должен тратить несколько часов на дорогу
в офис и домой. Ты можешь жить на берегу чистой реки, дышать свежим
воздухом и работать в том ритме, который тебе подходит.

В 2022, самый большой плюс к твоим навыкам программирования — английский


язык. Для того, чтобы работать программистом в иностранной компании
достаточно читать, писать и говорить на уровне Intermediate. Если к концу
обучения в Coderslang ты сможешь свободно читать техническую
документацию, если сможешь внятно объяснить свой код, то выбор вакансий
увеличится для тебя в 2-3 раза.

Средняя зарплата программистов в разных странах (в месяц):

Страна Junior Middle Senior

Россия $500 $2500 $4500

Украина $700 $3000 $5200

Беларусь $550 $2600 $4800

Германия €3500 €5500 €7800

США $5800 $7400 $9700


Даже если твоя первая работа будет рядом с домом, важно учится тому, что
будет актуально десятилетия вперед. Кто знает, как дальше жизнь сложится?

Твоя уникальность, в том, что ты родился еще до того, когда был создан
полноценный искусственный интеллект. Симуляция 2022 для тебя ничем не
будет отличаться от жизни до погружения в камеру анабиоза.

Начни с постановки целей. Их обязательно должно быть несколько и все они


должны быть достижимыми. Нельзя просто прийти на собеседование в ККНН
прочитав "Программирование для чайников" и надеяться, что тебя возьмут
"на перспективу". Мы разделим твою подготовку на небольшие лекции и
множество практических задач. Если возникнут проблемы — поможем.
1. Регулярно занимайся. Выдели 30 минут в день на учебу. Это немного, но это
минимум, который ты должен обязательно выполнять. В Coderslang за эти пол
часа ты сможешь прочитать одну лекцию и решить практическую задачу.

2. Исключи все раздражители. Если ты любил читать новости и пользовался


социальными сетями в 2022, то у тебя будет соблазн продолжать и после
возвращения в симуляцию. Не стоит. Как бы нам ни хотелось тебя научить,
но последнее слово за тобой. На что потратишь свое время — то и получишь.
3. Отслеживай прогресс. Мир вокруг нас существует ровно настолько, насколько
мы его замечаем. Начни записывать. Каждый день пиши несколько предложений
о том, как он прошел. Что нового ты выучил. Какие вопросы у тебя
возникли? Несколько предложений это не "Война и Мир", не нужно быть
великим писателем, чтобы осилить их. Если не знаешь "куда" писать, то
можешь начать тему-колодец на форуме Coderslang. А если знаешь
английский, то отличное сообщество начинающих программистов — DEV.
4. Практика, практика и еще раз практика. Книги могут научить "что стоит
делать". Практика научит "как делать". Мы подготовили для тебя сотни
практических задач. Начиная с вывода на экран компьютера сообщения Hello,
world!, ты дойдешь за несколько месяцев до самостоятельного создания
бекэндов, веб приложений и даже мобильной игры для iOS и Android.

5. Твоя цель — найти работу. Быстрее всего, ты будешь прогрессировать в


первые два года после того, как устроишься на работу программистом. Твой
путь к ядру ККНН может занять долгое время и наша задача номер один, как
можно быстрее сделать из тебя Junior разработчика.

Алгоритмическое мышление и псевдокод


Есть всего 10 типов людей. Те, кто понимают двоичную систему и все
остальные.
— Автор Неизвестен

— Привет, Герой! Сегодняшнюю лекцию для тебя проведет C2H5. Он — робот и


наш союзник в борьбе с машинами.

— Спасибо за представление, Профессор. Мышление машин принципиально мало


отличается от человеческого, но у него есть свои особенности.

— Если эта лекция покажется тебе слишком сложной или скучной, ты можешь
ее пропустить и вернуться позже. Проблем с этим нет. Ты же хочешь стать
программистом?
Пропустить лекцию

Двоичная система счисления

Так как тебе придется вернуться в симуляцию 2022, то будем рассматривать


все в терминах того времени. Мы считаем, что полноценный искусственный
интеллект тогда еще не был создан и ты еще можешь все остановить.

Для начала, представь 10 лампочек. Каждая из них может быть включена или
выключена.

Внимание, вопрос! До скольки ты мог бы посчитать используя эти 10


лампочек?
— Ээээммм, до 10?
— До 10 мы однозначно можем посчитать, если будем каждый раз считать
общее количество включенных лампочек. Но, как думаешь, мы могли бы
досчитать до 1000 используя те же 10 лампочек?
— Нет, думаю, что это невозможно.

— Ну что ж, попробую все таки показать тебе, что для машин нет ничего
невозможного.

Попробуй представить, что каждая лампочка не просто включена или


выключена, а отображает одну цифру от 0 до 9.

Тогда, мы бы могли получить все комбинации от 0 000 000 000 до 9 999 999
999. Или 10 миллиардов разных чисел, включая 0, конечно.
— Все равно не понятно, как посчитать до 1000 с помощью 10 обычных
лампочек.

— Сейчас разберемся детально. Давай представим на нашем "ламповом


калькуляторе" какое-нибудь не слишком длинное число, например 2375. Нули
отбросим для простоты подсчета. Чтобы понять, что это число "две тысячи
триста семьдесят пять", ты делаешь несколько операций:
5 * 1 + 7 * 10 + 3 * 100 + 2 * 1000 = 5 + 70 +300 + 2000 = 2375

У каждой цифры или, другими словами, у каждого разряда, есть свой вес. Он
считается по формуле:
БАЗА в степени ПОЗИЦИЯ.

БАЗА — это основа системы счисления. В привычных людям числах — это 10. В
более естественной для машин, двоичной системе - 2.

Отсчет позиций мы начинаем справа и начальная позиция всегда 0, поэтому


вес первого разряда - 1, т.к. возведение любой БАЗЫ в нулевую степень
равно 1.
Следующие разряды в десятичной системе, имеет вес 10, 100, 1 000, 10 000
и т.д.

Конечно, ты не считаешь это каждый раз, когда видишь десятичное число. Но


понимание этой формулы нужно нам для продвижения вперед.
— Это достаточно очевидно, но ведь в первоначальной задаче не было цифр,
а были только 2 состояния — вкл/выкл?

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


включенных лампочек, а еще и учитывать их позицию.

Представь такую последовательность лампочек - 0 0 1 0 1 0 1 1 0 1

Начнем с того, что присвоим каждой позиции свой вес.

Вес позиции 0 = 2 ^ 0 = 1

Вес позиции 1 = 2 ^ 1= 2

Вес позиции 2 = 2 ^ 2 = 4

...

Вес позиции 9 = 2 ^ 9 = 512

Теперь, чтобы посчитать итоговое число, нам осталось только умножить вес
каждой позиции на ее значение (0 или 1).

Получится 1 * 1 + 0 * 2 + 1 * 4 + 1 * 8 + 0 * 16 + 1 * 32 + 0 * 64 + 1 *
128 + 0 * 256 + 0 * 512 = 1 + 4 + 8 + 32 + 128 = 173

— Теперь понятно. Получается если мы включим все лампочки, то получится 1


+ 2 + 4 + 8 + 16 + 32 + 64 + 128 + 256 + 512 = 1023?
— Все верно. В каком-то смысле так даже проще считать, потому что мы или
учитываем вес определенного разряда, или отбрасываем его.

Абстракция

— Если ты посмотришь внимательно, то, на самом деле, и десятичные числа и


двоичные существуют только в нашем воображении.
— Но как же, я вполне могу представить себе 12 стульев возле обеденного
стола или 40 человек стоящих в очереди. В том смысле, что они могут
существовать не только у меня в голове, но и в реальности.

— Конечно, и люди, и стулья будут существовать. Но чтобы связать число 12


в десятичной системе или 10100 в двоичной с четко определенным
количеством стульев — нужно всем договорится о том, как их считать.

Если стульев было бы 3, а не 12, то проще всего было бы показать их, как
3 пальца на одной руке или 3 включенных лампочки. Это самый простой и
очевидный способ что-то посчитать. Но что делать если их 12, 40, 125?
Пальцев уже не хватит, а где хранить лампочки тоже не очень понятно.

Поэтому, люди придумали системы счисления. И десятичная система далеко не


всегда была так распространена. Римляне бы подписали те же стулья как XII
и, скорее всего не поняли бы что означает надпись 12.

Все это — абстракция. Мы усложнили подсчет количества предметов, но


оперировать большими числами стало намного проще.
— Мне кажется, что числа встречаются в нашей жизни настолько часто, что
уже никто и не воспринимает это как усложнение.

— Все верно. Первокласснику непросто посчитать сколько денег ему нужно,


чтобы купить два килограмма яблок. Но у взрослых этой проблемы нет,
потому что когда делаешь что-то очень часто, оно становится естественным.
Обрати внимание, что если бы, в какой-то момент вместо цифр 0 1 2 ... 9,
люди решили использовать буквы a b c ... k, то твои 12 стульев
превратились бы в bc стульев, но на сами стулья это бы никак не повлияло.

Алгоритмы и псевдокод

— В 2022, машины еще не были полностью автономны и, в основном, служили


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

Единственная сложность была в том, что компьютеру нельзя было просто


сказать "сделай что-то хорошее, полезное и важное". Нужно было написать
программу, которая делала бы это "что-то".

Хорошая новость — после того, как эта программа написана, ей могли


пользоваться совершенно разные люди.

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

Целью лучших программистов тех времен — было создание полноценного


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

Чем все закончилось — ты уже знаешь. В какой-то момент даже сами


создатели уже не могли предсказать как поведут себя машины. Они
оправдывали это всеобщим благом и рассказывали как была ужасна жизнь
людей до изобретения ИИ.
Но в итоге — люди стали для машин тем, чем когда-то были муравьи для
людей. Они повсюду, но нет никакого смысла их истреблять. Как и
испытывать какие-либо эмоции по поводу того, что они растащат несколько
крупинок сахара упавшие с кухонного стола.
— И ты думаешь, что мне действительно по силам разобраться в том с чем не
справились лучшие умы 2022 ?

— Иначе мы бы не тратили время на твою подготовку, Герой. Твоё


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

Но, что-то мы отвлеклись. Давай попробуем разобраться, как найти человека


в телефонном справочнике. Пусть его зовут Васильев Петр.

Способ 1 - Линейный поиск


открываем справочник на первой странице
просматриваем все записи сверху вниз
если найдено совпадение (Васильев Петр)
звоним по найденному номеру
Иначе
переходим к следующей записи
повторить пока не закончатся страницы
— C2H5, ты конечно извини, но так никто не ищет. Даже моя бабушка
догадается сначала посмотреть оглавление, найти на какой странице
находится буква "В" и только потом начнет поиск по твоему алгоритму.

— Ты прав. При наличии оглавления туда точно стоит взглянуть. Этот метод
называется индексация и он очень часто используется в базах данных.

Но, при всей неоптимальности линейного поиска, он точно поможет нам найти
телефон Петра Васильева или убедиться в том, что в справочнике его нет.

Как бы ты оптимизировал алгоритм линейного поиска, если бы у тебя не было


индексов (оглавления), но ты знал что все записи упорядочены от А до Я?
— Наверное попробовал бы угадать на какой странице находится буква В.
Открыл бы справочник ближе к началу, но не на самой первой странице. Если
бы не угадал, то попробовал бы еще раз в зависимости от того, на какую
страницу попал бы.

— Отличный подход, т.к. ты предполагаешь, что все в справочнике примерно


одинаковое количество фамилий на каждую букву алфавита.

Но, так как этого не было в условии, и мы можем оказаться в мире где 90%
людей с фамилией на букву А, то я бы предложил открыть справочник ровно
посередине. Оценить точность попадания и пойти направо или налево,
отбросив половину записей.

Так как я робот, попробую формализовать этот алгоритм для тебя "почти"
человеческим языком.

Способ 2 - Двоичный поиск


открываем справочник посередине
смотрим куда попали
если найдено совпадение (Васильев Петр)
звоним по найденному номеру
Иначе
если фамилии на букву "В" находятся раньше
отбрасываем правую часть справочника и переходим на шаг 1
если фамилии на букву "В" находятся позже
отбрасываем левую часть справочника и переходим на шаг 1
повторить пока не закончатся страницы

Как и в случае с линейным поиском, мы или найдем необходимую запись, или


убедимся в том, что ее нет. ВАЖНО! Двоичный поиск работает только в
отсортированных структурах данных, например телефонных справочниках или
словарях.
Он работает намного быстрее, чем линейный поиск. И чем больше элементов в
структуре данных, тем сильнее становится преимущество!

Если представить, что одна итерация (повторение) алгоритма поиска


занимает 1 секунду и в нашей телефонной книге 100 записей, то линейный
поиск займет 100 секунд в худшем случае или 50 в среднем, а двоичный
поиск — меньше 7!
— Очевидно, что сокращение количества данных для поиска в 2 раза после
каждого повторения очень полезно.

— Но самое интересное продолжится, если мы попробуем увеличить размер


телефонной книги до миллиона записей.

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


поиск справится всего за 20 секунд!
— Ого.

— К сожалению, если мы работаем с неупорядоченными данными, то линейный


поиск — наш единственный вариант.
— А если мы отсортируем данные перед поиском?

— Сортировка — дорогой и долгий процесс. Она будет иметь смысл только в


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

Кстати, способ описания алгоритмов, который я показал — называется


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

У псевдокода нет жестких правил, машины с ним не работают. Он просто


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

— Это мы исправим. Давай пройдем тест, чтобы оценить, как ты усвоил новую
информацию.
Если у тебя возникнут проблемы с тестом, ты можешь перейти к следующей
лекции из меню "". Открываются лекции кликом на иконку с "замком".
Перейти к тесту

Первая программа
— Привет, Герой! Меня зовут Фей, - сказала девушка с темными волосами.
Перед твоим погружением в Симуляцию, я вкратце расскажу тебе о том как
работали программисты в 2022.
— Вы совсем не похожи на программиста, пробормотал я.

— Ты прав. Два года назад меня достали из такой же камеры анабиоза, как и
тебя. В 2022 я была лучшим HR специалистом в отделе кадров "компании,
которую нельзя называть". На основной работе я обеспечивала комфортные
условия работы команды, занималась поиском и наймом новых технических
специалистов.
— Интересно.

— Сейчас, я понимаю что было не так и плохо, — улыбнулась Фей. Но в тот


момент на меня накатила жуткая депрессия, я ушла в подполье на несколько
лет и в итоге оказалась в "холодильнике".

Впрочем, хватит о грустном. Давай лучше я расскажу о том, что ждет тебя в
2022.

В те времена, люди искренне верили, что у них в запасе есть еще десятки
лет до того как будет создан первый AGI - искусственный интеллект общего
назначения. Большинство компьютерных программ представляли собой обычный
текст.
— Текст?
— Да, именно текст. Компьютеры еще не умели программировать сами себя, а
для людей ничего привычнее текста не было. К тому же, текст намного проще
структурировать чем речь. Для написания программ нужно было следовать
определенным правилам, иначе возникали синтаксические ошибки т.к.
компьютер делал вид не понимал что от него хочет человек.

Но с правилами мы разберемся позже, а пока просто запомни, что


компьютерная программа — это вполне текст написанный по правилам, которые
понятны компьютеру.
— Если программа — это текст, то это значит, что я могу писать его где
угодно?

— Совершенно верно, хоть на заборе :). Но чтобы компьютер мог выполнить


программу, она должна к нему как-то попасть. Поэтому удобнее всего писать
программы на компьютере, а не на заборах, стенах или клочках бумаги. Для
того, чтобы писать программы было проще, появились IDE (интерактивные
среды разработки). Если упростить, то я это текстовые редакторы с
расширенной функциональностью. Большинство из их функций на начальном
этапе тебе не понадобятся, но проверка синтаксиса и автоматическое
форматирование кода — незаменимы.

Итак, программы ты можешь писать в любом текстовом редакторе, но мы


рекомендуем IDE. В 2022 лучшими считались Visual Studio Code, Atom
(бесплатные) и WebStorm (платная).
Если ты еще не решил, что выбрать, то я рекомендую Visual Studio Code. У
него отличная функциональность, красивый интерфейс и с ним просто начать
работу.

В комнату вошли Техник и Профессор. Вид у них был озабоченный.

— Похоже Машинам стал известен наш план. Больше откладывать нельзя,


возвращаем тебя в 2022 и будем превращать в супер-программиста.

— Путь легким не будет, но ты — наша единственная надежда, - сказал


Техник подключая датчики к моему телу.

Я снова оказался в своей комнате, казалось, что с момента


выполнения coderslang init прошло всего несколько минут.
— Как самочувствие, Герой? - раздался в голове знакомый голос Сигмы.
— Самочувствие хорошее, настрой боевой!
— Отлично, тогда продолжим обучение
Еще с 1970х годов, первой программой, которую пишут все программисты
стала программа Hello, world!. Все что эта программа делает — выводит на
экран строку "Hello, World!" без кавычек.
Твоя миссия — стать крутым JavaScript программистом, внедриться в
"компанию, которую нельзя называть" и предотвратить порабощение планеты
машинами. Без Hello, world! тут никак не обойтись.

Итак, все компьютерные программы состоят из команд. Простейшая команда


состоит из одной строки:
console.log('Эта программа выводит на экран текст и состоит из одной
команды');

Чтобы людям было проще читать программы написанные другими людьми —


каждую команду пишут с новой строки, например так:
console.log('Эта программа сложнее');
console.log('Она состоит из трех команд');
console.log('И тоже выводит на экран текст');
Команда console.log выводит текст на экран. Чтобы она правильно работала,
нужно придерживаться двух правил:

1. Текст нужно оборачивать в одинарные, двойные или косые кавычки:


console.log(этот текст на экран выведен не будет...) - неправильно
console.log('а с этим все хорошо') - правильно
console.log("и с этим") - правильно
2. Текст в кавычках должен быть обернут в круглые скобки:
console.log 'нет скобок :(' - неправильно
console.log('а тут все хорошо') - все отлично
В конце строки рекомендую ставить ;, хотя в 2022 это не обязательно.

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


намного удобнее это делать в IDE, например VSCode.

Если ты еще не настроил VSCode для решения практических задач, сделай это
сейчас.

"Даже путь в тысячу ли начинается с первого шага." - Лао-цзы


Инструкции
ВАЖНО: Если на каком-то шаге у тебя возникли проблемы и не получается его
выполнить, напиши нам в Телеграм чат или на почту и мы тебе поможем.

А теперь перейдем к следующему заданию. Чтобы получить его, нажми еще раз
на кнопку Получить задачи. Задача появится в папке task70.

Во второй задаче тебе нужно исправить код, который программа выводит на


экран.
Так как ты только начинаешь, то эта программа будет состоять из одной
команды - console.log. С ее помощью выведи на экран строку I will become
a JS developer in 2022!.

Вторая задача не обязательна для выполнения. Ты можешь пропустить ее и


перейти к следующей лекции.

8. Переменные и константы

— Привет, Герой! - передо мной стояли Техник, Сигма, Фей, Профессор и


C2H5.
— Эммм... Привет... Как вы тут оказались?

— Не переживай, это всего лишь сон. С2H5 провел несколько миллиардов


симуляций и доказал, что это оптимальный способ коммуникации. Внешнее
вмешательство в твоё сознание во время бодрствования с вероятностью 98%
приведет к потере рассудка. - сказал Профессор
— Да уж, и что теперь делать?

— Делать все то же, учиться! - продолжила Сигма. Во сне способности к


обучению выше и твой мозг намного лучше воспринимает новую информацию.
— Тогда я готов. Чем займемся дальше?

Передо мной остался только Профессор, а все остальные растворились в


воздухе.

— Будем разбираться с тем как заставить компьютер делать что-то полезное.


Глобально, все программирование состоит из двух важных концепций. Это —
Алгоритмы и Структуры Данных.

Начнем с данных. Чтобы информация лучше воспринималась, мы не будем


зацикливаться на терминологии, а перейдем к сути. Сегодня разберем 3
простых типа данных JavaScript:
1. Число (Number)

2. Строка (String)

3. Логический тип (Boolean)


С числами в JS никаких сюрпризов.1, 2, 1000, 0.99, 15.55 - числа. Ты это
проходил в начальной школе и тут все стабильно.
Строка — любая последовательность текстовых символов, например 'Hello,
World!' или 'JavaScript rules!'.
Если ты обернешь число в кавычки — это уже будет не число, а строка.
Например: 10 - это число, а '10' - строка.
Логический тип может быть не так очевиден для тебя, если ты только
знакомишься с программированием. Его придумали специально, чтобы описать
ситуации в которых возможно всего 2 значения — правда и ложь.
Соответственно и множество допустимых значений для логического типа
ограничено значениями true и false.

Создание переменных и хранение данных

Чтобы данными можно было пользоваться, их нужно где-то хранить. Для этого
придумали понятие переменная.
Создать переменную можно с помощью ключевого слова let, а сохранить в нее
значение можно используя оператор =.

Например:
let greeting = 'Hello, world!';
let salary = 100000;
let isWinning = true;
Сразу после ключевого слова let нужно указать имя переменной.
Как следует из названия — переменная может менять свое значение. Чтобы
сменить значение переменной, нам нужен все тот же оператор
присваивания =.

let greeting = 'Hello, world!';


greeting = 'Hi';
console.log(greeting);

В этом примере кода выше происходит несколько действий:


1. В первой строке мы создаем переменную greeting и сразу присваиваем ей
значение Hello, world.
2. Во второй строке мы меняем значение сохраненное в greeting на Hi.
3. В третьей строке мы выводим значение переменной greeting на экран. И что
это будет?

— Hi?
— Точно! Начальное значение переменной greeting мы заменили на Hi и
именно его вывели на экран.
— Чем-то похоже на подписывание контейнера или коробки на складе.

— С одной стороны — да. Переменная — это коробка, в нее можно положить


что-то полезное. А имя — это надпись на коробке, по которой ее можно
легко найти. Но правильнее будет сказать, что значение — это одна
коробка, а переменная — другая.

Переменная хранит в себе не само значение, а только адрес коробки со


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

let salary = 100;


let oldSalary = salary;

// обе переменные указывают на одно и то же значение


console.log(salary);
console.log(oldSalary);

salary = 200000;

// значение salary стало 200000, а oldSalary осталось 100!


console.log(salary);
console.log(oldSalary);

Тут, мы:
1. Создали переменную salary и связали ее (сохранили адрес) со значением
100.
2. Создали переменную oldSalary и связали ее с тем же значением 100.
3. Вывели на экран значения переменных salary и oldSalary, чтобы убедиться,
что они равны.
4. Связали переменную salary с новым значением - 200000.
5. Снова вывели на экран значения переменных salary и oldSalary, чтобы
убедиться, что изменилась только salary!
— Действительно, если бы хранила значение, а не адрес значения, то так бы
не получилось сделать. Иначе oldSalary изменилась бы вместе с salary.

— Мне начинает казаться, что еще немного и ты сможешь занять мое место,
Герой.
— А может в переменной ничего не храниться? Коробка ведь вполне может
быть пустой.
— Конечно! В JavaScript "пустые коробки" содержат
значение null или undefined. Между ними есть некоторые отличия, но тебе о
них пока не стоит переживать.

Есть 2 способа получить пустую коробку переменную:


1. Создать переменную, но не присваивать ей значение - let salary;
2. Явно установить значение переменной равным undefined или null с помощью
оператора присваивания - salary = undefined.
К слову, оба способа достаточно редко используются на практике и чаще
одно значение заменяется другим, без промежуточного "освобождения".

Форматирование имен переменных и camelCase

Не забывай, что greeting, Greeting и GREETING - 3 разные переменные.


Думаю, ты уже догадался почему.
— Да, похоже, что имеют значение большие и маленькие буквы.
— Верно, большая буква A и маленькая буква a - разные символы и для
компьютера отличие между ними примерно такое же как между
числами 65 и 97.
— Почему именно эти числа, а не 1 и 2, например?
— Об этом нам еще рано говорить, но если очень хочешь, то можешь на
досуге исследовать тему ASCII Character Set.
Если ты хочешь назвать переменную несколькими словами, то можно сделать
это несколькими способами. В JavaScript чаще всего
используется camelCase.

Правила простые:
1. Пишем все слова слитно.

2. Первое слово начинаем с маленькой буквы.

3. Все остальные - с большой.


Например, если ты хочешь создать переменную и сохранить туда домашний
адрес (home address), то в camelCase - это будет homeAddress. Ограничения
по количеству слов нет, вполне допустимы
переменные userNotificationPreference, dailyMorningRoutine, hasUpdatedSub
scriptionSettings.
— Выглядит немного странно, но вроде понятно.
— JavaScript, сам по себе, не запрещает тебе создавать
переменные home_address или HomE_AddresS, просто в большинстве проектов
программисты используют camelCase. Лучше быть с коллегами на одной волне,
чем стараться стать пятым колесом.
Еще одно негласное правило касается именования булевых (логических)
переменных. Старайся имена таких переменных начинать со слов is или has.
Например, если ты хочешь отметить какой-то визуальный элемент как
"кликабельный", то это можно сохранить в переменной isClickable, вместо
просто clickable.
— Довольно удобно, можно сразу понять, что мы имеем дело с переменной
логического типа, а не строкой или числом.

— Некоторые переменные никогда не изменяются после того, как они были


инициализированы. Количество часов в сутках, дней в неделе, времена года
и многие другие значения — остаются постоянными, чтобы бы ни происходило
в программе.
С одной стороны можно сделать использовать ключевое слово let и объявить
их как переменные. Но, тогда мы не будем защищены от случайного изменения
этих значений в коде программы.
— Разве это проблема? Просто я буду помнить о том, что некоторые
переменные трогать не нужно и не буду изменять их значение.

— Помнить ты, конечно, будешь. Но:


1. Рано или поздно ты забудешь о том какие переменные можно менять, а какие
— нет.

2. Другие программисты не умеют читать твои мысли.


— И что же делать?
— Использовать ключевое слово const вместо let.
const numberOfSeasons = 4;
Если кто-то в программе попробует изменить значение
константы numberOfSeasons - возникнет ошибка.
— Полезно, так мы сразу поймем, где проблема и не нужно будет разбираться
куда пропали весна и лето.

— Именно так! Прежде чем перейти к практическим задачам, давай проверим


как ты усвоил теоретический материал с помощью теста.

9. Экспорт и программы из нескольких файлов


— Привет, Герой! С предыдущей лекцией и практическими задачами ты
справился отлично, - начала Сигма.
Сегодня я расскажу тебе о команде export и о том, как создавать программы
состоящие из нескольких файлов.
— А разве есть ограничение на длину одного файла в JavaScript?

— Ограничения может и нет, но чем больше становится программа, тем важнее


становится структура. Большинство проектов создаются командами
программистов. Если бы вся разработка происходила в одном файле - их
жизнь стала бы адом.
В прошлой лекции, мы рассматривали константы. Очень часто возникают
ситуации, когда константу нужно использовать в нескольких местах. Тогда
создают отдельный файл, например constants.js, инициализируют там
константы и экспортируют их для использования в других файлах проекта.
//constants.js
export const NET_PROFIT_MARGIN = 10;
export const REGULAR_SALES_TAX = 25;

//solution.js
import { NET_PROFIT_MARGIN, REGULAR_SALES_TAX } from
'./constants.js';

console.log('Net profit margin is ' + NET_PROFIT_MARGIN);


console.log('Regular sales tax is ' + REGULAR_SALES_TAX);
В этом примере мы объявили и экспортировали две константы из
файла constants.js, а потом импортировали и использовали их в
файле solution.js.

☝️ Чтобы экспортировать константу, нужно добавить ключевое


слово export перед const.
☝️ Чтобы импортировать константу, нужно воспользоваться
конструкцией import { CONSTANT_NAME } from 'source_file_path', где
CONSTANT_NAME - имя константы, а source_file_path - путь к файлу в
котором она объявлена.
Обрати внимание, на то, что путь может быть абсолютным или относительным.
Абсолютный путь начинается с корня файловой системы / в Unix-системах и
редко используется для импорта констант в javascript.
Относительный путь, как правило, начинается с . или .. и включает в себя
только часть абсолютного пути.

. - значит, что поиск файла нужно начать с текущей директории


.. - поиск файла начнется на одну директорию выше текущей

В нашем примере, "увидя" путь к файлу './constants.js' - компьютер


попробует найти файл constants.js в той же директории в которой находится
файл solution.js. Если же мы представим, что файл constants.js находится
в директории env, то путь станет ./env/constants.js.
Если же предположить, что файл solution.js находится в папке src, которая
находится на одном уровне с папкой env, то нам нужно будет "выйти" на
один уровень выше прежде чем начать поиск файла и путь изменится
на ../env/constants.js.
— Сигма, уровни, это, конечно, круто, но почему ты не
используешь camelCase?
— Рада, что ты обратил внимание на это. Экспортируемые константы очень
часто используют верхний регистр с нижними подчеркиваниями (UPPERCASE
with underscores), чтобы дополнительно подчеркнуть то, что они являются
глобальными для всего проекта.

Правило простое: пиши все слова большими буквами разделяй их символом


нижнего подчеркивания (underscore).
Если ты пишешь игру и хочешь создать константу, которая хранит
"вероятность дождя", то получится что-то вроде CHANCE_OF_RAIN.
— Вроде понятно, но нужно будет попробовать на практике. Особенно
относительные пути.

— О, конечно! Практические задачи откроем тебе сразу после теста.


Если у тебя есть вопросы по задачам или что-то не получается, ты можешь
задать вопросы в нашем телеграм чате или перейти к следующей лекции.
Пересдать тест

10. Функции

— Мы уже разобрались, что программы состоят из команд, которые


выполняются по порядку. Сверху вниз. Таким образом мы можем предоставить
компьютеру набор инструкций (команд). Он их выполнит и мы получим
ожидаемый результат. Например, найдем телефонный номер в "справочнике" из
миллиона записей.

Но давай представим, что у нас не один справочник, а 5. Тогда, нам нужно


было бы повторить всю последовательность команд для каждого справочника.
Программа бы стала в 5 раз длиннее.
— Ну, что поделаешь, найти то нужно...

— Конечно, нужно найти, но справочников может быть и больше. Что будем


делать, если их будет 100? А если 1000?
— Похоже, ты хочешь рассказать мне о каком-то очень удобном способе
избежать повторений?
— Все так. Чтобы не дублировать код, есть несколько приемов. Первый из
них, называется - функция.

Идея простая. Мы выделяем часть программы и даем ей имя. Вспомним


алгоритм линейного поиска из предыдущей лекции:
открываем справочник на первой странице
просматриваем все записи сверху вниз
если найдено совпадение (Васильев Петр)
звоним по найденному номеру
Иначе
переходим к следующей записи
повторить пока не закончатся страницы
Мы можем дать ему имя, например linearSearch. Теперь, когда нам нужно
выполнить поиск телефона в справочнике, мы уже не будем повторять все
команды. Будет достаточно написать linearSearch(phoneBook, name).
— Вроде понятно, а что такое phoneBook и name?

— Это — аргументы функции. Или, другими словами, значения, которые мы


передаем в функцию. Ведь чтобы код (набор команд) можно было использовать
снова и снова, он должен быть универсальным, то есть уметь работать с
разными данными. В нашем случае, мы даем алгоритму поиска справочник и
имя для поиска.
— А как это будет выглядеть в JavaScript?

— Для реализации поиска в JavaScript ты еще не дорос, но давай попробуем


написать простую функцию, которая принимает в качестве параметров имя и
фамилию пользователя и приветствует его.
Если бы пользователя звали Peter Peterson, то команда приветствия
выглядела бы как
console.log('Hello, Peter Peterson!');

Чтобы сохранить этот код в функцию, нам нужно создать переменную (или
константу) :
const sayHello;
Теперь осталось "положить" или "связать" функцию, приветствующую
пользователя, с константой sayHello. Для этого, мы обернем все команды
внутри функции в конструкцию () => {…}. Вместо трех точек мы напишем наши
команды, поставим знак "равно" и функция будет готова!
const sayHello = () => { console.log('Hello, Peter Peterson!'); };
— Так, но мы же планировали приветствовать пользователя с любым именем.
Будет странно, если к нам пришел John Johnson, а мы говорим ему Hello,
Peter Peterson!.

— Это верно, но давай по порядку.

Параметры и аргументы

— Очень удобно иметь возможность инкапсулировать какие-то куски кода в


функцию. Это как самолет, который летает по маршруту Нью-Йорк — Лондон. В
процессе перелета может быть задействовано множество различных людей и
техники. Но нас это не очень беспокоит. Мы просто знаем, что если сядем
на этот рейс в Нью-Йорке, то через несколько часов окажемся в Лондоне.

Но настоящая сила появляется у функций, которые могут менять свое


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

В таком случае вам не нужно содержать 100 разных самолетов во всех


крупных городах мира. Достаточно будет и одного. Ведь, по сути, вас
интересует просто перелет из точки А в точку Б и было бы удобнее всего
сообщать о своем желании непосредственно перед вылетом.

Дадим два ключевых определения. У функций бывают параметры и аргументы.


o Параметры - это переменные, которые мы указываем при определении функции.
o Аргументы - это значения, которые мы передаем в функцию при ее вызове.
После того, как мы вызвали функцию, параметры инициализируются
значениями аргументов.
В целом, не будет ничего ужасного, если ты вдруг перепутаешь эти два
термина. Просто важно понимать, что внутрь функции можно передать какие-
то данные (аргументы). И, внутри функции, можно реализовать какой-то
алгоритм, даже не имея данных, а опираясь только на параметры.
— Можно пример?

— Конечно. Смотри:
const addAndPrint = (x, y) => {
console.log(x + y);
}
Мы создали функцию addAndPrint, которая складывает два числа и выводит
результат на экран.
Переменные x и y - это параметры. У них нет значения в момент объявления
функции, но оно появится, когда функция будет вызвана.
Обрати внимание, что тут мы не используем ключевые слова let и const, а
просто пишем параметры в круглых скобках через запятую. Их количество
неограниченно.

Использование функций и возвращаемое значение

— Кроме того, что в функцию можно передать аргументы, из функции также


можно вернуть значение.
Сделать это можно с помощью ключевого слова return. Важно, что
возвращаемое значение у функции может быть только одно, в отличие от
параметров, которых может быть много.

К примеру, мы могли бы реализовать функцию которая просто складывает два


числа.
const add = (x, y) => {
return x + y;
}

Или другую функцию, которая добавляет восклицательный знак к любому


переданному значению.
const addExclamationMark = (s) => {
return s + '!';
}
— Ясно. Чем-то похоже на формулу в которую нужно подставить данные.
— Если функция возвращает какое-то значение, то бывает удобно сохранить
его в переменную. Используем функцию add из предыдущего примера:
const sum = add(5, 3); // sum = 8

Также в качестве аргументов можно использовать не только "обычные" числа,


но и другие переменные, константы или даже результат выполнения других
функций.
Чему будет равно значение result?
const a = 1;
const b = 2;
const c = 5;
const d = 10;

const result = sum(sum(a, b), sum(c, d));


— Ох… Если я все правильно понимаю, то мы можем упростить выражение
до sum(3, 15). Тогда, ответ - 18!
— Правильно! Возвращаясь к примеру с именем и фамилией в объявление
функции нам нужно также добавить параметры name и surname. Также немного
изменим вывод на экран.
const sayHello = (name, surname) => {
console.log('Hello, ' + name + ' ' + surname + '!');
};

Итак, чтобы создать функцию нам нужны:


1. Круглые скобки, в которых мы можем указать параметры функции (name,
surname).
2. Стрелочка => чтобы разделить параметры функции и список команд (тело)
функции.
3. Фигурные скобки {}, внутри которых мы разместим тело функцию.
4. Ключевое слово return, после которого будет указано возвращаемое
значение.

Ты можешь рассматривать функцию, как подпрограмму. После того, как мы ее


вызовем, инструкции составляющие тело функции будут выполнены по порядку,
сверху вниз.
— А как вызвать функцию и что за 'Hello, ' + name + ' ' + surname + '!',
которые ты написал внутри console.log?

— Чтобы вызвать функцию, нужно написать ее имя, добавить две круглые


скобки и список аргументов.
o console.log('Hello, world!') - вызываем функцию console.log с параметром
(аргументом) 'Hello, world!'
o add(2, 2) - вызываем функцию add с параметрами 2 и 2
o sayHello() - вызываем функцию sayHello без параметров

Кроме того, объявлена функция может быть в одном файле, а использована в


другом. Точно так же, как и любая другая константа.
//functions.js
export const sayHello = (name, surname) => {
console.log('Hello, ' + name + ' ' + surname + '!');
};

//solution.js
import { sayHello } from './functions.js'

sayHello('Jack', 'Jackson'); // Hello, Jack Jackson!


Для того, чтобы вывести приветствие пользователю, мы используем
конкатенацию (сложение) строк. Она выполняется с помощью оператора +,
который "приклеивает" вторую строку к концу первой.
Обрати внимание, что если мы напишем просто console.log('Hello, name
surname!');, то это и будет выведено на экран. Компьютер не может
"догадаться", что вы хотели заменить name и surname на значения
переданные в функцию и просто напечатает Hello, name surname!.
— А что будет, если мы не передадим ни одного аргумента в функцию и
просто напишем sayHello()? Программа сломается?
— Нет, она не сломается. Внутри функции sayHello значения
переменных name и surname будут не определены и равны undefined.
Для console.log и оператора + - это не проблема. Выражение 'Hello, ' +
name + ' ' + surname + '!' станет 'Hello, undefined undefined!' и это
сообщение будет выведено на экран.

11. Объекты и Массивы


— Кроме примитивных значений, таких как строки, числа и логические
переменные, существуют и более сложные структуры данных. Сегодня,
поговорим об объектах и массивах.
— А зачем они нужны?
— Нужны они для того, чтобы программы было удобно разрабатывать и легко
поддерживать. С помощью объектов мы можем объединить различные данные под
одним именем. Например, мы можем создать объект user, который будет
хранить информацию о пользователе.
const user = {
name: 'Jack',
age: 25
}
Тут мы создаем объект с двумя полями - name и age. Если в будущем нам
нужно будет обратиться к ним, то мы можем использовать
запись user.name и user.age. Добавив точку после имени объекта, мы
получим доступ к его внутренним полям.
— А я могу создать объект без полей?

— Конечно. Ты можешь создать пустой объект и сохранить в него данные


позднее.
const user = {};
user.name = 'Jack';
user.age = 25;

В итоге у тебя получится ровно то же, что и в первом примере.


— Понятно.

— Поля объектов также иногда называются свойствами. Свойством может быть


не только примитив, но и объект.
const user = {
name: 'Jack',
age: 25,
address: {
country: 'US',
state: 'NY',
postcode: '10005',
street: '11 Wall St'
}
}
Чтобы получить доступ к полям вложенного объекта, нужно использовать все
ту же запись с точкой. Например, console.log(user.address.state) выведет
на экран строку NY.
— А что будет, если я попробую обратиться к полю, которого нет?
— Хороший вопрос, Герой. В этом случае значение поля будет считаться не
определенным и равно undefined. Важно заметить, что undefined в данном
случае, это не строка, а специальное значение.

Для того, чтобы изменить поле, достаточно использовать все тот же


оператор присваивания.
const user = {
name: 'Jack',
};

user.name = 'John';
Тут мы переименовали пользователя и теперь его зовут не Jack, а John.
— Мне кажется, или мы только что изменили объект, который был объявлен
как константа?
— Верно, но мы не изменили значение user - он по-прежнему ссылается на
тот же объект. Изменилось только его внутреннее состояние, а это
разрешено.

Объекты позволяют нам хранить данные с произвольными ключами (имя,


возраст, адрес и т.п.) и описывать сущности из "реального мира".

Но, очень часто, нам хочется хранить однотипные данные в упорядоченном


формате. Тогда мы смогли бы обращаться к ним, как "первый", "второй",
"третий" и так .
— Объекты для этого не очень подходят, да?
— Да. Но тут на помощь нам приходят массивы. Массив — это структура
данных, состоящая из однотипных элементов, например строк, чисел или
объектов.

Если для создания объекта мы использовали фигурные скобки, то тут нам


помогут квадратные.
const animals = ['Cow', 'Horse', 'Dog', 'Cat'];

console.log(animals[0], animals[3]); // Cow Cat

В результате на экран будут выведены первый и последний элементы массива.


Обрати внимание, что нумерация элементов массива начинается с нуля, а
запись animals[1] даст нам доступ ко второму элементу массива.
— А я могу заменить одну из строк в массиве на объект?
— Можешь заменить и на объект, и на число и даже вручную установить его
равным undefined. JavaScript тебе никак это не запрещает. Но, это не
очень хорошая идея.
Если ты хочешь улучшить массив animal таким образом, чтобы кроме типа
животного у них могло бы быть еще и имя, лучше сделай каждый элемент
объектом. И можешь не переживать, что некоторые из животных окажутся
безымянными.
const animals = [{
type: 'Dog',
name: 'Rex',
}, {
type: 'Horse',
}];
Так мы создадим массив с собакой по имени Rex и лошадью без имени. И,
почти всегда, это будет НАМНОГО лучше, чем такой вариант:
const animals = [{
type: 'Dog',
name: 'Rex',
}, 'Horse'];
— А как добавить новый элемент в массив?
— Для добавления нового элемента существует функция push. Она встроена в
любой JS массив и добавляет элемент в конец массива.
const animals = [{
type: 'Dog',
name: 'Rex',
}, {
type: 'Horse',
}];

console.log(animals.length);

animals.push({type: 'Cat', name: 'Murka'});

console.log(animals.length);
— Дай угадаю, мы выводим на экран длину массива перед добавлением нового
элемента и после, чтобы было очевидно ее изменение?
— Именно так. Свойство length есть в любом массиве и оно всегда равно
количеству элементов массива.

Теперь проверим твои знания с помощью теста и нескольких практических


задач.

12.Принимаем решение
— В программировании, как и в жизни, очень часто возникает необходимость
изменить поведение программы в зависимости от какого-то условия.
Несколько примеров:
Если пойдет дождь, то используй зонтик. Если будет солнечно и ты пойдешь
на пляж, то используй солнцезащитный крем. Если на часах больше 22
часов, то активируй темную тему приложения.
Тут мы видим ключевые слова выделенные полужирным шрифтом. Условие,
выделенное курсивом. И, полезное действие, выделенное подчеркиванием.

В общем случае это будет выглядеть как:


if (/*условие*/) {
//сделаем что-то полезное, если условие истинно
}

И чуть ближе к нашим примерам, мы бы могли достать зонтик примерно так:


if (isRaining) {
useUmbrella();
}
— А разве тут не будет ошибки из-за того, что переменная isRaining и
функция useUmbrella() не определены?
— Отлично, что ты это заметил, Герой. Но, сейчас, мы рассматриваем
выдернутый из контекста кусок кода, который позволит тебе разобраться со
структурой условной конструкции if. В практических задачах ты сможешь
написать полностью рабочий код от А до Я.
— Хорошо.
— Так вот, в JavaScript условная конструкция состоит из: ключевого
слова if, условия (заключенного в круглые скобки) и полезного действия,
которое обернуто в фигурные скобки.
1. Полезное действие будет выполнено только если условие истинно.

2. Полезным действием может быть любой блок кода и состоять он может из


любого количества строк.
— А что делать, если я хочу выполнить какой-то блок кода тогда когда
условие ложно?
— Тут есть два варианта. Во-первых, можно инвертировать условие с помощью
"восклицательного знака" - !.
if (!isRaining) {
useUmbrella();
}

В таком случае мы достанем зонтик когда НЕ будет идти дождь.


Или же, если нам нужно сделать два полезных действия в зависимости от
одного условия, мы можем добавить ключевое слово else.
if (isRaining) { // условие
useUmbrella(); // выполняем, если условие истинно
} else {
hideUmbrella();// выполняем, если условие ложно
}
— Вроде несложно.
— Для того, чтобы скомбинировать несколько условий, тебе пригодятся
операторы && (логическое "И") и || (логическое "ИЛИ").

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


использовать его и в ситуациях, когда слишком сильно светит солнце.
if (isRaining || isHeavySunshine) { // если идет дождь ИЛИ сильно
светит солнце
useUmbrella(); // достаем зонтик
} else {
hideUmbrella();// прячем зонтик
}
— Если честно, то я бы еще проверил что зонтик у нас вообще есть.

— Отличная идея! Но реализацию ты напишешь сам в рамках одной из


практических задач.
Сейчас очень важно запомнить 2 правила по которым работают логическое И и
логическое ИЛИ.
1. Если, хотя бы одно из условий ложно, то результатом логического "И"
- && - всегда будет ложь.
true && true; // true
true && false; // false
false && true; // false
false && false; // false
2. Если хотя бы одно из условий истинно, то результатом логического "ИЛИ"
- || - всегда будет истина.
true || true; // true
true || false; // true
false || true; // true
false || false; // false
Чуть было не забыл. Чтобы сравнить числа, ты можешь использовать
стандартные операторы < (меньше), и > (больше). Но для проверки равенства
используй оператор ===.
Одинарное = - это оператор присваивания, который мы уже разбирали раньше,
а двойное == имеет некоторые особенности, которые мы разберем в следующем
уровне.

13. Циклы while и for


— Одна из самых важных задач программиста — писать чистый и понятный код.
Код, который легко изменить и сложно сломать. К сожалению, чем сложнее
задача, которую решает программист, тем сложнее становится и код.
— Легко усложнять, а упрощать — сложно?

— Именно так. Но, компьютеры очень хороши в многократном повторении одних


и тех же однотипных действий.

Представь что у тебя в магазине есть 3 товара и их нужно вывести на экран


для того, чтобы пользователь мог сделать выбор.

В простейшем случае, можно было бы сохранить их в массиве и обратиться к


каждому из товаров по индексу (порядковому номеру товара).
const stock = [
{title: 'fork', price: 10},
{title: 'spoon', price: 15},
{title: 'knife', price: 20},
];

const printStock = (stock) => {


console.log(stock[0]);
console.log(stock[1]);
console.log(stock[2]);
}
Но, в долгосрочной перспективе, такой подход окажется провальным. Когда
количество товаров увеличится или уменьшится, то изменить нужно будет не
только массив stock, а и функцию printStock.
К тому же, в функции printStock есть дублирование кода, которого нужно
избегать всеми силами, чтобы программа оставалась простой, а код было
легко улучшать.
Чтобы выполнять однотипные действия много раз, существуют циклы.
Начнем с простейшего цикла while. Его синтаксис чем-то похож на условную
конструкцию if, но в случае с циклом блок кода в фигурных скобках будет
выполняться до тех пор, пока истинно условие в круглых скобках.
while (condition) {
// делаем что-то полезное
// ...
}

Например, можно попробовать вывести на экран цифры от 0 до 9:


let i = 0; // создаем переменную i с начальным значением 0

while (i < 10) { // пока значение i меньше 10, выполняем код в


фигурных скобках
console.log(i); // печатаем на экран текущее значение i
i++; // увеличиваем i на 1, или иначе можно было написать i = i + 1
}
— Ух ты, получается что если задача изменится и нужен будет вывод не до
9, а до 99, то мне нужно будет добавить всего лишь один символ в условие
цикла!
— Также цикл в JavaScript можно создать с помощью ключевого слова for. Он
немного отличается в форме записи, но суть остается той же — выполняем
блок кода до тех пор, пока условие истинно.
for (let i = 0; i < 10; i++) {
console.log(i);
}
Как видишь, нам удалось уменьшить количество кода в 2 раза за счет того,
что объявление переменной i и ее увеличение i++ находятся в одной строке
с условием.
Но, помни, что в цикле for первым всегда идет объявление переменной (let
i = 0), за ним — условие (i < 10), а в конце — увеличение (i++). И все
это должно быть разделено знаком ;.
— А что будет, если я попробую вместо увеличения переменной i, сделать ее
меньше на 1 за каждый проход цикла?

— Тогда у тебя получится бесконечный цикл, так как условие выхода никогда
не будет достигнуто. Вывод на экран не прекратится до тех пор, пока не
закончится память выделенная твоей программе.

Хотя, вполне можно изменить начальное значение переменной и условие.


Смотри:
for (let i = 9; i >= 0; i--) {
console.log(i);
}
Мы начнем итерацию (проход по циклу) с 9, и будем работать до тех пор,
пока i больше или равно 0. А i-- значит, что на каждой итерации мы будем
уменьшать значение счетчика i на единицу.
— Понятно.
— Давай вернемся к нашему примеру с товарами в магазине и улучшим
функцию printStock.
const printStock = (stock) => {
for (let i = 0; i < stock.length; i++) {
console.log(stock[i]);
}
}
Обрати внимание, что мы начинаем с первого товара (с индексом 0) и
работаем до тех пор, пока не обработаем их все - i < stock.length, все
так же увеличивая счетчик i на единицу за каждую итерацию (проход) цикла.
Может показаться, что функция printStock стала даже сложнее в сравнении с
тремя console.log написанными подряд. Но, теперь, нам не нужно вносить
изменения в printStock, если изменится количество товаров.

14. Строки
— Привет, Герой. Я заменю Сигму на несколько ближайших лекций, - сказал
Техник.
— Что-то случилось?
— Пока я не могу тебе об этом рассказать. Постарайся сфокусироваться на
новом материале. Все вопросы сможешь задать в конце лекции.
— Как скажешь, — постарался я ответить как можно спокойнее.
— Сегодня мы будем подробно разбираться со строками.

Что такое строка в JS

Строкой в JavaScript считается любая последовательность символов,


заключенная в одинарные, двойные или обратные (косые) кавычки.
const doubleQuotedString = "This is a double quoted string";
const singleQuotedString = 'This is a single quoted string';
const backtickQuotedString = `This is a string wrapped in
backticks`;

Между одинарными и двойными кавычками разницы нет, а у обратных есть две


особенности.

Шаблонные строки

Шаблонные строки — это первая полезная особенность строк обернутых в


обратные кавычки. Помнишь задачу с приветствием пользователя и
подстановкой в строку его имени? Используя шаблонные строки, решение
выглядело бы так:
const sayHello = (name) => {
return `Hello, ${name}!`;
}
Если в строке с обратными кавычками встретится конструкция ${}, то
выражение внутри фигурных скобок будет вычислено и подставлено в итоговую
строку. Тут может быть не только переменная, а и любой другой валидный JS
код. Арифметические операции, вызов других функций, все подходит.
Использование шаблонных строк удобнее и красивее чем конкатенация строк с
помощью +, особенно, если тебе нужно скомпоновать строку с множеством
параметров.

Строки состоящие из нескольких "строк"

Также, с помощью обратных кавычек, ты можешь создать строку, которая


отобразится на нескольких "строках" на экране. Во втором случае, я беру
слово "строки" в кавычки, чтобы была очевидна разница между типом
данных String (строка) и строкой на экране. В английском языке
ориентироваться немного проще, т.к. экранная строка обычно
называется line, а не string.
const multiLineString = `This string consists
Of
Multiple
lines.

You can try to print the "multiLineString" to the screen with


console.log
to make sure it's true.
`;

Еще раз повторю, что все текстовые данные в JavaScript считаются


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

Сходство строк и массивов

По своей сути, строка — это массив символов. У нее также, как и у массива
есть свойство length. В нем хранится длина строки. Так же, как и у
массива, доступ к элементам можно получить по индексу, с помощью
квадратных скобок [] после имени.
const welcomeMessage = 'Hi!';
const goodbyeMessage = 'Bye!';

console.log(welcomeMessage.length); // 3
console.log(goodbyeMessage.length); // 4
console.log(welcomeMessage[0]); // H
console.log(goodbyeMessage[2]); // e

Но между строками и массивами есть одна огромная разница.

Строки в JS неизменяемы.

В массив ты можешь добавить новый элемент или заменить существующий с


помощью операции присваивания. Если попробовать сделать, то же самое со
строкой, то у нас ничего не получится.
const arr = [1, 2, 3, 4];
arr.push(5);
arr[2] = 0;
console.log(arr); // [1, 2, 0, 4, 5]

const s = '1234';
s[2] = '0';
console.log(s) // 1234
Неизменяемость строк — очень важное их качество. Дальше мы рассмотрим
некоторые функции, которые "кажется" изменяют строку,
например replace или slice, но на самом деле, они просто создают новую
строку, а старая остается без изменений.

Полезные встроенные функции строк

Все JS строки относятся к типу String и хранят в себе несколько полезных


функций. Они все встроены в тип String и могут быть вызваны на всех
объектах этого типа.

Функция Описание

Поиск подстроки. Возвращает true, если совпадение


includes(substring)
найдено, иначе - false.

Определяет заканчивается ли строка на substring.


endsWith(substring)
Возвращает true или false.

Возвращает индекс начала вхождения substring в


indexOf(substring)
строку или -1, если оно не найдено.

Заменяет все вхождения s1 на s2 в строке и


replace(s1, s2)
возвращает результат

Разделяет строку на массив подстрок


используя separator, который также является
split(separator) строкой. Пример: В результате выполнения 'hello
world !'.split(' ') получится ['hello', 'world',
'!']

Это только некоторые функции, которые помогут нам взаимодействовать со


строками. Полный список и описание ты можешь найти в нашей базе знаний
на learn.coderslang.com.

Специальные символы

Кроме привычных букв и цифр в строках также могут встречаться специальные


символы. Пожалуй, чаще всего используется символ \n - перевод строки.
Если он встретится в строке, то часть, которая будет напечатана после \n,
отобразится на новой строке.
const twoLineString = 'This string consists of \n two lines';
const multilineString = 'This string \n has \n even more \n lines,
\n as there are more \n newline chars';
Если мы выведем в консоль строки twoLineString и multilineString, то
первая займет две строки, а вторая — пять.

Кроме того, в строку можно добавить символы, которых нет на обычной


клавиатуре, например смайлики.
const stringWithAHappyFace = 'I like to smile \u263A !' // I like to
smile ☺ !

const checkmarkString = '\u2714 DONE'; // ✔ DONE

const failedTask = '\u2B55 FAIL'; // ❌ FAIL


Специальные символы встречаются в нескольких форматах. Выше, ты
видел UTF-16. Он состоит из четырех цифр в шестнадцатеричной системе
счисления (0, 1, … F), перед которыми добавлен \u.
В какой-то момент свободных значений в UTF-16 стало слишком мало и ввели
новый формат UTF-32. Общая формула похожа, начинается со \u, но
дальнейшая последовательность шестнадцатеричных цифр берется в фигурные
скобки и в итоге получается что-то вроде \u{1f44d} или 👍, после вывода
на экран.
Учить эти коды бессмысленно. Просто знай, что они существуют. Если они
будут тебе нужны ты сможешь их легко найти. Вот сайт со списком всех
специальных символов, которые ты можешь добавить в строку.

Экранирование
Вывести на экран некоторые символы не так просто, как кажется. Например,
представь, что тебе нужно напечатать строку 'Ваши данные находятся в
каталоге \new'. Если просто передать эту строку в console.log, мы получим
вывод на экран состоящий из двух строк:
Ваши данные находятся в каталоге
Ew
Произойдет это потому, что последовательность \n интерпретируется, как
перевод строки. Чтобы решить эту проблему, нужно экранировать символ \.
Сделать это можно с помощью того же символа \ размещенного перед
символом, который мы хотим экранировать.
Так, наша строка изменится на 'Ваши данные находятся в каталоге \\new'.
Еще одним примером может быть символ кавычки или апостроф. Представь
строку - 'I'm a Full Stack JS Engineer, I can use symbols like "", ``, ''
in any string'. Опять-таки, нельзя просто сохранить ее в переменную и
ожидать что все будет хорошо. Нужно "сообщить компьютеру" о том, что
некоторые символы в строке должны рассматриваться не так как обычно.
const incorrectString = 'I'm a Full Stack JS Engineer, I can use
symbols like "", ``, '' in any string';
const fixedString = 'I\'m a Full Stack JS Engineer, I can use
symbols like "", ``, \'\' in any string'
В первом случае, компьютер "думает", что строка заканчивается после
второй одинарной кавычки, а дальше идет непонятный набор символов.
Добавив \ перед символом ’, мы его экранировали, то есть "дали понять"
компьютеру, что этот символ — часть строки и должен быть выведен на экран
именно в таком виде.

Конвертация строки в число

В одной из первых лекций ты узнал, что '123' и 123 - это разные вещи.
Первое — строка, второе — число. Они выглядят почти одинаково, но
относятся к разным типам.
Часто возникают ситуации, когда нам нужно преобразовать строку в число
или наоборот. Для того, чтобы это сделать, нам понадобятся
функции String(value) и Number(value).
const unformattedPrice = '1100 USD';
const normalPrice = 20;
И вот мы оказались в ситуации, когда нам нужно выделить числовую часть из
строки unformattedPrice и сложить ее со значением normalPrice. Если мы
попробуем сделать просто unformattedPrice + normalPrice, то в результате
получится строка 1100 USD20. Число будет преобразовано в строку, и две
строки будут склеены вместе.
Первый шаг, который нам нужно сделать — выделить числовую часть из
строки unformattedPrice. Воспользуемся функцией replace и получим:
const unformattedPrice = '1100 USD';
const normalPrice = 20;
const formattedPrice = unformattedPrice.replace(' USD', '');

console.log(formattedPrice); // 1100
console.log(formattedPrice + normalPrice); // 110020
Помним, что строки не изменяются и функция replace вернет нам новую
строку, которую мы сохраним в константу formattedPrice. Но это только пол
дела. formattedPrice - это по прежнему строка, и в результате у нас
получится 110020 вместо ожидаемых 1120.
const unformattedPrice = '1100 USD';
const normalPrice = 20;
const formattedPrice = unformattedPrice.replace(' USD', '');
const formattedConvertedPrice = Number(formattedPrice);

console.log(normalPrice + formattedConvertedPrice);
Вот теперь все на своих местах. Мы передали значение ’1100’ в
функцию Number, которая сделала для нас конвертацию строки в число.
Теперь в formattedConvertedPrice лежит число, которое мы можем сложить с
другим числом normalPrice и вывести результат на экран для проверки.

Код выше можно упростить до трех строк:


const unformattedPrice = '1100 USD';
const normalPrice = 20;

console.log(normalPrice + Number(unformattedPrice.replace(' USD',


'')));
Чтобы разобраться в других правилах преобразования типов и арифметических
операций в JS - прочитай эту статью. Заодно потренируешь английский язык.
15. Продвинутые функции
Ты уже умеешь делать некоторые части кода универсальными с помощью
сохранения их в функцию. Умеешь передавать в функцию аргументы и
использовать их внутри.

Сегодня расскажу тебе о некоторых продвинутых возможностях функций в JS.


Это сложная лекция, если ты что-то не поймешь — перечитай еще раз,
попробуй решить задачи и задай вопросы, если они будут.
Сигма должна была тебе рассказывать что функция похожа на математическую
формулу — подставляешь в нее необходимые значения и получаешь результат,
который функция вернет с помощью ключевого слова return.
const mult = (x, y) => {
return x * y;
}
Функция mult(x, y) принимает два числа в качестве параметров и возвращает
их произведение.

Очень удобно, что функции в JS можно сохранять в переменные. Но в JS у


функций есть еще одно очень крутое свойство. Функцию можно создать не
только во время написания программы, а и во время выполнения.

Это стало возможным потому, что функция необязательно должна возвращать


число, строку или объект.
Функция может вернуть другую функцию.
const multiplyBy = (x) => {
return (y) => {
return x * y;
}
}

const multiplyBy2 = multiplyBy(2);


const multiplyBy3 = multiplyBy(3);
console.log(multiplyBy2(10)); // 20
console.log(multiplyBy3(10)); // 30
В этом примере функция multiplyBy примет значение и вернет другую
функцию. Эта функция "запомнит" значение параметра x переданного в
функцию верхнего уровня multiplyBy и сможет использовать его внутри себя.
, с помощью функции multiplyBy мы создаем две другие
функции multiplyBy2 и multiplyBy3. Первая будет принимать любое число и
умножать его на 2, вторая — на 3. В этом можно убедиться с
помощью console.log в конце примера.

Выглядит достаточно бесполезно, но все становится намного интереснее,


когда мы подходим ко второму важному свойству функций JS.
Функция может принять другую функцию в качестве параметра.

Представь, что у тебя есть массив каких-то данных. И тебе нужно его
отфильтровать по какому-то правилу. Правило ты еще не знаешь, но было бы
очень удобно, если бы ты не мог менять правила фильтрации по ходу дела.

Возможно ты захочешь оставить только товары с ценой меньше 100$.


Возможно, ты бы хотел увидеть только то, что можно получить с доставкой
до 31 декабря.
В общем случае это выглядело бы как функция, которая принимает элемент
массива, анализирует его и возвращает true, если его нужно оставить и
иначе - false. Тогда мы бы могли применить эту функцию к каждому элементу
массива и понять какие из них нам подходят.
Так вот, в JS это уже реализовано как функция filter встроенная во все JS
массивы. Она принимает функцию, применяет ее к каждому элементу массива и
возвращает новый массив, состоящий из тех элементов для которых эта
функция вернула true.
const animals = ['Cow', 'Horse', 'Dog', 'Cat', 'Whale', 'Dolphin',
'Rabbit'];

const longerThan = (n) => {


return (s) => {
return s.length > n;
}
}

console.log(animals.filter(longerThan(3))); //['Horse', 'Whale',


'Dolphin', 'Rabbit']
console.log(animals.filter(longerThan(5))); //['Dolphin', 'Rabbit']
Функция longerThan(n) похожа на multiplyBy(x). Она вернет другую функцию,
которая "запомнит" n, примет строку s и вернет true, если длина
строки s больше n. Эту функцию мы можем передать в filter. Она уберет из
массива все значения, для которых получился результат false.

Область видимости
В процессе написания программы ты будешь создавать множество функций и
переменных. Не все они будут друг-другу доступны. Чтобы хорошо понять
ошибки, которые будут возникать в процессе разработки, тебе нужно
запомнить несколько правил.
JS код в базовом случае выполняется сверху вниз.
Это значит, что есть исключения и особые правила, например в случае с
циклом for мы переместимся "вверх" после выполнения последней инструкции
и продолжим, если условие истинно.
Нельзя использовать переменные или функции до того, как они были
объявлены.
console.log(message); //ReferenceError: Cannot access 'message'
before initialization
const message = 'hello';
Этот код работать не будет, т.к. в первой строке мы пытаемся получить
доступ к константе message, которая еще не существует. Если поменять
строки местами, то все станет хорошо и мы увидим строку hello на экране.
Нельзя использовать переменные или функции вне области видимости
Каждый блок кода обернутый в фигурные скобки {}, создает отдельную
область видимости, скрытую от внешнего воздействия.
const animals = ['Cow', 'Horse', 'Dog', 'Cat', 'Whale', 'Dolphin',
'Rabbit'];

for (let i = 0; i < animals.length; i++) {


let totalLength = 0;
totalLength += animals[i].length;
}

console.log(totalLength); //ReferenceError: totalLength is not


defined
Переменная totalLength объявлена внутри цикла for, которое является
отдельным блоком кода. Когда, в последней строке, мы пытаемся вывести ее
значение на экран, получаем ошибку. Она возникает из-за того, что
переменные объявленные во внутренних блоках кода, невидимы снаружи. Даже
несмотря на то, что они существовали внутри.
Но, заметь, что наоборот это правило не работает! Ведь мы без проблем
получаем доступ к массиву animals внутри цикла for.
Нельзя создать переменные с одинаковыми именами в одном блоке кода
const message = 'hello';
const message = 'hello world';
console.log(message); //SyntaxError: Identifier 'message' has
already been declared
Это правило достаточно логично, т.к. если бы его не было, то непонятно
было бы какой из message использовать при выводе на экран.
Если внутри блока кода и снаружи существуют переменные с одинаковыми
именами, то будет использоваться та переменная, которая "ниже".
const animals = [ 'Cow', 'Horse', 'Dog', 'Cat', 'Rabbit' ];

for (let i = 0; i < animals.length; i++) {


const animals = [ 'Whale', 'Dolphin' ];
console.log(animals[i]);
}

Это важный пример, давай разберем его подробно.


o Мы создаем массив animals с пятью строками 'Cow', 'Horse', 'Dog', 'Cat',
'Rabbit'.
o Запускаем цикл от нуля до animals.length, которое равно пяти.
o Внутри тела цикла, мы создаем новый массив animals с двумя
строками 'Whale', 'Dolphin'.
o Выводим на экран по порядку элементы массива animals с индексами
от 0 до 4. То есть, до тех пор, пока условие i < animals.length -
истинно.

Внимание, вопрос! Значения какого массива будут использованы при выводе


на экран?
Whale
Dolphin
undefined
undefined
undefined
Несмотря на то, что при определении количества итераций цикла мы
использовали "верхний" массив animals и его длину 5, он был
переопределен и использован в console.log. Ну а из-за того, что у
"нижнего" animals всего два элемента, то мы и видим undefined в качестве
последних трех строках.

Замыкания
Вернемся к нашему примеру с созданием функций.
const longerThan = (n) => {
return (s) => {
return s.length > n;
}
}
При каждом вызове функции longerThan(n) будет создана новая функция,
которая "запомнит" параметр n, который использовался при ее создании.
Можно создать сколько угодно функций с разными значениями n и для каждой
из них n будет своим. Это — замыкание.
Замыкание - это функция со всеми доступными ей внешними переменными.
Может показаться, что это просто или очевидно, но если бы мы сохранили n,
как обычную переменную, то у нас бы ничего не получилось.
Значение n каждый раз бы менялось и это изменение ломало бы логику
программы.

16. Таймауты и интервалы


Хорошо иметь простые и понятные правила выполнения кода. Сверху вниз,
слева направо. Все просто, все понятно.

Но, если текущих средств недостаточно для выполнения задачи, то нужно


вводить новые правила.
Отложенное выполнение и setTimeout

Бывает, что мы хотим что-то сделать, но не прямо сейчас, а через какое-то


время.
Например, у нас есть функция вызова такси callTaxi(destination). Она
принимает один параметр — точку назначения и мгновенно подает такси для
поездки по указанному адресу. Нам нужно ехать в аэропорт, но не прямо
сейчас, а через 5 часов.
Для того, чтобы вызов какой-то функции можно было отложить, в JS
существует функция setTimeout(callback, delay). Она принимает два
параметра:
o delay - задержку, в миллисекундах
o callback - функцию, которая будет вызвана после завершения задержки

Вернемся к нашему примеру с такси:


const callTaxi = (destination) => {
console.log(`Your taxi to the ${destination} has arrived!`)
}
setTimeout(() => {
callTaxi('airport')
}, 1000 * 60 * 60 * 5);
В последней строке мы устанавливаем таймаут на "через 5 часов", после
чего будет выполнена функция переданная в качестве первого параметра.
Задержку мы специально пишем формулой, чтобы ты понимал, как она
получается. Нет способа вызвать с setTimeout с чем-то кроме миллисекунд,
поэтому любое цифровое значение нужно преобразовывать в ms.
В setTimeout мы должны первым параметром всегда передавать функцию. Мы не
могли просто вызывать callTaxi('airport'), потому что такая запись
мгновенно бы выполнилась и наш план не сработал.
С другой стороны, если мы уверены, что callTaxi будет всегда
использоваться с setTimeout, мы можем ее переписать и сделать так, чтобы
она возвращала функцию.
const callTaxi = (destination) => () => {
console.log(`Your taxi to the ${destination} has arrived!`)
}

setTimeout(callTaxi('airport'), 1000 * 60 * 60 * 5);


Теперь у код стал чуть чище, callTaxi вернет функцию, которую и
вызовет setTimeout через 5 часов или 18 000 000 миллисекунд.

Периодическое выполнение и setInterval


Представь, что мы работаем на сверхсекретном объекте. Нам нужно проверять
его безопасность некоторых его частей с разными интервалами.
o Каждый час нужно проверять периметр

o Каждые 3 часа нужно проверять блокпосты

o Каждые сутки нужно проверять цифровые системы


Таймаут нам тут не подойдет, потому что нужно запускать проверки с
регулярной частотой, а не 1 раз. Но, кроме setTimeout, в JS есть еще одна
полезная функция - setInterval, она принимает те же два
параметра (callback, delay) и запускает функцию callback через промежутки
времени равные delay.
const checkSecurity = (target) => () => {
console.log(`Checking ${target}'s security...'`)
}

setInterval(checkSecurity('perimeter'), 1000 * 60 * 60);


setInterval(checkSecurity('checkpoint'), 1000 * 60 * 60 * 3);
setInterval(checkSecurity('digital system'), 1000 * 60 * 60 * 24);
Функция checkSecurity, вернет другую функцию, которую и сможет
выполнить setInterval.
, мы создаем три интервала. Каждый из них будет работать до тех пор, пока
программа в которой они были запущены не будет остановлена. Интервалы
запущены с разным параметром delay, поэтому каждый из них будет крутиться
с собственной периодичностью.
Чтобы проверить функциональность и не ждать часами, можешь запустить этот
код и убрать 60 * 60, таким образом проверка периметра будет происходить
раз в секунду, а не раз в час.

Удаление таймаута или интервала

Иногда, может быть полезно удалить установленный таймаут или интервал.


Например, мы установили будильник на "через 8 часов" и легли спать.
Проснулись через 7 часов и будильник нам больше не нужен.

Нам нужно каким-то образом запомнить установленный таймаут и попросить


компьютер остановить его.
К счастью, этот функционал уже реализован в JS.
Функция setTimeout возвращает нам id таймаута после его создания. А
глобальная функция clearTimeout(id), удаляет функцию переданную в таймаут
из очереди выполнения.
const alarm = () => {
console.log('Bzzz, Bzzz!! WAKE UP!')
}
const timeoutAlarm = setTimeout(alarm, 1000 * 60 * 60 * 8);

if (isAwake) {
clearTimeout(timeoutAlarm);
}
В случае, если isAwake будет равно true, таймаут будет очищен и будильник
уже не будет звонить.
То же работает и для setInterval, только уже нужно использовать
функцию clearInterval(id).
let i = 0;
const printWelcomeMessage = () => {
if (i > 9) {
clearInterval(printNumbersInterval);
}
console.log('Hey, we learn JS!');
i++;
}
const printNumbersInterval = setInterval(printWelcomeMessage, 1000);
Этот пример чуть сложнее, но суть остается той же. Мы
сохранили id интервала в printNumbersInterval и когда мы решили, что
интервальное выполнение функции printWelcomeMessage нам больше не нужно,
мы передали этот id в clearInterval.
Можешь попробовать запустить этот код и убедиться, что сообщение Hey, we
learn JS! выводится на экран только 10 раз, после чего программа
завершается.
Обрати внимание, что мы снова использовали замыкание, когда объявили
переменную i снаружи функции printWelcomeMessage. Если бы мы
перенесли let i = 0 внутрь функции, то были бы проблемы.

17. Удобные сокращения


В JavaScript существует множество способов сделать одно и то же действие.
Я расскажу тебе о некоторых нюансах, которые помогут тебе писать более
красивый код.

Функция из одной строки

Когда ты знакомился с функциями, мы тебе рассказывали, что нужно


размещать тело функции в фигурных скобках { ... }.

Но если твоя функция состоит из одной команды, например складывает два


числа и возвращает результат, то можно убрать фигурные скобки и сократить
создание функции до одной строки.
const sum = (x, y) => {
return x + y;
}

Превращается в
const sum = (x, y) => x + y;
После стрелки => ты вместо фигурных скобок можешь сразу написать
возвращаемое значение. Ключевое слово return в таком случае не
используется.

Давай другой пример, например мы принимали имя и возвращали приветствие


пользователю:
const getPersonalizedGreeting = (name) => `Hello, ${name}!`;

Или сразу выводим его на экран:


const printGreeting = (name) => console.log(`Hello, ${name}!`);

Возвращение объектов в одну строку

Но, у функций из одной строки, есть одна особенность. Чтобы вернуть


объект, его нужно обернуть его в круглые скобки.

Это — неправильно:
const getObjectIncorrect = (name) => { name: 'Jack', email:
'welcome@coderslang.com' };

Круглые скобки — обязательны!


const getObjectCorrect = (name) => ({ name: 'Jack', email:
'welcome@coderslang.com' });

Тернарный оператор

Когда тебе нужно проверить условие, не обязательно использовать if/else.


Проверку можно сократить до одной строки с помощью тернарного оператора.
В общем случае он имеет вид condition ? expression1 : expression2. Что
тут происходит:
o condition - это то же условие, которое ты указывал в круглых скобках
после if
o expression1 - выражение (команда), которая будет выполнена, если
выполняется condition
o expression2 - выражение (команда), которая будет выполнена, если НЕ
выполняется condition
o условие отделено от выражений знаком вопроса ?
o выражения отделены друг от друга двоеточием :

Объединив тернарный оператор и однострочные функции, мы можем сокращать


довольно большие куски кода.
Представь, что нам нужно реализовать функцию isOfAge(user), которая
проверит поле age параметра user и вернет true, если оно больше или
равно 21, иначе - false.

Простыми словами — мы проверяем является ли пользователь достаточно


взрослым.
const isOfAge = (user) => {
if (user.age >= 21) {
return true;
} else {
return false;
}
}

Эту же функцию можно записать намного короче:


const isOfAge = (user) => user.age >= 21 ? true : false;
Хотя есть еще один способ сократить функцию isOfAge, но над ним ты
подумаешь самостоятельно в VSCode.

18. Продвинутые циклы


Ты уже знаком с циклами for и while. Я расскажу тебе о двух способах
прервать цикл и одном способе пропустить итерацию.
Прерывание цикла с помощью return

Когда в функции встречается ключевое слово return - она прерывается и


вместо вызова возвращается какое-то значение.
o return 1 - вернет число 1
o return 'hello' - вернет строку hello
o пустой return - вернет undefined
Ты не обязан ставить return в конце функции. Ты вполне можешь проверять
какое-то условие в цикле и если оно выполнится сразу вернуть результат.

Это может быть полезно, если ты что-то ищешь.


const findValueInArray = (arr, n) => {
for (let i = 0; i < arr.length; i++) {
if (arr[i] === n) {
return i;
}
}
return -1;
}
Функция findValueInArray вернет позицию элемента n в массиве arr или -1,
если он не будет найден. Цикл прервется сразу же, как только мы найдем
совпадение и мы не будем анализировать оставшиеся элементы массива.
Прерывание цикла с помощью break

Бывает, что тебе не нужно прерывать выполнение функции, но нужно


остановить цикл. Тогда вместо return нужно написать break. Цикл
прервется, но функция продолжит выполнение.
export const askNameAndGreet = () => {
let name;
while (true) {
name = readlineSync.question('Please enter your name: ');
if (name && name.length > 2) {
break;
} else {
console.log('Your name should have at least 3 characters in it');
}
}
console.log(`Hello, ${name}!`);
}
Мы немного переписали функцию askNameAndGreet из начальных лекций. Вопрос
об имени пользователь будет получать до тех пор, пока не введет имя из
трех символов или больше. Как только это случится, условие (name &&
name.length > 2) внутри if станет равно true и выполнится команда break.
Цикл прервется и выполнится команда console.log, которая выведет
приветственное сообщение на экран.
Пропуск итерации с помощью continue

Если нам нужно пропустить только текущую итерацию цикла, но не прерывать


его полностью, то нам нужна команда continue. Например, у нас есть цикл,
который печатает на экран числа от 0 до 9.
for (let i = 0; i < 10; i++) {
console.log(i);
}
Но нам хочется немного его улучшить и не выполнять
команду console.log для числа 5.
for (let i = 0; i < 10; i++) {
if (i === 5) {
continue;
}
console.log(i);
}
После того, как условие внутри if станет истинным, выполнится continue и
мы сразу перейдем на следующую итерацию (виток)
цикла. console.log выведет на экран все значения i кроме 5.
Перебор элементов массива с помощью for...of

Если мы используем цикл для анализа элементов массива и нам не важен их


индекс (позиция в массиве), то мы можем использовать цикл for...of.

Вот как будет выглядеть функция, которая печатает на экран значения всех
элементов массива.
const printArrayItems = (arr) => {
for (const item of arr) {
console.log(item);
}
}
Как видишь, у нас уже нет счетчика цикла. Цикл будет крутить до тех пор,
пока мы не переберем все элементы массива arr.
В for...of вместо массива можно также использовать строку. В таком случае
мы пройдемся по всем ее символам.
Перебор ключей объекта с помощью for...in

Если нам нужно перебрать значения всех ключей (полей) объекта, мы можем
использовать цикл for...in. Этот цикл похож на for...of, но вместо
элементов массива, у нас будут ключи объекта.
const printObjectKeys = (obj) => {
for (const key in obj) {
console.log(key);
}
}
Если мы передадим в такую функцию объект user = { name: 'Jack', email:
'welcome@coderslang.com' }, то на экран будут выведены
строки name и email.
Кстати, доступ к полям объекта можно получить не только с помощью
точки ., а и с помощью квадратных скобок []. Смотри:
const user = { name: 'Jack', email: 'welcome@coderslang.com' };

console.log(user['name']); // Jack
console.log(user.name); // Jack
В обоих случаях на экран выведется значение поля name объекта user.

Квадратные скобки для доступа к полям объекта могут быть полезны тогда,
когда мы не знаем какие именно поля есть в объекте.

Например, можно написать универсальную функцию, которая будет выводить на


экран названия всех полей объекта и их значения.
const printObjectKeysAndValues = (obj) => {
for (const key in obj) {
console.log(`${key}: ${obj[key]}`);
}
}
Если мы передадим в функцию printObjectKeysAndValues нашего Джека, то
увидим на экране:
name: Jack
email: welcome@coderslang.com

Но можем использовать и какой-то другой объект. Например, собаку Джека.


const pet = { type: 'Dog', name: 'Rex', age: 5};
printObjectKeysAndValues(pet);

И увидим такой вывод:


type: Dog
name: Rex
age: 5
Несмотря на то, что у объекта pet больше полей и они называются иначе, мы
смогли их все вывести на экран с помощью той же
функции printObjectKeysAndValues. Если мы для доступа к полям мы
использовали точку - ., а не квадратные скобки - [], у нас бы ничего не
получилось, потому что мы не знаем наперед какие поля будут в объекте,
который попадет в функцию.

19. Обработка ошибок


В этой лекции, ты научишься обрабатывать ошибки в JavaScript и узнаешь,
почему ошибка — это далеко не худший вариант.
Если что-то может пойти не так, оно пойдёт не так.
— Закон Мерфи

Зачем нужны ошибки

Я расскажу тебе историю. В ней нет ничего общего ни с программированием,


ни с JavaScript. Но эта история поможет тебе понять в чем польза ошибок.
И особенно — почему ошибки не стоит скрывать, а о проблемах стоит
сообщать как можно раньше.

Жила-была девочка Маша. Ей вот-вот должно было исполнится 5 лет. Родители


очень хотели сделать Маше сюрприз и заказали большой розовый торт. Маша
мечтала о том, как она задует 5 свечек и загадает свое самое заветное
желание.
Мама заказала торт в лучшей кондитерской города. Она сделала заказ
заранее. Кондитер сообщил, что все будет отлично и торт принесет фея в
самый разгар праздника.

Но, время идет, а торта все нет. Родители пытаются дозвониться, чтобы
уточнить срок доставки, а ответа нет.

Тут раздается стук в дверь. Маша бежит открывать. Она видит чудесную фею
с розовыми крылышками. В руках у нее огромная коробка.

У всех чудесное настроение, все радуются и с нетерпением ждут кульминации


праздника. Открывают коробку, а там пусто.

Маша плачет, родители в шоке, фея ничего не понимает. Ничего уже не


исправить. Конец.

Что произошло

В кондитерской был обычный день. Заказ #735443, который разместила мама


Маши, не был чем-то особенным. Все выполняли по плану.

Для начала рассмотрим алгоритм работы кондитерской:


1. Кондитерская принимает заказ

2. Кондитерская передает заказ в пекарню

3. Пекарня выполняет заказ

4. Пекарня передает заказ обратно в кондитерскую

5. Кондитерская упаковывает заказ

6. Кондитерская передает заказ в службу доставки

7. Служба доставки доставляет заказ

Ошибка может возникнуть на любом этапе. Представим ситуацию, где все


хотят "как лучше" и вместо того, чтобы сообщить о проблеме предпочитают
промолчать.
Вполне возможно, что еще на первом этапе кондитерская не получила
достаточно информации, чтобы выполнить заказ. Могли возникнуть проблемы
со связью и в деталях заказа была только дата доставки и адрес.

Посмотрим что произойдет дальше:


1. Кондитерская принимает неполный заказ
2. Кондитерская передает неполный заказ в пекарню (что получили, то и
отдали: "не наши проблемы!")
3. Пекарня выполняет неполный заказ (круто, ничего не надо делать!)
4. Пекарня передает неполный заказ обратно в кондитерскую (кондитерская не
замечает ничего странного, так как раньше проблем с неполным заказом не
возникало)
5. Кондитерская упаковывает неполный заказ (все хорошо, упаковка в наличии)
6. Кондитерская передает неполный заказ в службу доставки
7. Служба доставки доставляет неполный заказ

В алгоритме работы пекарни можно добавить проверки. Например:


o На этапе принятия заказа проверить что в заказе обязательно должны быть
продукт, дата доставки и адрес.

o На этапе пекарни в заказе обязательно должен быть продукт. Пекарню уже не


интересуют дата доставки и адрес.

o На этапе доставки проверить, что в заказе адрес, дата доставки и


упакованный продукт. В этот раз проблем с адресом и датой не было. Но кто
знает что случится завтра!
— Мне кажется, что достаточно добавить все проверки на первом этапе.
Зачем повторяться?

— Отличный вопрос. Каждый этап — это отдельная функция. Она обязана


проверить входящие параметры и если с ними что-то не в порядке — сообщить
наверх бросив ошибку.

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


будут вызываться функции.
Лучший способ избежать проблем — это сообщить о проблеме на шаге 1,
конечно. И если в примере из реальной жизни, это кажется очевидным, то в
программировании очень велик соблазн "ехать дальше" вместо того, чтобы
завершить выполнение программы, если что-то идет не так.

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


сообщать о проблеме так быстро, как только сможем.

Даже если кто-то наймет на работу бестолкового менеджера (перепишет


функцию приема заказа), то грамотный пекарь не будет принимать заказ в
котором нет информации о том, что именно ему нужно приготовить.

При чем тут JavaScript

Похожие проблемы могут возникнуть и в программировании. Например, ты


пишешь функцию, которая принимает уникальный идентификатор пользователя и
возвращает массив его друзей.
const getUserFriends = (userId) => {
if (!userId) {
return [];
}

const user = getUser(userId);


const userFriends = [];

for (let friendId of user.friends) {


userFriends.push(getUser(friendId));
}

return userFriends;
}
Ты не хочешь, чтобы программа сломалась, поэтому проверяшь userId в самом
начале функции. Если его нет — возвращаешь пустой массив.

Но хорошо ли это? Правильно ли?


До поры до времени ты можешь и не замечать проблему в коде. Например, все
работает хорошо и у тебя на входе всегда есть userId. Так может длиться
годами. Но рано или поздно твою функцию начнут вызывать другие функции,
их — еще кто-то. И в итоге, когда пользователи начнут видеть белую
страницу вместо списка своих друзей, понадобится долго и нудно
разбираться с причиной.

Обработка ошибок в JavaScript

Ошибка в JavaScript - это объект типа Error.


const e = new Error();

К ошибке можно добавить сообщение, чтобы была более понятна ее причина.


const e = new Error('user_id is missing');

У ошибок есть одна интересная особенность. Их можно "бросать".


Чтобы бросить ошибку, используй ключевое слово throw.
if (Math.random() > 0.5) {
throw new Error('random error');
}

console.log('no error, execution continues');

Код выше в половине случаев бросит ошибку и выполнение программы


прекратится.

В таком случае ты увидишь на экране сообщение об ошибке и информации о


том, где именно произошла ошибка.
Error: random error
at /home/runner/9ag23k3avya/index.js:2:9
at Script.runInContext (vm.js:130:18)
at Object. (/run_dir/interp.js:209:20)
at Module._compile (internal/modules/cjs/loader.js:999:30)
at Object.Module._extensions..js
(internal/modules/cjs/loader.js:1027:10)
at Module.load (internal/modules/cjs/loader.js:863:32)
at Function.Module._load (internal/modules/cjs/loader.js:708:14)
at Function.executeUserEntryPoint [as runMain]
(internal/modules/run_main.js:60:12)
at internal/main/run_main_module.js:17:47
После ключевого слова throw нужно обязательно указать объект типа Error.
Иначе будет ошибка (но вряд ли та, которую ты ожидаешь).
throw 'an error!';
throw new Object;
throw false;
Пример выше работать не будет, потому что после throw я написал строку,
объект и значение булевого типа.
Как только JavaScript замечает ключевое слово throw, он пытается
прекратить выполнение программы. Единственный способ помешать ему и
продолжить выполнение программы — это блок try/catch.
try {
if (Math.random() > 0.5) { // Math.random() вернет случайное число
от 0 до 1. В половине случаев оно будет больше 0.5.
throw new Error('random error'); // бросаем ошибку
}

console.log('no error'); // выводим это сообщение на экран если


ошибки не было
} catch (e) {
console.log(e.message); // выводим сообщение об ошибке 'random
error' если она возникла
}

console.log('execution continues'); // продолжаем выполнение


программы и в любом случае выводим это сообщение на экран
Перед блоком кода, который может бросить ошибку я поставил ключевое
слово try и обернул весь "опасный код" в фигурные скобки.
Сразу за ним, я добавил блок catch и обработал ошибку внутри него.
— Вижу что после try круглых скобок нет, а после catch есть (e).

— Да. Так и должно быть.


В блоке catch мы сможем проанализировать ошибку и решить что делать
дальше. Чтобы работать с ошибкой, нужно как-то ее назвать. Воспринимай
это как параметр функции.
Ты вполне можешь написать что-то вроде catch (myError). Но тогда и внутри
блока catch ты будешь работать с объектом myError, а не e.

Как тогда будет выглядеть вывод сообщения об ошибке на экран?


— Думаю, что console.log(myError.message)
— Правильно. Сообщение об ошибке, которое мы указали при ее создании new
Error('random error'), можно получить в блоке catch. Оно находится в
поле message. Так как ты переименовал ошибку в скобках после catch, то и
обращаться к полю message нужно уже не как e.message, а
как myError.message.
Блок catch полезен тем, что он дает тебе возможность решить что делать
дальше. В некоторых случаях программу стоит продолжить, а в других
разумнее завершить.
Ты вполне можешь бросить еще одну ошибку из блока catch.
— Плохо понимаю какой в этом смысл. Может не стоило тогда ее и ловить?
— Может и не стоило. Но причины ошибок бывают разными. Объект Error дает
тебе возможность понять что происходит и принять решение о дальнейшей
судьбе программы.
В дополнение к try/catch в JavaScript существует еще один важный блок. Он
называется finally. Блок finally выполняется независимо от того, возникла
ли ошибка в блоке try.
Один из распространенных сценариев использования блока finally выглядит
так:
o опасная операция в блоке try захватывает ресурсы (файлы, сеть, память)

o ресурсы нужно освободить в любом случае, независимо от того успешно ли


прошла опасная операция
o освобождение ресурсов происходит в блоке finally
try {
dangerousOperation();
} catch (e) {
handleError(e);
} finally {
performCleanUp();
}
Ты можешь пропустить блок catch и написать просто try/finally.
try {
dangerousOperation();
} finally {
performCleanUp();
}
Код в блоке finally по прежнему выполнится даже если в
блоке try возникнет ошибка.
— Странно. Мне казалось, что смысл try как раз в том, чтобы поймать
ошибку в catch. А тут какая от него польза?

— Мы не всегда можем исправить ошибку в JavaScript.

Бывает, что мы никак не можем продолжить выполнение функции. В таком


случае мы обязаны сообщить об это ошибке наверх, в функцию, которая нас
вызвала.
С блоком catch - это будет выглядеть так.
try {
dangerousOperation();
} catch(e) {
throw e;
} finally {
performCleanUp();
}
Но если ты уберешь блок catch оставив только try/finally, поведение
останется прежним и ошибка "всплывет" на следующий уровень.

Бекэнд, фронтэнд и сервер


— До сих пор, ты работал только с консолью. Выводил текст на экран,
создавал функции и переменные, работал с таймаутами и интервалами. Пришло
время поговорить о бекэнд разработке.
— Отлично, а то я уже начал переживать, что моя судьба — это console.log
— Ну-ну, не торопись. Ты еще не раз оценишь силу console.log, когда тебе
нужно будет быстро понять что не так с твоей программой. Но,
сосредоточимся на важном, а вопросы задашь в конце, как обычно.

Архитектура современных веб приложений

Упрощенно, все что ты видишь в интернете реализовано с помощью двух


частей: Backend и Frontend.
Бекэндом принято называть все, что не видит пользователь. Это и база
данных, и сервер, и балансировщик нагрузки, и прокси и множество других
вещей.
Фронтэнд - это то, с чем взаимодействует пользователь. Это может быть и
консоль, как в последней задаче первого уровня "Золотая Шахта", и веб
приложение на React.js, и нативное мобильное приложение для iOS и
Android. Словом, все, что находится в прямой пользовательской
доступности.
Бекэнд и Фронтэнд взаимодействуют с помощью API. Это некоторые "точки
входа", которые бекэнд открывает для взаимодействия с фронтэндом.

Попробуем продумать реализацию простого приложения "Прогноз погоды",


чтобы понять на примерах, что к чему. Начнем с формулировки задачи:
"Как пользователь, я хочу видеть текущую погоду в своем городе." — Аноним

Итак, у нас 2 части каждая со своими задачами:


1. Фронтэнд — должен показать пользователю текущую температуру и вероятность
осадков.

2. Бекэнд — должен дать фронтэнду все необходимые данные.

Задача бекэнд разработчика — сделать все необходимое, чтобы фронтэнд мог


отправить запрос с названием города и получил в ответ текущую погоду.
Во-первых, нужно реализовать API, с которым сможет работать фронтэнд.
Нужно корректно обрабатывать любые входящие данные. Если передан
корректный город для поиска прогноза погоды, то нужно будет эту погоду
где-то получить и отправить ответ фронтэнду в формате, который будет ему
понятен.

Возможно, для поиска нужно будет сделать запросы в базу данных, а может
нужно будет авторизоваться на каком-то сервисе и получить прогноз погоды
там.

Мы будем разрабатывать бекэнд на Node.js, как одной из самых быстрых и


современных технологий в 2022.

Сервер

Сервер — это компьютер. Практически такой же, как и твой телефон, планшет
или ноутбук. Большинство отличий, видимых тебе, лежат в плоскости
операционной системы (ОС).
Телефоны работают на iOS и Android. Ноутбуки используют Linux, macOS,
или Windows. Операционная система определяет то, какие возможности будут
доступны пользователю, и что он сможет делать со своим компьютером
(телефоном, ноутбуком и т.п.)
Большинство серверов в 2022 работают на Linux. Возможно ты слышал такие
слова, как Ubuntu, Fedora, Debian, Red Hat Linux - это все "дистрибутивы
Linux". Проводя нам не очень важны отличия между ними.
С точки зрения сервера, бекэнд — это просто программа. Hello world,
который мы писали в первой лекции может быть отличным бекэндом и
запускаться все той же командой node solution.js на любой из
перечисленных выше серверных операционных систем.

Конечно, есть свои особенности в том, что фронтэнд работает у


пользователя на компьютере, а бекэнд на сервере. Между ними может быть
тысяча миль и нужно настроить их взаимодействие по сети.

Но, запомни главное. Нет ничего сверхъестественного и магического во всех


неизвестных словах, которые ты видел раньше.
Сервер — это компьютер, а бекэнд — это программа запущенная на этом
компьютере.

С одной стороны, Интернет работает очень сложно. А с другой — очень


просто. Это вводная лекция и мы не будем перегружать тебя лишней
информацией, а дадим только самое важное. Если ты что-то не поймешь —
обязательно спроси!

IP адрес
У каждого устройства подключенному к интернету есть свой IP адрес. IP -
сокращение от Internet Protocol. Ты можешь встретить сокращение до
просто IP или просто адрес. Так же как и домашний адрес, однозначно
определяет твою позицию на Земле, IP позволяет однозначно определить
положение твоего компьютера.
IP адрес выдает вам провайдер в момент подключения к интернету. Это может
быть мобильный оператор, если ты используешь телефон или кабельный
провайдер, если работаешь дома со стационарного компьютера.
Существует два стандарта IP адресов в интернете. Это IPv4 и IPv6. Мы
рассмотрим только первый, так как вероятнее всего именно IPv4 адреса ты
встретишь в работе.
Общий формат IPv4 адреса: xxx.xxx.xxx.xxx, где xxx - число от 0 до 255.
Проверить адрес своего устройства ты можешь прямо сейчас тут.
Запросы

Устройства в интернете общаются между собой с помощью запросов.


Существует множество разных протоколов (правил), по которым происходит
это общение. Мы будем рассматривать исключительно протокол HTTP и его
защищенную версию HTTPS.
Когда ты открываешь какую-то страницу в браузере, твой компьютер
отправляет на сервер GET запрос. Этот запрос служит для получения данных.
Кроме GET, часто используются POST, PUT и DELETE.

Чтобы отправить запрос на сервер (или с одного компьютера на другой), нам


нужно кроме типа запроса и IP адреса знать еще и порт. Если IP адрес —
это аналог адреса твоего дома, то порт — это номер квартиры.
Порт — это число от 0 до 65535.
Порт чаще всего указывается после IP адреса через двоеточие. Например
так address:port или 175.12.65.123:8080.

Одна программа запущенная на сервере может занять один или более портов.
Занять порт — значит быть ответственным за все запросы, которые будут на
него отправлены. Как правило, нам достаточно одного порта.
Итак, у нас есть IP адрес, порт и тип запроса GET. Попробуем отправить
его на сервер и получить ответ.
Введи в браузер строку 51.15.200.89:8090 и посмотри что получится.

Доменные имена

Чтобы не нужно было запоминать IP адреса, придумали систему доменных


имен.
Например, google.com, apple.com,coderslang.com, js.coderslang.com, facebo
ok.com - это все доменные имена.
Доменное имя указывает на IP адрес. Чтобы определить на какой именно,
попробуй выполнить в терминале команду ping.
ping google.com
В результате ты увидишь на экране IP адрес, на который указывает доменное
имя google.com.
PING google.com (172.217.16.110): 56 data bytes
64 bytes from 172.217.16.110: icmp_seq=0 ttl=117 time=16.889 ms
64 bytes from 172.217.16.110: icmp_seq=1 ttl=117 time=15.768 ms
64 bytes from 172.217.16.110: icmp_seq=2 ttl=117 time=15.582 ms
64 bytes from 172.217.16.110: icmp_seq=3 ttl=117 time=19.150 ms
Если вывод не прекратится сам собой, ты можешь нажать Ctrl+C, чтобы
остановить ping.
Кроме IP адреса 172.217.16.110, мы видим время, которое
потребовалось ping, чтобы отправить минимальный запрос и получить ответ
от сервера.
Связь между доменным именем и IP адресом хранится на специальном сервере,
который называется DNS. Таких серверов множество.
Не вникая в глубины реализации, просто запомни, что DNS можно представить
как список пар доменное имя - IP адрес. Чтобы сделать HTTP запрос с
использованием доменного имени, нужно сначала узнать у DNS сервера IP
адрес на который указывает нужный нам домен.

К счастью, в Node.js эта функциональность уже реализована "под капотом" и


мы можем использовать как доменные, так и IP адреса.

103. Первый сервер на Express.js


В прошлой лекции ты увидел серверный аналог Hello, world. Наша программа
смогла обработать запрос, и вернуть ответ, который и вывел на экран
браузер.

Сегодня, ты реализуешь свой первый бекэнд на Node.js!

Express.js и модули npm

Когда говорят о бекэнд разработке на Node.js, то в большинстве случаев


подразумевают работу с Express.js. Это фреймворк с открытым кодом,
который распространяется с помощью npm.
Если ты еще не знаешь, как работать с npm пакетами, то советую прочитать
эти две статьи:
o Поиск и установка npm модулей
o Импорт и использование npm модулей
Чтобы добавить Express.js в проект, ты должен его установить:
npm install --save express
Флаг --save говорит о том, что express будет добавлен в
секцию dependencies в файле package.json, где хранятся описания
зависимостей твоего Node.js проекта.

После установки, добавим импорт:


import express from 'express';
const server = express();

Express.js и Node.js скрывают от нас множество сложностей связанных с


обработкой HTTP запросов и всю низкоуровневую работу с сетью.
Чтобы добавить обработчик GET запросов, нам нужно вызвать
функцию server.get(route, handler), где route - строка, описывающая путь
к ресурсу, а handler - функция, которая будет выполнена, когда на
путь route придет запрос типа GET.
server.get('/', (req, res) => {
return res.send('Hello, Express.js!');
})
В качестве пути, мы используем /, но это может быть и что-то другое,
например /about, /blog или /profile.
Функция обработчик принимает два параметра req и res. Они очень важны, но
пока запомни, что можно отправить ответ с помощью функции res.send().
Если ты не вызовешь res.send(), а просто напишешь return 'hello world';,
то сервер не отправит ответ.

Дальше, нам нужно запустить сервер.


Воспользуемся функцией listen(port, callback). Она ожидает 2 параметра. В
качестве порта ты можешь выбрать любой свободный порт в своей системе,
а callback - это функция, которая выполнится после того как сервер будет
запущен. Обычно в ней выводят информацию о том, что сервер запущен на
таком-то порту и все хорошо.
const port = 8080;

server.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
Если ты выполнишь все по инструкции, сохранишь javascript код в
файл index.js и напишешь node index.js, то ты запустишь свой первый
сервер написанный с использованием фреймворка Express.js.
Пока он не будет доступен никому извне, но если ты попробуешь перейти в
браузере по адресу localhost:8080, ты увидишь, что сервер отвечает Hello,
Express.js!.

104. Middleware и внешний доступ


Отлично, у нас уже есть рабочий сервер и мы сделали несколько заданий,
чтобы лучше понять как он работает. Теперь, разберем middleware - одну из
важнейших концепций Express.js.

Middleware

У термина middleware нет хорошего перевода на русский язык. Кто-то


говорит "промежуточное ПО" или "промежуточные функции", но в реальности
просто выбери любую транслитерацию английского слова и смело ее
используй. Это может быть "миддлвэр", "мидлвар", "мидлвэйр" или что-то
похожее.
Прежде чем запрос попадет на функцию обработчик, который мы указали
вторым параметром в server.get, она проходит через цепочку других
функций. Каждая из этих функций, и все они вместе —
называются middleware.
Добавить эти middleware функции, ты можешь с помощью use.
server.use((req, res, next) => {
console.log(`Received ${req.method} request!`);
next();
})
Добавив эти несколько строк кода в проект, ты увидишь в консоли
сообщение Received GET request! при каждом обновлении
страницы localhost:8080 в браузере. В поле req.method хранится
тип HTTP запроса, в данном случае GET.
У middleware функций 3 параметра. Последний, next - это сервисная
функция, которую нужно вызвать, если ты хочешь продолжить движение
запроса по стеку middleware и дальше.

Бывают ситуации, когда ты хочешь, чтобы не все запросы дошли до


обработчиков маршрутов. Хороший пример — авторизация. Ты можешь принять в
запросе пароль или секретный ключ и пропустить дальше только те запросы,
которые пройдут проверку.
const checkSecretKey = (req, res, next) => {
if (!req.query.secretKey || req.query.secretKey !==
'TistRIanDsMOUnca') {
return res.status(403).send('Secret key is missing or incorrect');
}
next();
}
Тут, мы проверяем, есть ли в запросе поле secretKey и если есть, то
проверяем его значение. Если возникают проблемы с проверкой, то
отправляем обратно статус 403 и сообщение Secret key is missing or
incorrect. А если все хорошо, то отправляем запрос дальше с помощью
вызова next().
Добавить функцию checkSecretKey в стек middleware можно с помощью все той
же функции use.
app.use(checkSecretKey);
Чтобы проверить себя, попробуй добавить проверку секретного ключа сначала
перед выводом типа запроса, а потом после. В одном случае ты увидишь
вывод в консоль, в другом — нет. Понимаешь почему?

Что такое строка запроса (query string)

В предыдущем примере, мы попробовали получить доступ к req.query - строке


запроса. В нее входят все параметры, которые ты укажешь после основного
маршрута и знака вопроса.
localhost:8080/about?secretKey=123&name=jack
В этом примере, строка запроса — это secretKey=123&name=jack. Она состоит
из двух параметров - secretKey и name.
Express.js сам расшифрует ее и даст нам красивый объект в req.query,
который будет равен
{
secretKey: 123,
name: 'jack',
}
Вернись на полшага назад и попробуй отправить на сервер запрос с
правильным secretKey. Наша авторизация должна сработать и ты должен
получить доступ к серверу.
Доступ из внешнего мира и localtunnel

Сейчас со своим новым бекэндом ты можешь пообщаться только со своего


локального компьютера. Для того, чтобы устройства и пользователи извне
могли поговорить с твоим сервером, нельзя просто заменить localhost на IP
адрес, который ты узнал по ссылке в самом начале лекции. Точнее,
технически, было бы можно, но для этого нужно было бы написать инструкцию
на 3 страницы. Думаю, что ты бы уснул не дочитав и до середины.
К счастью, есть очень удобный npm пакет, который называется localtunnel.
Ему можно сказать примерно следующее: "Мой бекэнд запущен на моем
компьютере, на порту 8080. Сделай, пожалуйста, так, чтобы он был доступен
всем желающим.". Звучит как магия, и, если не вдаваться в подробности,
так оно и есть, localtunnel выдаст тебе адрес, по которому любой
пользователь интернета сможешь поговорить с твоим бекэндом.
Инструкции по установке localtunnel, ты можешь найти тут. Но я покажу
весь процесс по шагам:
1. Открываем терминал и вводим эту команду для глобальной
установки localtunnel
npm install -g localtunnel

2. Запускаем наш бекэнд на порту 8080 (или любом другом свободном)

3. Возвращаемся в терминал и пишем


lt --port 8080

4. В ответ получим сообщение


your url is: https://bright-pug-55.loca.lt
Твой url будет, конечно, другим, но если ты введешь его в браузер своего
мобильного телефона или любое другое устройство подключенное к интернету,
ты сначала увидишь приветственную страницу

А после клика на Click to Continue то же, что было доступно раньше только
на localhost:8080.
105-107. JSON
Один из самых популярных форматов обмена данными между бекэндом и
фронтэндом - JSON, или JavaScript Object Notation. Он очень похож на то,
как выглядят обычные JavaScript объекты, но также имеет свои особенности.
Читается - "джейсон", хотя часть твоих будущих коллег будут говорить и
"джсон", и "жсон", и даже "жисон".

JSON не накладывает никаких ограничений на язык программирования, который


будет с ним работать. Ты можешь работать в организации, где часть бекэнд
сервисов написана на Python, часть на Java, а фронт на JS и все они
прекрасно обмениваются JSON сообщениями.

Хранение данных в формате JSON

Начнем с того, что JSON - это строка. Это позволяет при необходимости
очень эффективно сжимать данные. Недостаток — мы не можем хранить
циклические структуры данных, например объект, который ссылается на
самого себя.

(Почти) все должно быть обернуто в кавычки

В отличие от JavaScript, ты должен пользоваться только двойными кавычками


и оборачивать в них все свойства объектов. Одинарные или обратные (косые)
кавычки использовать нельзя.

В JS у нас был такой объект


{
name: 'Jack',
isMarried: false,
age: 25,
}
А в JSON он станет таким
{
"name": "Jack",
"isMarried": false,
"age": 25
}
Обрати внимание, что в JavaScript объектах наличие запятой после age:
25, является допустимым, а в JSON - нет.

Названия всех полей обернуты в двойные кавычки, а значения — не все.


Числа и булевы значения хранятся без кавычек.

Объекты хранятся в фигурных скобках

Для хранения объектов используются фигурные скобки, как и в JS.

Заметь, что если сервер отвечает в формате JSON, то предполагается, что


он ответит объектом. Ты не можешь просто список полей. Они все
обязательно должны быть обернуты в фигурные скобки, чтобы стать JSON
объектом.

Массивы хранятся в квадратных скобках

Все точно как в JS, оборачиваем название массива в двойные кавычки, а сам
массив указываем в квадратных скобках.
{
"pets": ["Rex", "Sandy"]
}

Еще раз обращаем внимание, что в конце строки нет ни запятой, ни точки с
запятой.

Все данные JSON в объекте хранятся как пары "ключ":"значение"


Как и в JS, ты можешь добавлять в объект только пары ключ:значение. Если
тебе нужно сохранить несколько значений без ключей, то тебе нужен массив.
Конвертация JavaScript объектов в JSON и обратно

Для конвертации из обычного JS объекта в JSON строку, тебе нужна


функция JSON.stringify(obj). Она доступна без установки дополнительных
модулей. Передаешь ей объект obj и на выходе получаешь JSON объект.
const user = {
name: 'Jack',
isMarried: false,
age: 25,
}

const userJSON = JSON.stringify(user);


console.log(userJSON); // {"name":"Jack","isMarried":false,"age":25}
Для конвертации из JSON в обычный объект, нам нужна
функция JSON.parse(s). Даем строку в формате JSON на вход, получаем JS
объект на выходе.
const jsonString = '{"name":"Jack","isMarried":false,"age":25}';
const parsedUser = JSON.parse(jsonString);

console.log(parsedUser); // { name: 'Jack', isMarried: false, age:


25 }

Express.js и JSON

Так как мы знаем, что JSON объект — это строка, нам будет просто
модифицировать сервер и вместо Hello, Express.js отправлять какой-нибудь
объект.

Представим, что нам нужно передать на фронтэнд объект


{
name: 'Hero',
isLearning: true,
level: 'apprentice',
}
Сделаем это несколькими способами. Во всех случаях фронтэнд получит одно
и то же, в чем ты можешь убедиться с помощью запроса в браузере.

1. Обычная строка:
server.get('/', (req, res) => {
return
res.send('{"name":"Hero","isLearning":true,"level":"apprentice"}');
})
2. Объект, преобразованный с помощью JSON.stringify:
server.get('/', (req, res) => {
const user = { name: 'Hero', isLearning: true, level: 'apprentice'
};
return res.send(JSON.stringify(user));
})
3. Объект преобразованный с помощью res.json:
server.get('/', (req, res) => {
const user = { name: 'Hero', isLearning: true, level: 'apprentice'
};
return res.json(user);
})
Повторю еще раз. Во всех случаях в итоге получится одно и то же. Мы
отправим ответ со статусом 200 и
строкой {"name":"Hero","isLearning":true,"level":"apprentice"}, которую
получатель сможет использовать как ему захочется.
По правде говоря, между res.send и res.json есть разница и она состоит в
типе ответа. Это специальный заголовок Content-Type, который в
случае res.send устанавливается равным text/html, а
для res.json — application/json.
Используй res.json, если у тебя есть готовый объект, который ты хочешь
отправить в формате JSON.
Третий пример самый удачный, так как нам нужно делать миниум лишних
действий. Мы передаем объект в res.json и преобразование в JSON строку
происходит внутри. Дополнительный (явный) вызов JSON.stringify, как в
примере 2, в этом случае не нужен.

108. Промисы
До сих пор ты работал только с обычными значениями. Создавал переменную
или константу, сохранял туда значение и мог сразу его использовать,
например вывести на экран.

Но что делать, если значение появляется не сразу, а спустя какое-то


время? Данные мы часто получаем из базы данных или внешнего сервера. Эти
операции занимают какое-то время и есть два способа работать с ними:
o Мы можем попробовать заблокировать выполнение программы до тех пор, пока
не получим данные.

o Или мы можем продолжить выполнение программы, а с данными разобраться


когда они появятся.

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

Если данные, которые ты ждешь критически важны для продвижения дальше, то


нужно блокироваться и ничего с этим не сделать. А если можно отложить их
обработку, то конечно не стоит терять время, ведь можно сделать еще что-
то полезное.

Что такое Promise?

Promise - это специальный тип объектов, который помогает работать с


асинхронными операциями.

Многие функции будут тебе возвращать промис в ситуациях, когда значение


невозможно получить сразу.
const userCount = getUserCount();
console.log(userCount); // Promise { }
В этом случае getUserCount - это функция, которая возвращает Promise.
Если мы попробуем сразу же вывести на экран значение
переменной userCount, то получим что-то вроде Promise { <pending> }.

Это произойдет потому, что данных еще нет и нам нужно их ждать.

Состояния Promise

Промис может быть в нескольких состояниях:


o Pending - ответ еще не готов. Ждите.
o Fulfilled - ответ готов, успех. Заберите данные.
o Rejected - ответ готов, ошибка. Обработайте.
С состоянием pending мы не можем сделать ничего полезного, только ждать.
А в других случаях, можем добавить функции обработчики, которые будут
вызваны при переходе промиса в состояние fulfilled или rejected.
Для того, чтобы обработать успешное получение данных, нам нужна
функция then.
const userCount = getUserCount();
const handleSuccess = (result) => {
console.log(`Promise was fulfilled. Result is ${result}`);
}

userCount.then(handleSuccess);
А для обработки ошибок - catch.
const handleReject = (error) => {
console.log(`Promise was rejected. The error is ${error}`);
}

userCount.catch(handleReject);
Обрати внимание, что функция getUserCount возвращает промис, поэтому мы
не можем напрямую использовать userCount, а передаем
в then и catch функции обработчики, которые будут вызваны в случае успеха
или ошибки.
Функции then и catch можно вызвать последовательно. В таком случае мы
учтем и ситуацию, когда все будет хорошо, и когда возникнет ошибка.
const userCount = getUserCount();
const handleSuccess = (result) => {
console.log(`Promise was fulfilled. Result is ${result}`);
}

const handleReject = (error) => {


console.log(`Promise was rejected. The error is ${error}`);
}

userCount.then(handleSuccess).catch(handleReject);

Обработка ошибок

Предположим, что у нас есть функция getUserData(userId), которая


возвращает информацию о пользователе или бросает ошибку, если с
параметром userId будут какие-то проблемы.
Раньше мы добавляли обычный try/catch и могли обработать ошибку в
блоке catch.
try {
console.log(getUserData(userId));
} catch (e) {
handleError(e);
}
Но ошибки, которые возникают в асинхронном коде внутри промисов,
невозможно поймать обычным try/catch.
Попробуем заменить блокирующую функцию getUserData(userId) , которая
сразу возвращает результат на асинхронную fetchUserData(userId), которая
возвращает промис.

Поведение мы хотим оставить тем же — вывести на экран результат в случае


успеха, или обработать ошибку, если она возникнет.
try {
fetchUserData(userId).then(console.log);
} catch (e) {
handleError(e);
}
Но у нас это не получится. В основном, синхронном, коде ошибки нет,
поэтому выполнение продолжится и дальше. Но когда в асинхронном коде
возникнет необработанная ошибка, мы получим UnhandledPromiseRejection и
наша программа завершится.
Для того, чтобы лучше понять порядок выполнения программы, добавим
блок finally. Он выполнится в любом случае, но будет это
до UnhandledPromiseRejection или после?
try {
fetchUserData(userId).then(console.log);
} catch (e) {
handleError(e);
} finally {
console.log('finally');
}

Разберемся пошагово:
1. В блоке try мы вызываем функцию fetchUserData, которая
возвращает Promise в состоянии pending
2. Блок catch игнорируется, потому что в блоке try ошибок не было.
Асинхронное выполнение пока никого не интересует!
3. На экран выводится строка finally
4. Возникает ошибка в асинхронном коде и мы видим в
консоли UnhandledPromiseRejectionWarning
Чтобы не возникало необработанных ошибок в промисах, к ним нужно всегда
добавлять обработчики в .catch().
fetchUserData(userId).then(console.log).catch(handleError);

Код стал короче, чище и мы избавились от неожиданных ошибок, которые


могут сломать наш код.
В предыдущей лекции мы рассмотрели ситуацию, когда промис нам возвращает кто-то
другой. Сегодня начнем с того, как создать промис самостоятельно.

Создание промисов

Промис (и не только) можно создать с помощью ключевого слова new


const promise = new Promise(executor);
Параметр executor - это функция у которой должно быть два параметра (тоже
функции):
o resolve - используется когда все прошло хорошо и нужно вернуть результат
o reject - используется если возникла ошибка
Функция executor вызывается автоматически, а resolve или reject внутри
нее нам нужно вызвать самостоятельно.
Напишем функцию coinflip, которая имитирует бросок монетки. Принимает
ставку bet и в половине случае завершается ошибкой, а в половине случаев
"думает" 2 секунды и возвращает удвоенную ставку.
const coinflip = (bet) => new Promise((resolve, reject) => {
const hasWon = Math.random() > 0.5 ? true : false;
if (hasWon) {
setTimeout(() => {
resolve(bet * 2);
}, 2000);
} else {
reject(new Error("You lost...")); // same as -> throw new Error("You
lost...");
}
});
В функцию resolve мы передаем значение, которое станет доступно после
того, как промис станет fulfilled.
А в reject - ошибку. Технически мы можем не вызывать reject, а просто
использовать throw. Разницы никакой не будет.
Давай используем функцию coinflip.
coinflip(10)
.then(result => {
console.log(`CONGRATULATIONS! YOU'VE WON ${result}!`);
})
.catch(e => {
console.log(e.message);
})
Как и раньше, если все пройдет хорошо, то результат мы получим
внутри then. А ошибки обработаем внутри catch.

109.Цепочки промисов
Часто возникают ситуации, когда одно асинхронное действие должно
выполниться после другого асинхронного действия.
Например, мы можем попробовать поставить еще раз, если удалось
выиграть coinflip. И потом еще.
Для этого, можно создавать цепочки промисов. В общем виде они выглядят
так:
promise
.then(...)
.then(...)
.then(...)
.catch(...)
Первый .then вернет промис, и к нему можно будет привязать еще
один .then и так .
А вот обработчика ошибок в .catch нам будет достаточно одного, в самом
конце цепочки. Заодно добавим небольшой рефакторинг, чтобы избежать
дублирования кода. Смотри:
const betAgain = (result) => {
console.log(`CONGRATULATIONS! YOU'VE WON ${result}!`);
console.log(`LET'S BET AGAIN!`);
return coinflip(result);
};

const handleRejection = (e) => {


console.log(e.message);
};

coinflip(10)
.then(betAgain)
.then(betAgain)
.then(betAgain)
.then(result => {
console.log(`OMG, WE DID THIS! TIME TO TAKE ${result} HOME!`);
})
.catch(handleRejection);
Функция betAgain принимает число, выводит на экран поздравления и делает
ставку снова. Потом мы добавляем столько блоков .then сколько нам нужно
для выполнения задачи.
На самом деле, betAgain нам была нужна только для вывода сообщений
внутри. Если бы нас просто интересовало увеличение ставки, то мы бы могли
просто передавать в .then функцию coinflip. Вот так:
coinflip(10)
.then(coinflip)
.then(coinflip)
.then(coinflip)
.then(result => {
console.log(`OMG, WE DID THIS! TIME TO TAKE ${result} HOME!`);
})
.catch(handleRejection);

Promise.all

Вернемся из нашего виртуального казино в реальный мир.


Представим, что у нас есть функция getUserData, которая возвращает имя
пользователя, его id и список id его друзей. Примерно такой объект:
{
id: 125,
name: 'Jack Jones',
friends: [1, 23, 87, 120]
}
Получаем мы его, конечно, не сразу, а после того как промис
станет fulfilled.
И нам поставили задачу — вывести на экран список всех друзей
пользователя, но не просто id, а все их данные.
Работать с одним промисом мы уже умеем, начнем с вывода списка id друзей
на экран:
getUserData(userId).then(console.log);
Дальше, мы могли бы попробовать взять список друзей и преобразовать его с
помощью map таким образом, что у нас бы появилась информация о каждом
друге:
getUserData(userId)
.then(userData => {
return userData.friends.map(getUserData);
})
.then(console.log)
.catch(e => console.log(e.message));
Неплохо. Но на экране мы увидим [ Promise { <pending> }, Promise {
<pending> } ] вместо полной информации о друзьях.
К сожалению, у нас не получится добавить еще один then или map, потому
что массив у нас уже есть, а промисы внутри него еще в состоянии pending.
Для решения этой проблемы нам будет нужна функция Promise.all(array). Она
принимает массив промисов и возвращает один промис.
Этот промис станет fulfilled когда все промисы из array станут fulfilled.
А если хотя бы один из них завершится с ошибкой, то
статус rejected получит и весь Promise.all.
getUserData(userId)
.then(userData => {
return Promise.all(userData.friends.map(getUserData));
})
.then(console.log)
.catch(e => console.log(e.message));

Теперь программа работает как ожидалось и мы выводим на экран список всех


друзей пользователя.

Promise.race

Если нам нужен результат только одного промиса из множества, то мы можем


использовать функцию Promise.race(arr).
Как и Promise.all, она принимает массив промисов и возвращает один
промис. Но нельзя предсказать заранее, чему будет равно значение
возвращенное из промиса после того, как он перейдет в
состояние fulfilled.
С помощью Promise.race мы можем получить результат того промиса, который
выполнится быстрее всех в массиве.
const fastPromise = new Promise((resolve, reject) => {
setTimeout(() => resolve(`fast`), 100);
});

const slowPromise = new Promise((resolve, reject) => {


setTimeout(() => resolve(`slow`), 200);
});

const arr = [fastPromise, slowPromise];

Promise.race(arr).then(console.log); // fast
На экран будет выведено сообщение fast через 100 миллисекунд и мы не
будем ждать выполнения второго промиса.

110. Async/await
Промисы — очень удобный инструмент организации асинхронного кода. Но
мозгу человека намного привычнее синхронные операции. Сделали что-то,
подождали, потом продолжили.

Await

Для упрощения асинхронных операций в JavaScript придумали ключевое


слово await. Если написать его перед функцией, которая возвращает промис,
то выполнение программы остановится до тех пор, пока промис не
завершится.
const userCount = await getUserCount();

console.log(userCount); // 12345
На экране появится сразу значение с которым завершится промис, который
возвращает getUserCount(). А если не написать await, то мы увидим в
консоли строку Promise { <pending> }.

Async

Ключевое слово await нельзя использовать в обычной функции. Нужно


обязательно сделать ее асинхронной, добавив перед списком параметров
функции ключевое слово async.
const logUserCount = async () => {
const userCount = await getUserCount();
console.log(userCount);
}
Все асинхронные функции возвращают Promise, даже если внутри них
нет await.
const getHelloWorld = async () => {
return 'Hello, world!';
}

console.log(getHelloWorld); // Promise { }
А чтобы получить результат промиса, нужно добавить все тот же await. Или
убрать async, если ты точно уверен, что функция getHelloWorld будет
выполнять только синхронные операции.
const getHelloWorld = async () => {
return 'Hello, world!';
}

console.log(await getHelloWorld()); // Hello, world!

Обработка ошибок

Ошибка может возникнуть всегда. Особенно в асинхронных операциях. Файл


может оказаться недоступен, когда мы пытаемся его прочитать. Или может
пропасть интернет, когда мы будет грузить картинку с удаленного сервера.
Ошибки в промисах обрабатываются с помощью .catch():
fetchUserData(userId).then(console.log).catch(handleError);
Но если ты используешь await для ожидания промиса, то ты можешь
использовать для обработки ошибок обычный блок try/catch:
try {
await fetchUserData(userId)
} catch (e) {
console.log('asynchronous error was caught!');
handleError(e);
}

201.Браузер и текстовый редактор


— Привет! И вот мы добрались до фронтэнд разработки. Если ты только
начинаешь и не будешь что-то понимать, вернись на уровень "Новичок" или
задай мне вопросы после лекции.
В лекции Бекэнд, Фронтэнд и Сервер, Техник рассказывал тебе о базовой
архитектуре современных веб приложений и о том, как разделены роли между
бекэндом и фронтэндом.

Сервер работает непрерывно. Он ожидает запросы от браузера, которые могут


прийти в любое мгновение. Запросы включают в себя веб-страницы, аудио,
видео или простой текст. Сервер находит эти ресурсы и отправляет их
пользователю.

Минутка истории

С браузером ты работаешь каждый день. В твоем мобильном телефоне, на


планшете, ноутбуке или стационарном компьютере у тебя наверняка есть
Chrome, Safari, Firefox или Opera.

В 2022 браузеры — это мощнейшие инструменты для взаимодействия с сетью.


Все идет к тому, что у нас не будет "обычных приложений". И мы сможем
делать все что нам нужно в браузере.

Возможно ты удивишься, но это не всегда было так. Первые браузеры,


появившиеся в начале 90х годов отображали только текст и очень редкие
картинки.
Первый успешный браузер - Netscape Navigator, появился в 1994 году и в
основном отображал статический контент.
Все изменилось в 1995 году, когда появился JavaScript. Название было
выбрано не то чтобы случайно, но явно разработчики долго над ним не
думали. В те времена в раскрутку языка Java вкладывались очень большие
деньги, и они решили прокатиться на волне этой популярности. Хотя есть и
более консервативное мнение, которое говорит о том, что
название JavaScript должно было просто отражать поддержку Java апплетов
в Netscape.
Но, оставим раскопки археологам. Нас интересует из прошлого только то,
что актуально сейчас и будет актуально еще через 20 лет. Например - HTML.

HTML

HyperText Markup Language или HTML - это язык на котором говорит браузер.
В русском языке есть термин "язык гипертекстовой разметки", но им никто
не пользуется.
HTML - это текст, написанный по определенным правилам. Задача браузера —
понять этот текст, преобразовать и выдать тебе на экран красивую веб
страницу.
Например, вот так выглядит фрагмент html файла нашей базы знаний:

А вот что нам показывает браузер:


Можешь расслабиться. Пока тебе не нужно вникать ни в первый, ни во второй
примеры. Максимум можешь попробовать найти общий текст в них.
В следующей лекции, я расскажу тебе об основных правилах HTML и ты
сможешь создать свою первую веб страницу.

Текстовый редактор

Так как HTML - это текст, то писать его можно где угодно. Точно как и
программы на JavaScript!
Я рекомендую тебе использовать VSCode, как один из лучших бесплатных
инструментов для написания кода в 2022.
Практические задачи ты будешь получать прямо в VSCode и там же сможешь
отправить их на проверку. Если задание будет выполнено неверно, то ты
сразу увидишь изменения, которые необходимо сделать.

202. Введение в HTML


Что же такое HTML? Именно с помощью HTML обеспечивается структура
контента, который отображается на веб-странице.
HTML расшифровывается как HyperText Markup Language - язык гипертекстовой
разметки. Язык HTML отвечает за структуру и содержание страницы, а
кирпичиками для построения страницы являются теги.

Код HTML интерпретируется браузерами. Каждый тег имеет свое значение и


нужен для отдельных задач. Одни теги увеличивают размер шрифта, другие
создают списки, третьи — таблицы.

HTML теги

Тегов существует не очень много и с практикой ты их все запомнишь.


У всех тегов есть имена, которые заключены в угловые скобки, или, другими
словами, знаки меньше < и больше >. Мы начнем с парных тегов, потому что
состоят из двух частей: открывающий тег и закрывающий. Закрывающий тег
отличается наличием / перед именем тега. Давай рассмотрим на примерах:
o <h1> - <h6> - используются для определения заголовков
HTML. <h1> определяет самый важный заголовок. <h6> определяет наименее
важный заголовок. Заголовок первого уровня отображается с самым большим
размером шрифта, каждый последующий заголовок все меньше и меньше.
<h1>Заголовок 1</h1>
<h2>Заголовок 2</h2>
<h3>Заголовок 3</h3>
<h4>Заголовок 4</h4>
<h5>Заголовок 5</h5>
<h6>Заголовок 6</h6>
Как видишь, каждая строка начинается с открывающего тега (например <h1>),
затем следует текст заголовка, и сразу после него закрывающий тег, перед
именем которого стоит слэш (</h1>).

Принцип написания всех парных тегов одинаковый, меняется только имя.


Кроме заголовков, которые есть во всех веб страницах, очень часто
используются теги <p> и <div>:
o <p> - определяет абзац или параграф. Браузеры автоматически добавляют
одну пустую строку до и после каждого элемента <p>.
<p>Это первый параграф.</p>
<p>Это второй параграф. Он обязательно начнется с новой строки</p>
o <div> - раздел в документе HTML. Также, этот элемент используется, как
контейнер для других HTML элементов. Это — один из самых распространенных
HTML тегов. Содержимое тега <div>, как и тегов заголовка или параграфа,
всегда начинается с новой строки. Его мощь ты сможешь полностью понять
только после того, как познакомишься со стилями.

Одинарные теги

Кроме парных тегов, существуют одинарные. Они отличаются тем, что состоят
только из одной части.
Например, есть такой тег <hr>, который рисует горизонтальную линию-
разделитель. Давай посмотрим как будут выглядеть два параграфа с
заголовком второго уровня каждый, разделенные горизонтальной линией:
<h2>Это заголовок первого параграфа</h2>
<p>Первый параграф</p>
<hr>
<h2>Это заголовок второго параграфа</h2>
<p>А это второй параграф</p>

Одинарным тегам не нужен закрывающий тег.

Блочные элементы

Все теги, которые мы рассмотрели выше, относятся к категории блочных


элементов. Они занимают всю ширину экрана или "родительского контейнера",
но об этом тебе пока рано переживать.

Можешь просто запомнить, что перед и после любого блочного элемента


автоматически добавляется пустая строка.

Задача блочных элементов — задавать структуру веб-страницы. Если страница


— это дом, то блочные элементы — это несущие стены.

Использование тегов

Чтобы начать создавать веб страницы, тебе нужен только текстовый


редактор. Если ты уже работаешь в IDE, например VSCode, то все отлично.
Создавай файл index.html и попробуй добавить туда этот пример:
<h1>CoderslangJS</h1>
<h2>Учим HTML</h2>
<p>
Тяжело в учении, легко в бою.
</p>
<hr>
<p>
Главное - постоянство. Тренируйся каждый день и скоро станешь
профессионалом.
</p>
Любые теги можно писать как прописными, так и строчными символами или их
комбинациями. <DIV>, <div>, <DiV> - все три варианта абсолютно правильные
и результат будет одинаковым.

Но, твои коллеги наверняка будут писать все маленькими буквами, поэтому
советую следовать такому стилю и тебе.

203. Строчные теги, вложенность и


комментарии
В предыдущей лекции мы уже рассмотрели самые распространенные HTML теги и
ты смог потренироваться их использовать. Все изученные теги объединяет
то, что они являются блочными и каждый начинается с новой строки, а после
нее все элементы тоже отображаются с новой строки.
А что если нам нужно, чтобы элементы размещались в одну строку? Для этого
можно использовать строчные элементы.

Строчные элементы

Зачастую строчные элементы используются для форматирования текста,


акцентирования внимания на нем и так . Одними из очень широко
распространенных строчных тегов являются <b> и <i>. Первый тег делает
текст жирным, второй — наклонным. Оба тега являются парными, а значит
изменяют они именно тот текст, который размещен между открывающим и
закрывающим тегами:
<b>Этот текст будет жирным</b>
<i>А этот текст будет отображен курсивом</i>
Здесь вроде все понятно. Но ведь этот код кроме самих тегов ничем не
отличается от того, что мы изучали ранее.

Все дело в том, что строчные теги можно использовать внутри блочных не
нарушая структуру. На деле это выглядит следующим образом:
<p>Это параграф, где одно слово будет выделено <b>жирным</b>
шрифтом</p>
<p>А в этом параграфе одно слово будет написано <i>курсивом</i></p>
В данном примере структура построения HTML страницы никак не будет
отличаться с использованием или без тегов <b> и <i>. При этом
теги <b> и <i> называются вложенными по отношению к тегу <p>, внутри
которого каждый используется, а теги <p> называются родительскими.

Вложенные теги

Делать вложенными можно не только строчные теги. С легкостью можно


создать использовать тег <div> для объединения двух параграфов. Например:
<div>
<p>Первые вложенный параграф.</p>
<p>А это второй вложенный параграф.</p>
</div>

Несколько блочных элементов можно разместить внутри другого блочного


элемента — все зависит от структуры, которую ты задумал.
Но помни — не все теги можно вкладывать в другие. Например, тег
заголовка <h1> нельзя вкладывать в тег параграфа <p>. Но обо всем
постепенно — правила вложенности мы будем изучать позже.

А можно ли сделать текст, отображаемый курсивом, жирным? Конечно, это


всего лишь очередная вложенность тегов:
<div>
<p>Первый вложенный параграф.</p>
<p>А это второй вложенный параграф. <b><i>Этот текст будет и жирным,
и написанный курсивом</i></b>
</p>
</div>
Как думаешь, а можно ли поменять местами теги <b> и <i> местами и
сделать <i> - родительским тегом?

Комментарии в HTML

Иногда бывают моменты, когда тебе необходимо добавить комментарии в HTML


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

Или ты просто хочешь оставить себе напоминание, о том, что должно быть
сделано.

Причин для таких сообщений довольно много, но все эти сообщения


объединяет то, что они не должны отображаться на странице, а видны только
в редакторе кода, который ты используешь.

Для комментариев не нужны специальные теги, но их нужно как-то обозначить


чтобы было понятно, где начало комментария и где его окончание.
Простейший комментарий состоит из одной строки. Текст нужно разместить
между последовательностью символов <!-- и -->:
<!-- Это комментарий. Он не будет виден пользователям-->
<h1>Главный заголовок</h1>
<h2>Подзаголовок</h2>
Если твой комментарий не помещается в одну строку, то его можно сделать
многострочным, чтобы было легче читать. В отличие от JavaScript, тебе не
нужны новые комбинации символов. Используй те же <!-- и —>, но добавь
переносы строки в тех местах, где это нужно:
<!-- А это многострочный комментарий
Ничего дополнительного делать не надо
просто перенос на новую строку
-->
<h1>Главный заголовок</h1>
<h2>Подзаголовок</h2>

204. Ссылки и атрибуты


— Привет! Ну как, ты уже разобрался с базовыми HTML элементами?
— Вроде да, понял, что у каждого тега свои возможности и использовать их
нужно в разных ситуациях.
— Все верно. Сегодня расскажу тебе об HTML атрибутах.
— А зачем они нужны?

Атрибуты

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

Указывается атрибут сразу после имени тега:


<p align="center">Этот текст будет выровнен по центру.</p>
<hr color="red">
<p align="right">Этот текст будет выровнен по правому краю.</p>
Ты уже знаком с тегами <p> и <hr>. Разберем добавленные атрибуты:
o <p align="center"> - атрибут align со значением center - добавит тексту
внутри параграфа выравнивание по центру
o <hr color="red"> - атрибут color со значением red - изменит цвет
горизонтального разделителя на красный
o <p align="right"> - атрибут align со значением right - добавит тексту
внутри параграфа выравнивание по правой стороне
Множественные атрибуты

Если нам нужно, то мы можем добавить одному HTML тегу несколько


атрибутов. В таком случае они указываются через пробел. Например:
<!-- Несколько атрибутов -->
<h1>JavaScript Test</h1>
<img src="https://learn.coderslang.com/js-test-13.png" width="100"
alt="Первая картинка">
Тут, мы добавляем картинку используя новый для тебя тег img. Картинка
может быть в разных форматах, например GIF, JPEG или PNG.
o Путь к картинке мы добавляем с помощью атрибута src, где значение —
адрес.
o Атрибут alt устанавливает альтернативный текст для изображения. Этот
текст отображается, пока/если картинка недоступна.
o Атрибут width задает ширину изображения. В нашем примере, мы ограничиваем
максимальную ширину - 100 пикселей. Если картинка окажется шире, то она
будет уменьшена с сохранением пропорций.
— А высоту картинки можно менять?
— Высоту можно поменять с помощью атрибута height. Он работает точно так
же, как width, но ограничивает максимальную высоту вместо ширины.
Будь осторожен, если ты одновременно задашь параметры width и height, то
могут нарушиться пропорции изображения.
Также, высоту и ширину можно указать в процентах. Для этого просто
добавить знак % к значению атрибута, чтобы получилось что-то вроде
<img src="https://learn.coderslang.com/js-test-13.png" height="50%"
alt="Первая картинка">
Проценты — это не уменьшение размеров картинки, а ограничение размера
изображения относительно родительского контейнера, например <div>. Или,
окна браузера, если внешний <div> отсутствует.
— То есть я могу еще и менять размеры <div>?

— Можешь, но все по порядку. Пока тренируемся на картинках, а с


вложенностью разберемся позже.
Ссылки

Чтобы перейти с одной веб страницы на другую, существуют ссылки. В HTML


они описываются тегом <a>. Это — парный тег.
<a>Первая ссылка</a>

Но работать такая ссылка не будет.


— Конечно, ты же не указала путь для перехода.
— Для того, чтобы браузер понимал какую страницу нужно открыть по клику
на ссылку, тегу <a> нужно добавить атрибут href.
<a href="https://learn.coderslang.com/">Первая ссылка</a>
Значение атрибута href - веб адрес по которому нужно сделать переход.
Содержимое тега <a> по умолчанию становится синего цвета и
подчеркивается. Если браузеру знаком тип файла (например, файл с
расширением .html), то этот файл будет открыт в текущем окне, иначе будет
показано сообщение с просьбой выбрать вариант работы с таким файлом.
Адрес ссылки бывает абсолютным и относительным. Абсолютные ссылки обычно
применяются для указания документа на другом сетевом ресурсе.
<a href="https://www.google.com/">Открыть Google</a>
В этом примере браузер покажет ссылку с текстом Открыть Google, а по
клику на нее произойдет переход по адресу https://www.google.com/.

Относительные ссылки

Если ты хочешь добавить несколько ссылок на текущую веб страницу, то тебе


не нужно каждый раз печатать полный путь к ним. Достаточно добавить путь
относительно текущей страницы.
При создании относительных ссылок надо понимать, что значение для
атрибута href зависит от исходного расположения файлов. Вспоминай лекцию
о терминале и команде cd.

Представим такую структуру веб страниц нашего сайта:


|-- lecture
| |-- tests.html
| |-- tasks.html
|
|-- index.html
Если мы пишем разметку страницы index.html, и хотим добавить две ссылки —
на страницу задач и на страницу тестов, то вот как они будут выглядеть:
<a href="./lecture/tests.html">Тесты</a>
<a href="./lecture/tasks.html">Задачи</a>
А для того, чтобы со страницы tasks.html попасть на index.html, нужно
выйти на один уровень выше с помощью ..
<a href="../index.html">Домой</a>

Идентификаторы HTML элементов

Любому HTML элементу можно добавить атрибут id. Значение этого атрибута
может быть любым, но обязательно должно быть уникальным в рамках одной
веб страницы.
Атрибут <id> нужен для того, чтобы элемент можно было однозначно
идентифицировать.
<p id="p1">Первый абзац</p>
<p id="p2">Второй абзац</p>

Внутренние ссылки

И абсолютный путь, и относительный путь нужны нам для перехода на другую


веб страницу. Но бывает, что нам необходимо перейти к какому-то разделу
на текущей странице. Тогда нам нужна внутренняя ссылка или якорь. Разницы
между этими понятиями нет, ты можешь использовать любое.

Для того, чтобы создать внутреннюю ссылку, нам нужны 2 вещи:


o Добавить HTML элемент с уникальным id. На него мы и будем делать переход.
o Установить атрибут href, по формуле # + id нужного нам элемента.
<h1>Пример использования якорей</h1>
<a href="#paragraph1">Переход к параграфу 1</a>
<a href="#paragraph2">Переход к параграфу 2</a>
<p id="paragraph1">
Я произнёс всего два слова. Однако на каждое потратил изрядное
усилие. Оба слова, растянувшиеся во времени, дались мне с трудом:
челюсть налилась тяжестью, точно у бронзовой статуи, а язык казался
распухшим и бесчувственным, будто туда вкололи ультракаин.
</p>
<p id="paragraph2">
Губы Техника искривила едва приметная улыбка. Она была печальной и
одновременно понимающей. Такой, словно он улыбался так уже многим, и
я был далеко не первым в его списке. Техник поднял палец в синей
перчатке и сказал...
</p>
Чтобы разобраться, запускай VSCode, создай index.html, скопируй туда весь
пример и посмотри что получится. Потом можешь добавить еще пару абзацев и
ссылки на них.

205. Списки
— Продвигаемся и изучим новые теги.
— Я думал мы уже закончили с тегами, ведь уже даже атрибуты рассмотрели.

— Ооо нет, тегов еще огромное количество. Например, сегодня мы рассмотрим


списки и теги для их создания.
— Отлично, я готов!
Списки широко используются в HTML для группировки информации. Существует
три вида списков: маркированный, нумерованный и список определений.

Маркированный список

В маркированном списке каждый элемент выделяется каким-то маркером, по


умолчанию это закрашенный кружочек. Добавляется такой список с помощью
парного тега <ul>. А потом добавляем каждый пункт этого списка - с
помощью тега <li>.
<h2>Что я должен знать, чтобы стать Frontend разработчиком</h2>
<ul>
<li>JS</li>
<li>HTML</li>
<li>CSS</li>
</ul>
— Круто. А можно, чтобы рисовались не кружочки? Я вот хочу, например,
квадратики.

— Легко. Для этого тебе нужно воспользоваться атрибутами. Добавлять их ты


научился в прошлой лекции.
Чтобы изменить вид маркера, добавь тегу <ul> атрибут type. Для этого
атрибута может быть несколько значений:
o disc отобразит маркер в виде круга
o circle - в виде окружности
o square - квадратные метки
<h2>Что я должен знать, чтобы стать Front-end разработчиком</h2>
<ul>
<li>JS</li>
<li>HTML</li>
<li>CSS</li>
</ul>
— Ну а если я хочу вместо всяких квадратиков и кружочков добавить самые
обычные цифры? Неужели самому прийдется писать их?

— Конечно нет. Для этого тебе надо создать нумерованный список.

Нумерованный список

В нумерованном списке каждому элементу добавляется его порядковый номер.


Такой список можно создать с помощью тега <ol>. А каждый пункт такого
списка...
— Я знаю, я знаю. Создается с помощью тега <li>.
— Ты совершенно прав. Точно так же, как и со списком <ul>.
<h2>Что я должен знать, чтобы стать Frontend разработчиком</h2>
<ol>
<li>JS</li>
<li>HTML</li>
<li>CSS</li>
</ol>

У нумерованного списка тоже есть несколько атрибутов, которые помогут


тебе изменять внешний вид списка и его свойства:
<ol>
<li>reversed - задает отображение списка в обратном порядке.</li>
<li>
type - задает вид маркера для использования в списке (в виде букв
или цифр).
Например, `1` - значение по умолчанию, десятичная нумерация.
А `I` - нумерация римскими заглавными цифрами (I, II, III, IV).
</li>
<li>
start - задает начальное значение, от которого пойдет отсчет
нумерации.
Например, со значение 3 нумерация элементов списка начнется с цифры
3.
</li>
</ol>
— Стой, ты забыл сказать значение для атрибута reversed.

— Ну не совсем забыл. Просто у этого атрибута нет значения.


— Как же его тогда использовать?
— Еще легче, чем атрибуты со значением. Ты просто пишешь его имя и все.
Смотри:
<ol reversed type="I" start="10">
<li>Пункт 1</li>
<li>Пункт 2</li>
<li>Пункт 3</li>
</ol>

<!-- Результат:
X. Пункт 1
IX. Пункт 2
VIII. Пункт 3
-->

Список определений

Существует еще один вид списка. Он встречается не так часто как


предыдущие два. Называется он - список определений.
Добавляется список определений с помощью тега <dl>. Но в отличие от
списков <ol> и <ul>, элемент списка добавляется НЕ с помощью тега <li>.
Здесь каждый пункт состоит из двух элементов — термина и его определения.
Для термина используется тег <dt>, а для определения — тег <dd>.
<dl>
<dt>Термин 1</dt>
<dd>Определение термина 1</dd>
<dt>Термин 2</dt>
<dd>Определение термина 2</dd>
</dl>
— Интересно, а зачем такие списки вообще нужны?

— Список определений отлично подходит для расшифровки терминов, создания


словаря или справочника.
— Думаю я уже готов пробовать писать свои списки...
— Тогда вперед, к практическим задачам.

206. Таблицы
— Теперь разберемся с еще одним способом структурировать и группировать
информацию - с таблицами. Раньше их активно использовали для верстки веб
страниц. Но такая структура была не очень гибкой. После того, как
появилась необходимость делать адаптивный дизайн для разных размеров
экранов, от них постепенно отказались.
— Так если от них отказались, зачем тогда мне их изучать?

— Хоть таблицы и не используются для верстки самой веб страницы, они


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

Базовая структура

Для создания таблиц используется парный тег <table>. Дальше в таблицу


добавляются строки. Для этого внутрь тега <table> вкладывается парный
тег <tr>. Расшифровывается tr как table row, а row в переводе с
английского — ряд. Так наверное будет легче запомнить.
Строки в таблице состоят из ячеек. Чтобы добавить ячейку в строку, мы
должны вложить парный тег <td> внутрь <tr>.
<td>- это table data. В переводе с английского — данные таблицы. Внутри
тега <td> можно указать полезную информацию. Например, текст или число.
<table>
<tr>
<td>Моя первая ячейка</td>
</tr>
</table>
Ячейки — это основная часть таблицы. Кроме текста или числа, ты можешь
добавить в них любой другой элемент: изображения, параграфы, списки,
заголовки и даже другие таблицы.
— Выглядит довольно просто. Надо просто запомнить порядок вложенности
тегов.

— Совершенно верно.
— Но таких же таблиц в реальной практике не будет. Как мне добавить
несколько ячеек и строк?
— Для добавления несколько строк тебе нужно повторить несколько раз
тег <tr> внутри тега <table>. А для добавления нескольких ячеек — повтори
тег <td> внутри строки таблицы. Но помни - в каждой строке должно быть
одинаковое количество ячеек, для сохранения структуры таблицы.
<table>
<tr>
<td>Первая ячейка первой строки</td>
<td>Вторая ячейка первой строки</td>
</tr>
<tr>
<td>Первая ячейка второй строки</td>
<td>Вторая ячейка второй строки</td>
</tr>
</table>

Заголовок таблицы

У большинства таблиц есть заголовок. Чаще всего, там пишут имена


столбцов. Чтобы их выделить, вместо тега <td> используется
тег <th> (table header - заголовок таблицы). Заголовок отличается он тем,
что шрифт внутри него становится жирным.
<table>
<tr>
<th>Имя первой колонки</th>
<th>Имя второй колонки</th>
</tr>
<tr>
<td>Первая ячейка первой строки</td>
<td>Вторая ячейка первой строки</td>
</tr>
<tr>
<td>Первая ячейка второй строки</td>
<td>Вторая ячейка второй строки</td>
</tr>
</table>

Описание таблицы

Любой таблице можно добавить описание или подпись. Для этого используют
тег <caption>. Он тоже должен быть вложен внутрь тега <table>. Также,
тег <caption> должен быть самым первым тегом, это важно!
<table>
<caption>Описание таблицы</caption>
<tr>
<th>Колонка 1</th>
<th>Колонка 2</th>
</tr>
</table>

Объединение ячеек

Встречаются случаи, когда нужно объединить между собой соседние ячейки в


таблице. Такое объединение можно сделать и по вертикали, и по
горизонтали.
Для объединения ячеек используются два атрибута: colspan и rowspan.
Значение обоих атрибутов — это число ячеек, которые нужно объединить.
С помощью colspan объединяются ячейки по горизонтали. А с
помощью rowspan - по вертикали.
Рассмотрим использование атрибутов на примере горизонтального объединения
для строчки Итого:
<table>
<tr>
<th>Наименование</th>
<th>Кол-во</th>
<th>Цена за ед.</th>
<th>Стоимость</th>
</tr>
<tr>
<td>Карандаш</td>
<td>2</td>
<td>2.50</td>
<td>5.00</td>
</tr>
<tr>
<td>Ручка</td>
<td>4</td>
<td>3.50</td>
<td>14.00</td>
</tr>
<tr>
<td colspan="3">Итого</td>
<td>19.00</td>
</tr>
</table>
Для отображения итоговой строки нам необходимо все две колонки: в одной
описание значения, во второй — само значение. С помощью
атрибута colspan мы объединили три колонки (Наименование, Кол-во, Цена за
ед.) в одну.

Группы строк

Строки таблицы можно объединить в логические группы. Это не обязательно,


но может быть очень полезным для стилей, которые мы будем учить позже.
Итак, первая группа — это заголовок. Строки заголовка могут быть
заключены в тег <thead>.
Дальше, после заголовка, идет тело таблицы, которое состоит из строк. Для
тела используется тег <tbody>.
Для объединения строк нижней части таблицы используется тег <tfoot>.

Давай перепишем нашу смету за канцелярию с использованием новых тегов:


<table>
<thead>
<tr>
<th>Наименование</th>
<th>Кол-во</th>
<th>Цена за ед.</th>
<th>Стоимость</th>
</tr>
</thead>
<tbody>
<tr>
<td>Карандаш</td>
<td>2</td>
<td>2.50</td>
<td>5.00</td>
</tr>
<tr>
<td>Ручка</td>
<td>4</td>
<td>3.50</td>
<td>14.00</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="3">Итого</td>
<td>19.00</td>
</tr>
</tfoot>
</table>

Хотя все эти три тега использовать необязательно, лучше их все таки
добавлять.

Во-первых, ради стилей. Ты сможешь применить стили css напрямую только к


заголовку или телу таблицы, тебе не нужно будет разбираться какая строка
к чему относится — они уже разделены.

Во-вторых, ради функциональности. Представь, что тебе нужно просто


сортировать строки, скажем по цене. Но ведь у тебя есть еще строка
заголовка и, возможно, итоговая строка, которые не должны участвовать в
сортировке. И снова тебе прийдется каким-то образом разделить все эти
строки. А с использованием группировки — они уже разделены.
— Но ведь эти все случаи мы еще не рассматривали?

— Совершенно верно. Но тебе стоит научиться обращать внимание на моменты,


которые могут появиться в будущем. Это облегчит тебе работу! А пока это
все, что нужно тебе знать о таблицах на данный момент. Давай проверим,
как ты усвоил материал.
207. Структура HTML документа
В предыдущих лекциях ты узнал о самых распространенных HTML тегах. Без
заголовков, параграфов, ссылок, списков и таблиц не обходится ни одна
веб-страница.

Сегодня, я расскажу тебе о тегах, которые не так очевидны.

Тип документа

HTML появился давно и существует в нескольких версиях. Обычно, в самом


начале HTML документа указывают его тип или версию HTML, которая
используется на этом сайте. Для этого используется специальный
элемент <!DOCTYPE>. Вот пример для HTML5:
<!DOCTYPE html>
В данном примере все очень просто — мы говорим браузеру, что тип
документа - html. В более старых версиях HTML (например, HTML4) все
немного сложнее, но не будем тратить время на это.
Просто запомни, что в начале HTML документа стоит всегда добавлять
тег <!DOCTYPE> с одним атрибутом html.
Тег <html>

После типа документа, следует тег <html>, который определяет начало и


окончание HTML-файла.
Внутри контейнера <html> все можно разделить на два блока: заголовок и
тело. Заголовок определяется тегом <head>, тело — тегом <body>.

Заголовок документа

<head> - парный тег. Еще его называют контейнером для метаданных.


Метаданные — данные о данных; информация об используемых на странице
скриптах, стилях и так . Информация, указанная в <head>, на странице не
отображается. Вся, кроме тега title.
Тег <title> определяет заголовок документа. Заголовок должен быть только
текстовым.
Использование заголовка очень важно:
o он отображается на вкладке браузера

o используется как название страницы при добавлении в избранное


o отображается при поиске, например на google.com
<html>
<head>
<title>Первая страница</title>
</head>
</html>

Мета теги

Также внутри тега <head> часто можно увидеть тег <meta>.

Этот тег — одинарный, и вся работа с ним заключается в использовании


атрибутов.
— А что я могу указать с помощью тега <meta>?

— Например, автора страницы:


<meta name="author" content="Hero">
С помощью атрибута name указывается имя мета тега, что также косвенно
задает его предназначение. С помощью атрибута content устанавливается
значение мета тега, заданного с помощью name.
Также можно задать описание страницы, или ключевые слова, которые будут
помогать поисковым сервисам, таким как google, находить твой сайт при
определенных запросах:
<meta name="description" content="My first website">
<meta name="keywords" content="HTML, CSS, JavaScript, Coderslang">
Обрати внимание на метатег keywords и его атрибут content. Нам нужно было
указать несколько значений для атрибута и мы перечислили их через
запятую.

Структура нашего документа изменилась и выглядит так:


<!DOCTYPE html>
<html>
<head>
<meta name="author" content="Hero">
<meta name="description" content="My first website">
<meta name="keywords" content="HTML, CSS, JavaScript, Coderslang">
<title>Первая страница</title>
</head>
</html>

Тело документа

<body> - парный тег, который содержит все, что посетитель твоего сайта
будет видеть: все заголовки, абзацы, изображения, ссылки, таблицы, списки
и т. д. В одном документе может быть только один тег <body>.
Добавим несколько тегов в <body и получим страницу с одним абзацем и
заголовком:
<!DOCTYPE html>
<html>
<head>
<meta name="author" content="Hero">
<meta name="description" content="My first website">
<meta name="keywords" content="HTML, CSS, JavaScript, Coderslang">
<title>Первая страница</title>
</head>
<body>
<h1>Первый заголовок</h1>
<p>Первый абзац. Также в этом абзаце есть текст, написанный
<b>жирным</b> шрифтом </p>
</body>
</html>
208. Основы CSS
— Попробуем подытожить. Что ты уже знаешь и умеешь?
— Знаю уже про параграфы, заголовки, списки и таблицы. Умею все это
объединять в один документ. Но все что я делаю, не выглядит так красиво,
как я этого хочу.
— Тогда нам пора переходить к изучению стилей или CSS.
CSS расшифровывается как Cascading Style Sheets и в переводе с
английского означает каскадные таблицы стилей.

CSS отвечает за всю красоту на экране: шрифты, цвета, фон, форматирование


текста и даже анимацию. С помощью CSS можно менять положение HTML
элементов относительно друг друга или зафиксировать их в какой-то точке
экрана.

Строчные стили

Попробуем с самого простого. В базовом случае, мы можем добавить любому


HTML тегу атрибут style и внутри него применить нужные нам стили.

Представим, что у нас есть параграф и нам необходимо сделать цвет текста
красным. Вот как это можно сделать:
<p style="color: red">Этот текст - красный</p>
Такой способ задания стилей называется встроенным или строчным (inline).
Он применяется только к одному тегу. Если у нас будут другие теги <p>,
цвет текста внутри них никак не изменится.
Для того чтобы изменить цвет текста, мы использовали атрибут style со
значением color: red. Это — строка стилей. Внутри нее можно указать
множество свойств стилей, одно из них color.
Свойства, как и атрибуты, состоят из двух частей: имени и значения,
которые указываются через двоеточие (:). В данном примере имя свойства
- color, с помощью него задается цвет текста. Значение
стиля red указывает, что цвет должен быть красным.
Если мы хотим, чтобы цвет текста стал зелёным, нам надо просто поменять
значение свойства на green:
<p style="color: green">Этот текст - зеленый</p>
— А что если нам надо поменять не только цвет текста, но и размер шрифта?
— Чтобы добавить несколько свойств в строку стилей, нужно перечислить их
через точку с запятой (;). Размер шрифта можно изменить с помощью css
свойства font-size.
<p style="color: red; font-size: 16px;">Этот текст - красный. Его
размер 16px.</p>
Как и в случае с цветом, для того, чтобы изменить размер шрифта, мы
указали имя свойства (font-size), и, после двоеточия (:), добавили
значение 16px.
Размер шрифта необязательно должен быть в пикселях. Например, мы можем
привязать размер шрифта вложенного тега к размеру шрифта родительского
тега. Сделать это можно, указав размер шрифта не в px, а в %.
<p style="font-size: 16px;">
Это родительский параграф. Размер шрифта - 16px;
<b style="font-size: 50%;">А это вложенный тег, размер шрифта - 50%
от родительского. То есть 8px.</b>
</p>
Мы привязали размер шрифта для текста внутри тега <b> к размеру шрифта
родительского тега <p>. Соответственно, размер текста внутри
тега <b> будет составлять 8px, половина от 16px.

Выделение строчных элементов

Текст можно делать жирным не только с помощью тега <b>, а и с помощью css
свойства font-weight. Для этого необходимо установить его в
значение bold. Давай немного улучшим предыдущий пример:
<p style="font-size: 16px;">
Это родительский тег, размер его шрифта равен 16 px;
<span style="font-size: 50%; font-weight: bold">А это вложенные тег,
размер его шрифта указан в процентах и равен 50% от
родительского</b>
</p>
— Ой, а я не помню, чтобы рассматривали тег <span>.

— Мы действительно его не рассматривали, потому что без стилей этот тег


не особо интересен.
Тег <span> отлично подходит для выделения кусочков текста, при
использовании стилей. Сам по себе он не изменяет текст (в отличие
от <b> или <i>). Но мы можем добавить ему любые стили для форматирования
текста:
<p>
Начало параграфа. Обычный текст.
<span style="font-size: 16px; font-weight: bold; color:
blue">Выделенный текст.</span>
Продолжение параграфа.
</p>

209. Внутренние стили


— Ты уже знаешь, как можно добавить стили конкретному элементу, например
изменить цвет текста внутри тега <p>. А что если нам необходимо это
сделать это для нескольких тегов одного типа?
— Так это же легко — просто добавляем каждому параграфу атрибут style и
указываем нужное свойство.
<p style="color: red">Красный текст</p>
<p style="color: red">Еще один красный текст</p>

— Неплохо, но что если таких параграфов будет сто? А если больше? А если
кроме цвета надо будет изменить размер шрифта?
Было бы намного удобнее один раз указать стиль, который применился бы ко
всем тегам <p>, чем дублировать атрибут style в каждом из них.
Начнем с простого HTML документа без стилей.
<!DOCTYPE html>
<html>
<head>
<title>Внутренние стили</title>
</head>
<body>
<h1>Заголовок</h1>
<p>Первый параграф</p>
<p>Второй параграф</p>
</body>
</html>
Первая задача - установить размер шрифта внутри всех тегов <p> - 16px.
Чтобы избежать дублирования стилей, мы добавим парный тег <style>. Внутри
него — имя тега, и список свойств стилей которые мы хотим применить.
o начинаем с имени тега

o после него поставили две фигурные скобки

o внутри фигурных скобок — список нужных css свойств


<style>
p {
font-size: 16px;
}
</style>
— А где мы должны использовать этот тег <style>?
— Сам тег <style> мы используем внутри тега <head>. Сразу после <title>.
Тогда наш HTML-документ будет выглядеть вот так:
<!DOCTYPE html>
<html>
<head>
<title>Внутренний стиль</title>
<style>
p {
font-size: 16px;
}
</style>
</head>
<body>
<h1>Заголовок</h1>
<p>Первый параграф</p>
<p>Второй параграф</p>
</body>
</html>
Теперь размер шрифта внутри каждого параграфа будет 16px.
Вторая задача — сделать задний фон всех параграфов красным.
Для смены цвета заднего фона, мы добавим css свойство background-color.
Цвет задается так же, как и цвет шрифта:
<style>
p {
font-size: 16px;
background-color: red;
}
</style>
— А если мы хотим изменить стили заголовка, нам надо добавлять еще один
тег <style>?
— Нет. Внутри тега <style> можно добавить столько тегов, сколько нужно.
Давай попробуем изменить высоту заголовка <h1>, чтобы выделить его.
Делается это с помощью свойства height.
— Точно, я помню как с помощью атрибута height мы задавали высоту
изображения.
<style>
h1 {
height: 40px;
}
p {
font-size: 16px;
background-color: red;
}
</style>
Как видишь, мы просто добавили имя тега h1 и указали стили для него точно
так же, как и для параграфа. Кстати, похожим образом можно добавить и
ширину. Только теперь нужно использовать свойство width.
Третья задача - установить ширину всех параграфов 500px:
<style>
h1 {
height: 40px;
}
p {
width: 500px;
font-size: 16px;
background-color: red;
}
</style>
Если мы добавим еще один параграф на нашу веб-страницу, к нему сразу же
применятся все стили описанные внутри <style>.
<!DOCTYPE html>
<html>
<head>
<title>Внутренний стиль</title>
<style>
p {
font-size: 16px;
}
</style>
</head>
<body>
<h1>Заголовок</h1>
<p>Первый параграф</p>
<p>Второй параграф</p>
<hr>
<p>
Длинный параграф, который мы добавили только что.
В нем будут все те же стили, что и в предыдущих двух.
Нет дублирования кода, что очень приятно :).
</p>
</body>
</html>
Как и шрифт, свойства height и width можно не просто задавать в пикселях,
но и относительно высоты родительского компонента. На это будет отдельная
задача.

210. Внешние стили


С помощью тега <style> можно добавить стили для одного HTML-документа.
Это — внутренние стили. Они будут применены ко всему содержимому
страницы. Но только одной страницы.

На практике тебе прийдется создавать сотни HTML-документов и использовать


одни и те же стили в них, например, для параграфов или заголовков. Было
бы очень удобно добавить стили один раз для всех похожих страниц.

Например, ты создаешь свой блог. Все страницы с постами должны быть


стилизованы одинаково.
<!DOCTYPE html>
<html>
<head>
<title>Мой личный блог</title>
</head>
<body>
<h1>Пост о обучении HTML и CSS</h1>
<h6>Дата публикации: 20.12.2020</h6>
<p>Мне очень нравится учить HTML верстку!</p>
</body>
</html>
Я уверен, что ты с легкостью сможешь добавить стили для
элементов <h1>, <h6> и <p> на странице с помощью внутренних стилей. Но
наша сегодняшняя цель — создать такие стили, которые можно будет
использовать и на других страницах.

На всех страницах блога заголовки и параграфы должны выглядеть одинаково.


— Наверное нам надо вынести все эти стили в какой-нибудь отдельный файл?
— Верно, все стили выносятся в отдельный файл с расширением .css. Назвать
его можно как угодно. Часто используют имя styles.css.
Правила написания стилей в нем точно такие же, как и для стилей в
теге <style>:
o сначала указываем имя тега

o потом в фигурных скобках перечисляем свойства со значениями


Начнем с выравнивания <h1> заголовков по-середине страницы. Для этого
можно использовать свойство стилей text-align, со значением center.
Создадим файл styles.css и добавим в него 3 строки:
h1 {
text-align: center;
}

Подключение внешних стилей

После создания файла styles.css, его нужно подключить к HTML документу.


Представим такую структуру документов нашего сайта:
|--my-blog
|-- post-1.html
|-- styles.css
Для подключения файла styles.css к файлу post-1.html нам нужен одинарный
тег <link>.
Стили всегда размещаются внутри контейнера <head>.
Для подключения стилей нам необходимо выполнить несколько действий с
тегом <link>:
o добавляем атрибут type со значением text/css.
o добавляем атрибут rel (расшифровывается как relationship - отношение) со
значением stylesheet.
o добавляем атрибут href, чтобы указать путь к файлу стилей.
Вот так будет выглядеть post-1.html с подключенными стилями
из styles.css:
<!DOCTYPE html>
<html>
<head>
<title>Мой личный блог</title>
<link rel="stylesheet" type="text/css" href="./styles.css">
</head>
<body>
<h1>Пост о обучении HTML и CSS</h1>
<h6>Дата публикации: 20.12.2020</h6>
<p>Мне кажется, что я делаю потрясающий прогресс в изучении
верстки</p>
</body>
</html>
Комментарии

Как и в HTML-документы, в CSS файлы можно добавлять комментарии. Может ты


хочешь оставить заметку для себя, а может хочешь помочь коллегам
разобраться в каких-то сложных моментах.
В HTML, комментарием считается любой текст между <!-- и -->. В CSS файлах
эти символы заменяются на /* и */ соответственно. Например:
/* Это комментарий к стилям заголовка */
h1 {
color: red;
}
Интересной особенностью тега style является то, что внутри него
комментарии добавляются точно так же, как и в файле CSS, несмотря на то,
что пишем мы его в документе html:
<head>
<title>Мой личный блог</title>
<!-- HTML комментарий -->
<style>
/* CSS комментарий */
h1 {
color: red;
}
</style>
<!-- Снова HTML комментарий -->
</head>

211. Идентификаторы и классы


— Способ, которым мы выбираем элементы для применения стилей называется
селектор (с англ. selector). До сих пор для добавления стилей мы выбирали
элементы по имени тега.
— Ну да, мы указывали, например, h1 или p и дальше перечисляли свойства
CSS. А что, есть другой способ?

— Иногда, нам нужно сделать разные стили для одного и того же тега.
Например, часть параграфов нужно красить в красный цвет, а другую часть -
в черный. Для этого селектор по имени тега нам не подойдет и нужно будет
использовать другой подход.

Идентификаторы

Когда нужно выделить один элемент из всех, ему можно добавить


идентификатор с помощью атрибута id.
У идентификатора есть важная особенность. Он должен быть уникальным в
рамках всего HTML документа. То есть значение атрибута id должно
использоваться только один раз.
Попробуем добавить уникальный id второму параграфу.
<p>Параграф 1</p>
<p id="unique-paragraph">Параграф 2</p>
<p>Параграф 3</p>
<p>Параграф 4</p>

Теперь нам надо выбрать этот параграф для применения стилей.


— Думаю для этого нам достаточно вместо имени тега p использовать unique-
paragraph.
— Не совсем. Если ты попробуешь добавить стили таким образом, то браузер
будет думать, что тебе нужен тег с именем unique-paragraph, которого не
существует.
Для того, чтобы селектор по id работал правильно, мы должны добавить
знак # перед идентификатором.
<style>
#unique-paragraph {
color: red;
}
</style>
Такой стиль будет применен только к тегу с id="unique-paragraph" и
изменим его цвет на красный.

Классы

— Но чаще тебе прийдется группировать разные элементы для применения


стилей.
— Выделить, например, не один параграф, а два?
— Да. Давай те же четыре параграфа разобьем на две группы — четные и
нечетные. Для этого каждому элементу нужно присвоить класс с помощью
атрибута class.
В отличие от идентификатора, один и тот же класс может использовать для
неограниченного количества элементов, а элементы будут объединены в
группы по значению атрибута class.
Для выполнения задания нам нужны будут два класса - odd (в переводе с
англ. - нечетные) и even (в переводе с англ. четные):
<p class="odd">Параграф 1</p>
<p class="even">Параграф 2</p>
<p class="odd">Параграф 3</p>
<p class="even">Параграф 4</p>
Селектор для классов немного отличается он идентификатора. Вместо
символа # надо указать точку ., а значение атрибута class или просто имя
css класса.
У нас будет два класса и два селектора: .odd и .even.

Давай у четных параграфов сделаем цвет текста зеленым, а у нечетных -


красным:
<style>
.odd {
color: red;
}
.even {
color: green;
}
</style>

Одному и тому же тегу можно добавить несколько классов.

Например, для двух средних параграфов нам надо сделать шрифт 18px. Так
как они относятся к разным классам, то мы не можем просто изменить стили.
Нужно добавить еще один класс.
Добавление мы делаем в атрибуте class, разделяя разные классы пробелами.
<p class="odd">Параграф 1</p>
<p class="even large-font">Параграф 2</p>
<p class="odd large-font">Параграф 3</p>
<p class="even">Параграф 4</p>

И добавим еще один селектор в стили:


<style>
.odd {
color: red;
}
.even {
color: green;
}
.large-font {
font-size: 18px;
}
</style>
— А раз у элемента может быть несколько классов, то можно ли делать
селектор по нескольким классам?

— Можно. Для этого два селектора по имени класса нужно перечислить


подряд. Без пробелов, запятых или других знаков препинания.
Если нужно добавить стили для всех HTML элементов, которые одновременно
относятся к классам even и large-font, то селектор будет таким:
<style>
.even.large-font {
text-align: right;
}
</style>

Внутри селектора мы добавили выравнивание текста по правому краю.


— Понятно.
— В предыдущих примерах мы группировали с помощью классов только
однотипные теги. Но мы вполне можем объединить и разные теги в одну
группу с помощью одного css класса. Например, добавим класс red-
text одновременно и параграфу и заголовку:
<h1 class="red-text">Заголовок 1</h1>
<p class="red-text">Параграф 1</p>
<h1>Заголовок 2</h1>
<p>Параграф 1</p>

После этого, в стилях можно будет изменить цвет всех HTML элементов этого
класса.

Есть еще огромное количество правил для селекторов, но о них немного


позже. Сейчас самое время закрепить полученные знания на практике.

212. Свойства текста


Мы уже рассмотрели с тобой несколько css свойств для работы с текстом.
Например, свойство color используется, чтобы изменить цвет текста.

Цвет

Значение для свойства color можно задать тремя основными способами:


o по названию цвета: red, green, blue
o по шестнадцатеричному значению (hexadecimal number):
от #000000 до #ffffff
o по коду цвета в RGB палитре: от rgb(0, 0, 0) до rgb(255, 255, 255)

С названием цвета все относительно просто. Единственное ограничение в


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

В стандартном наборе HTML 140 цветов и если тебе нужен будет какой-то
цвет, который не представлен там, то нужно будет использовать
шестнадцатеричный формат (HEX код) или RGB.
HEX код всегда начинается с символа # за которым следует трехбайтовое
шестнадцатеричное число (всего 6 цифр). Каждый байт отвечает за
интенсивность одного из трех основных цветов: первый отвечает за
интенсивность красного цвета, второй — зеленого, третий — синего:

Значение 00 - самая низкая интенсивность, FF - самая высокая. Например,


для получения белого цвета нужно "смешать" три основных цвета с
максимальной интенсивностью: #FFFFFF. Для получения черного цвета,
соответственно, интенсивность всех цветом должна быть
минимальной: #000000.
— Так это же как на уроках рисования — для получения любого цвета нужно
просто смешать красный, синий и зеленый.
— Точно. Для получения красного цвета нужно байту, отвечающий за красный
цвет установить максимальное значение, а для остальных —
минимальное: ##FF0000.
— Тогда для зеленого HEX код будет таким: #00FF00.
В RGB палитре цвет добавляется по такому же, принципу: задается
интенсивность красного (red), зеленого (green) и синего (blue) цветов. От
сюда и аббревиатура RGB. Только интенсивность указываться здесь числами 0
от 225, где 0 - самая низкая интенсивность, 225 - самая высокая. Для
примера, зеленый цвет будет выглядеть следующим образом: rgb(0, 225, 0);
Все эти значения подходят и для других ситуаций, где нужен цвет, например
в background-color.

Выравнивание

Для выравнивания текста предназначено CSS свойство text-align. С его


помощью можно выравнивать текст:
o left - по левому краю (вариант по умолчанию)
o right - по правому краю
o center - по центру
o justify - растягивать на всю ширину элемента

На практике все варианты используются так:


<p style="text-align: left;">Левое выравнивание</p>
<p style="text-align: right;">Правое выравнивание</p>
<p style="text-align: center;">Выравнивание по центру</p>
<p style="text-align: justify;">Выравнивание по ширине</p>

Украшение (decoration)

Текст можно украсить или сделать акцент на какой-то его части, например,
с помощью подчеркивания. В CSS за это отвечает свойство text-decoration:
<p style="text-decoration: none;">Никак эффектов не будет. Значение
по умолчанию</p>
<p style="text-decoration: underline;">Текст будет подчеркнут</p>
<p style="text-decoration: overline;">Над текстом будет нарисована
линия</p>
<p style="text-decoration: line-through;">Текст будет
перечеркнут</p>
Также можно задать форму или цвет линии, дополнительно к ее типу.
Рассмотрим на примере underline:
<p style="text-decoration: underline solid;">Текст будет подчеркнут
одной линией. Значение по умолчанию/p>
<p style="text-decoration: underline dotted;">Текст будет подчеркнут
точками</p>
<p style="text-decoration: underline dotted red;">Текст будет
подчеркнут красными точками</p>
<p style="text-decoration: underline double blue;">Текст будет
подчеркнут двумя синими линиями</p>
<p style="text-decoration: underline dashed rgb(0, 0, 0);">Текст
будет подчеркнут черными штрихами</p>
<p style="text-decoration: underline wavy #000000;">Текст будет
подчеркнут черной волнистой линией</p>
Форму и цвет линии также можно задать отдельными свойствами - text-
decoration-style и text-decoration-color:
<p
style="
text-decoration: underline;
text-decoration-style: dashed;
text-decoration-color: rgb(0, 0, 0);
"
>
Текст будет подчеркнут черными штрихами
</p>

Отступ первой строки

У параграфа или абзаца надо добавлять отступ первой строки. Например,


когда пишем статью или книгу. В CSS для этого служить свойство text-
indent:
<p style="text-indent: 100px">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed
vehicula pretium urna, ut ornare odio luctus a. Sed laoreet, velit
nec luctus pharetra, eros est condimentum urna, hendrerit malesuada
sem mi ac lectus.
</p>

Если задать отступ в процентах, то высчитываться он будет относительно


ширины HTML элемента.
<p style="text-indent: 30%">
Proin at risus vel lectus pretium ultrices. Integer et orci
condimentum, viverra tortor a, vehicula orci. Quisque pretium, nulla
et dapibus tempor, tellus felis mollis mi, bibendum malesuada lacus
ex in magna. Mauris rhoncus id massa ac viverra. Ut ante ligula,
dignissim pellentesque gravida at, condimentum a erat. In dignissim
consequat mollis.
</p>

Трансформация текста

С помощью свойства text-transform можно управлять размером (регистром)


букв. Всего может быть четыре значения:
o none - без изменений
o capitalize - все слова с большой буквы
o uppercase - все в верхнем регистре
o lowercase - все в нижнем регистре
<p style="text-transform: none;">Все буквы будут отображаться так,
как введены разработчиком. Значение по умолчанию</p>
<p style="text-transform: capitalize;">Все слова будут отображаться
с заглавной буква</p>
<p style="text-transform: uppercase;">Все буквы всех слов будут в
верхнем регистре</p>
<p style="text-transform: lowercase;">Все буквы всех слов будут
отображаться в нижнем регистре</p>
Расстояние между символами

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


свойство letter-spacing.
Значение по умолчанию - normal. В таком случае браузер все сам сделает за
тебя и в зависимости от самого шрифта и его размера задаст расстояние
между символами.

Если ты хочешь все контролировать сам, то можешь явно указать количество


пикселей между символами:
<p style="letter-spacing: 20px;">Текст с расстоянием 20 пикселей
между символами</p>

Расстояние между символами можно сделать отрицательным. В большинстве


случаев это сделает текст нечитаемым:
<p style="letter-spacing: -4px;">Текст, который очень трудно
прочитать.</p>

Расстояние между словами

Точно так же, как задается расстояние между символами, можно задать и
расстояние между словами. Отличие будет только в имени свойства - word-
spacing. Все остальные правила сохраняются:
<p style="word-spacing: 200px;">Текст с огромным расстоянием между
словами</p>
ВАЖНО! Если мы уже добавили свойство text-align: justify, то расстояние
между словами будет определяться браузером, но будет не меньше, чем
значение word-spacing:
<p style="text-align: justify; word-spacing: 20px;">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed
vehicula pretium urna, ut ornare odio luctus a. Sed laoreet, velit
nec luctus pharetra, eros est condimentum urna, hendrerit malesuada
sem mi ac lectus. Proin at risus vel lectus pretium ultrices.
Integer et orci condimentum, viverra tortor a, vehicula orci.
Quisque pretium, nulla et dapibus tempor, tellus felis mollis mi,
bibendum malesuada lacus ex in magna. Mauris rhoncus id massa ac
viverra. Ut ante ligula, dignissim pellentesque gravida at,
condimentum a erat. In dignissim consequat mollis.
</p>

213. Свойства шрифта


С помощью свойств шрифта можно изменять сам шрифт, его стиль, вес,
вариант, высоту линии. У каждого шрифта есть своем имя, и все эти имена
можно разделить на две группы: имя семейства (family name) и общее
семейство (generic family).
— Times New Roman - это семейство, правильно?
— Совершенно верно. Еще парочка известных семейств, о которых ты возможно
слышал: Verdana, Helvetica, Arial, Open Sans. При этом каждый из шрифтов
относится к какому-нибудь общему семейству. Например, Times New
Roman относится к общему семейству шрифтов serif,
а Verdana, Helvetica, Arial - к общему семейству sans-serif.

Выбор шрифта

Для выбора шрифта используется свойство font-family. Значение этого


свойства — это список шрифтов, которые перечисляются через запятую.
— А зачем нам нужно перечислять несколько шрифтов?

— Это нужно делать, потому что того шрифта, который ты хочешь


использовать, попросту может не быть у пользователя. Если браузер не
может найти первый указанный шрифт, он пробуем загрузить второй, потом
третий и так .
<p style="font-family: Verdana, Helvetica, sans-serif;">Текст
параграфа</p>

Список шрифтов рекомендую всегда заканчивать нужным общим семейством,


чтобы браузер наверняка загрузил тот шрифт, который ты ожидаешь.
Учти, если название шрифта состоит из нескольких слов, обязательно
заключай его в кавычки:
<p style="font-family: 'Times New Roman', serif;">Текст
параграфа</p>

Размер шрифта

Мы уже использовали это свойство, поэтому просто напомню, как его


использовать:
<p style="font-size: 18px;">Текст параграфа</p>

На самом деле есть очень много вещей, который стоит знать про размер
шрифта в CSS, но их рассмотрим в следующих лекциях.

Стиль шрифта

Для изменения стиля шрифта используется свойство font-style. Но значений


у него не так уж и много: normal - значение по умолчанию, которое никак
не изменяет шрифт; italic - браузер будет отображать шрифт под
наклоном, oblique - браузер будет отображать косой шрифт.
<p style="font-style: italic;">Текст параграфа</p>
— Так, погоди. А какая разница между italic и oblique?
— У всех шрифтов есть обычная версия normal, но версия под
наклоном italic специально создается дизайнером шрифтов - в ней буквы
отличаются от нормальной версии для сглаживания.
Если у шрифта нет версии italic, но нужен курсив, придется
использовать oblique. В этом случае браузер просто наклоняет вправо буквы
нормального шрифта.

Насыщенность шрифта

С помощью свойства font-weight можно регулировать насыщенность шрифта,


или, если так можно сказать, его жирность. Указывать можно значение от
100 до 900 с шагом 100. Чем меньше значение, тем шрифт будет тоньше, чем
выше — тем он будет насыщение, жирнее.
<p>Попробуем два значения: <span style="font-weight:
200">поменьше</span> и <span style="font-weight:
800">побольше</span></p>
— А какое значение font-weight по умолчанию?

— Значение по умолчанию 400. Это соответствует нормальному начертанию


шрифта.

Подключение шрифтов

Часто, шрифт, который ты хочешь использовать на своем сайте, не


установлен у пользователя в браузере.
Для того чтобы все тексты отображались как задумано, нужно подключить
шрифт с помощью правила @font-face. В этом правиле тебе нужно будет
указать название имя семейства с помощью свойства font-family и путь к
самому шрифту с помощью свойства src. Путь может быть как относительным,
так и абсолютным.

Структура документов сайта будет такой:


|-- index.js
|-- styles.css
|-- fonts
| |-- open-sans-regular.woff2
| |-- open-sans-regular.woff
Подключать наши шрифты будем в файле styles.css:
@font-face {
font-family: 'Open Sans';
src: url('../fonts/open-sans-regular.woff2') format('woff2'),
url('../fonts/open-sans-regular.woff') format('woff')
}
Шрифты могут быть различных форматов: .ttf, .woff, .woff2, .eot, .svg.
Разные браузеры поддерживают разные форматы шрифтов, но все современные
браузеры работают с форматами .woff и .woff2. Обрати внимание, что кроме
пути к шрифту, указывается еще и его формат, который помогает браузеру
определить тип загружаемого файла. Ниже приведена таблицы соответствия
расширения файла шрифтов, формата и браузеров:

Расширение
Формат Браузеры
файла

.ttf Truetype Safari, Android, iOS

.woff Woff Современные браузеры

.woff2 woff2 Супер современные браузеры

embedded-
.eot IE6-IE8
opentype

.svg Svg legacy iOS

Теперь ты можешь использовать шрифт Open Sans в стилях:


p {
font-family: "Open Sans", serif;
}
Для каждого шрифта обычно существуют разные файлы для различных
комбинаций насыщенности и стиля. Каждый такой файл нужно подключать
отдельно, и всем им указывать значения свойства font-weight и font-style.
Мы уже добавили правило для шрифта с насыщенностью 400 и стилем normal.
Теперь добавим один шрифт с насыщенностью 600 и второй — под наклоном с
насыщенностью 800:
|-- index.js
|-- styles.css
|-- fonts
| |-- open-sans-regular.woff2
| |-- open-sans-regular.woff
| |-- open-sans-semi-bold.woff2
| |-- open-sans-semi-bold.woff
| |-- open-sans-extra-bold-italic.woff2
| |-- open-sans-extra-bold-italic.woff

/* обычные шрифт с насыщенностью 400 и стилем normal */


@font-face {
font-family: 'Open Sans';
src: url('../fonts/open-sans-regular.woff2') format('woff2'),
url('../fonts/open-sans-regular.woff') format('woff')
}

/* шрифт с насыщенностью 600 и стилем normal */


@font-face {
font-family: 'Open Sans';
font-weight: 600;
font-style: normal;
src: url('../fonts/open-sans-semi-bold.woff2') format('woff2'),
url('../fonts/open-sans-semi-bold.woff') format('woff')
}

/* шрифт с насыщенностью 800 и стилем italic */


@font-face {
font-family: 'Open Sans';
font-weight: 800;
font-style: italic;
src: url('../fonts/open-sans-extra-bold-italic.woff2')
format('woff2'),
url('../fonts/open-sans-extra-bold-italic.woff') format('woff')
}

Теперь для использования соответствующего шрифта тебе нужно просто


указать свойство font-family, насыщенность и стиль:
p {
font-family: "Open Sans", serif;
}
div {
font-family: "Open Sans", serif;
font-weight: 600;
}
h1 {
font-family: 'Open Sans', serif;
font-weight: 800;
font-style: italic;
}
Одни и те же шрифты используются на разных страницах сайта, поэтому их
подключение стоит вынести в отдельный файл fonts.css. Подключить его
лучше самым первым в HTML документе. Таким образом, мы сможем
использовать стили шрифтов в других CSS файлах:
<!DOCTYPE html>
<html>
<head>
<title>Порядок подключения</title>
<link rel="stylesheet" type="text/css" href="./fonts.css">
<link rel="stylesheet" type="text/css" href="./other-styles-1.css">
<link rel="stylesheet" type="text/css" href="./other-styles-2.css">
</head>
</html>

214. Задний фон


— Ты уже использовал свойство background-color для того, чтобы добавить
задний фон элементу.
Но кроме трех основных вариантов есть еще один способ изменить цвет.
Любой элемент можно сделать прозрачным. Чтобы это сделать —
добавь background-color со значением transparent.
<div style="background-color: red">
Это блок с красным задним фоном
<p style="background-color: green">У этого параграфа задний фон -
зеленый</p>
<p style="background-color: transparent">
У этого параграфа указан прозрачный задний фон.
Но на самом деле мы увидим красный задний фон,
потому что параграф находится внутри блока с красным фоном
</p>
</div>

Изображение заднего фона

В качестве заднего фона можно использовать не только цвет, но и картинку.


Для этого используется свойство background-image.
В качестве значения применяется специальная конструкция, которая включает
в себя адрес картинки: url(путь_к_изображению).

Как и в случае со ссылками, путь может быть как абсолютным, так и


относительным. Давай рассмотрим на примере абсолютного пути:
<div style="background-image:
url(https://learn.coderslang.com/lecture-background-image.jpg);">
Блок с изображением на заднем фоне.
В качестве картинки используется ресурс на стороннем сервере.
</div>

А так будет выглядеть относительный путь:


<div style="background-image: url(`../images/background.jpg`);">
Блок с изображением на заднем фоне.
Файл с картинкой находится в папке images.
</div>
В качестве картинки используется файл background.jpg в папке images.
Повторение заднего фона

Когда мы разрабатываем сайт, то не знаем на каком устройстве пользователь


будет его просматривать. Это может быть большой монитор, а может быть и
экран телефона.
Если изображение заднего фона оказывается меньше чем размер экрана, то
можно сделать так, чтобы оно повторялось с помощью свойства background-
repeat. Варианты значений:
o repeat - это значение по умолчанию. Изображение повторяется и по
вертикали и по горизонтали, если размеры изображения не позволяют закрыть
весь элемент. Последнее изображение в строке или вся последняя строка
будет обрезаны, если элемент закончился, но изображение не влазит.
o repeat-x - изображение будет повторяться только по горизонтали.
o repeat-y - изображение будет повторяться только по вертикали.
o no-repeat - изображение не будет повторяться и будет показано только один
раз.
o space - изображение повторяется и по вертикали и по горизонтали. В
отличие от repeat, края не будут обрезаться. Вместо этого изображения
будут равномерно распределятся по вертикали и горизонтали, чтобы
заполнить все пространство и будут добавлены отступы между картинками.
<div style="
background-image: url(https://learn.coderslang.com/lecture-
background-image.jpg);
background-repeat: space;
">
Блок с изображением на заднем фоне
</div>

Положение заднего фона

Изначально изображение заднего фона выводится в верхнем левом углу (top


left) элемента. Но с помощью background-position это поведение можно
изменить.
Для управления вертикальным положением используется
значения top, center, bottom, а для горизонтального положения
- left, center, right. В следующем примере изображение будет сдвинуто в
нижний правый угол элемента:
div {
background-image: url(https://learn.coderslang.com/lecture-
background-image.jpg);
background-position: bottom right;
}

Еще ты сам можешь указать положение заднего фона в пикселях или


процентах. Для этого надо указывать два значения:
o первое отвечает за положение по горизонтали

o второе — за положение по вертикали


div {
background-image: url(https://learn.coderslang.com/lecture-
background-image.jpg);
background-position: 25% 75%;
}

Размер заднего фона

— А если я хочу, чтобы изображение на заднем фоне не повторялось, а


растягивалось на весь элемент?
— Для этого тебе надо использовать свойство background-size. С помощью
него ты можешь задать размеры каждой из сторон отдельно (в px или %) или
указать общее правило изменение размера.
Начальное значение этого свойства — auto, которое означает, что
изображение будет отображено в своем исходном размере.
Если ты знаешь точно, каких размеров должно быть изображение, можешь
использовать вариант с px. Нужно указать два значения через пробел:
o первое будет отвечать за ширину

o второе — за высоту изображения


div {
background-image: url(https://learn.coderslang.com/lecture-
background-image.jpg);
background-size: 100px 30px;
}
Если ты хочешь просто растянуть изображение и по высоте, и по ширине до
размеров блока, то тебе надо использовать значение cover. При этом
изображение может быть обрезано справа или снизу, если будет превышать в
каком-то направлении размеры элемента.
Чтобы не обрезать изображение, можно использовать значение contain -
изображение тоже растягивается по высоте и ширине, но до тех пор, пока в
одном из направлений, изображение полностью не займет элемент. Например,
если по высоте изображение уже полностью заполнило блок, то оно
перестанет увеличиваться и по ширине, даже если блок будет не полностью
закрыт в горизонтальном направлении.

215. Границы
Граница (англ. border) - это линия, которая рисуется по краю элемента.
Для ее добавления используется свойство border.

С помощью этого свойства ты можешь указать сразу толщину этой линии, ее


стиль и цвет. Например:
<div style="border: 1px solid red">
Блок с границей красного цвета и толщиной 1px
</div>

Вокруг блока будет нарисована сплошная красная линия толщиной 1px.


Если ты добавляешь одно свойство border, то важен порядок элементов:
o На первом месте всегда находится толщина линии

o На втором — стиль
o На третьем — цвет
Также, каждое из трех значений свойства border можно задать и с помощью
трех разных отдельных свойств: border-width, border-style и border-color.
Давай про каждое значение и поговорим.

Стиль линии

По умолчанию, стиль линии границы установлен в значение none. Это


означает, что границы не будет.

Поэтому, если ты хочешь чтобы граница была, тебе нужно явно указать
стиль. Возможные варианты:
o solid - сплошная линия
o double - двойная сплошная линия
o dotted - пунктирная линия в точку
o dashed - штрих-пунктирная линия
o groove - трехмерная рифленая граница (эффект зависит от выбранного цвета
границы)
o ridge - трехмерная гребенчатая граница (эффект зависит от выбранного
цвета границы)
o inset и outset - выгнутая внутрь или наружу 3d границы (эффект зависит от
выбранного цвета границы)
<style>
div {
border: 1px red;
}
</style>

<div style="border-style: solid;">Блок с границей solid</div>


<div style="border-style: double;">Блок с границей double</div>
<div style="border-style: dotted;">Блок с границей dotted</div>
<div style="border-style: dashed;">Блок с границей dashed</div>
<div style="border-style: groove;">Блок с границей groove</div>
<div style="border-style: ridge;">Блок с границей ridge</div>
<div style="border-style: inset;">Блок с границей inset</div>
<div style="border-style: outset;">Блок с границей outset</div>

Цвет границы

Цвет границы можно указывать всеми теми же способами, что и цвет заднего
фона, включая значение transparent.
Если для указания цвета ты выберешь свойство border-color, то тогда
можешь выбрать цвет отдельно для каждой из сторон элемента.
Цвет границ должен быть обязательно перечислен в правильно порядке. По
часовой стрелке, начиная с верхней: border-color: верхняя_граница правая
нижняя левая .
<style>
div {
border: 1px solid;
border-color: red green transparent gold;
}
</style>
<div>Блок с границей</div>

Толщина линий

Как и в случае с цветом, можно установить разную толщину границы для


каждой из сторон элемента с помощью свойства border-width:
<style>
div {
border-style: solid;
border-width: 1px 5px 2px 10px;
border-color: red green transparent gold;
}
</style>
<div>Блок с границей</div>
Порядок перечисления сторон такой же, как и с цветом границы. По часовой
стрелке, начиная с верхней.

Отдельные границы

— А если мне нужна только граница снизу, прийдется все остальные делать
толщиной 0px или цвет указывать transparent?
— Нет, это не обязательно. Например, если тебе нужна только нижняя
граница, то вместо свойства border тебе стоит использовать
свойство border-bottom:
<style>
div {
border-bottom: 1px solid red;
}
</style>
<div>Блок с границей</div>
Точно также ты можешь указать стиль границы для любой из сторон: верхняя
- border-top, правая - border-right, левая - border-left.
Можно даже правило выделить - border-сторона.

Правила едины для всех. С помощью них ты можешь не только изначально


задавать стили границы, но и изменять стили для конкретной стороны, если
раньше были написаны общие стили.
<style>
div {
border: 1px solid red;
border-bottom: 2px dashed green;
}
</style>
<div>Блок с границей</div>

Главное, чтобы уточняющие стили были написаны ниже начальных.


— Обязательно писать все параметры для уточнения, даже если я хочу
изменить только цвет линии с красной на зеленую?

— Нет, точно так же существуют свойства для указания цвета, стиля и


толщина границы каждой из сторон.
Например, для нижней границы они будут следующими: border-bottom-
color, border-bottom-style и border-bottom-width.
Как видишь, тут тоже можно выделить правило для построения
свойства: border-сторона-свойство.

Попробуй то же самое написать для правой границы!

Скругление углов

У всех элементов по умолчанию прямые углы. Это явно видно, если,


например, добавить границу элементу div (или задний фон). Каждый их этих
углов можно скруглить. Делается это с помощью свойства border-radius.
Давай скруглим у обычного блока div углы кругом радиусом 4px:
<div>Блок с кругленными углами</div>
<style>
div {
background-color: red;
color: white;
border-radius: 4px;
}
</style>
border-radius добавить скругление всех углов блока. Но если нам надо
добавить скругление только правого нижнего угла, это можно сделать с
помощью свойства border-bottom-right-radius:
<div>Блок с кругленными углами</div>
<style>
div {
background-color: red;
color: white;
border-bottom-right-radius: 4px;
}
</style>
Точно также можно указать скругление любого другого угла, правило
построение CSS свойства простое - border-вертикальная сторона-
горизонтальная сторона-radius. Вертикальные стороны - top и bottom;
горизонтальные - left и right.

216. Поля и отступы


Одна из основных задач, которую решает CSS — это размещение блоков и
элементов относительно друг друга.
Первый шаг — установка правильных полей (margins) и отступов (paddings).

Поля

С помощью полей задается расстояние между текущим элементом и элементами,


окружающими его. Иными словами — это расстояние между границей элемента и
его соседями.
Например у нас есть 3 блока <div>:
<div>Блок 1</div>
<div id="with-margins">Блок 2</div>
<div>Блок 3</div>
Задача: сделать так, чтобы блоки <div>, окружающие блок #with-margins,
находились от него на расстоянии 10px.
Чтобы решить задачу, мы добавим в стили новое свойство margin.
<style>
#with-margins {
margin: 20px;
}
</style>
Таким образом мы задаем поля для блока #with-margins и все элементы
вокруг него будут на расстоянии 20px.
Обрати внимание, тег <div> - блочный, а значит он всегда начинается с
новой строки и элементы после него тоже всегда начинаются с новой строки.
Достигается это с помощью того, что ширина блока <div> равняется ширине
родительского компонента, а если его нет, то ширине экрана.
— Что же тогда означает фраза все элементы вокруг него будут на
расстоянии 20px?

— Она означает следующее:


o Блок 1 будет над Блок 2 на расстоянии 20px
o Блок 3 будет под Блок 2 на расстоянии 20px
Также, ты увидишь пустые поля шириной 20px по бокам Блок 2, а сам блок
будет немного сжат. Это произойдет потому, что поля добавляются со всех
сторон элемента, а не только сверху и снизу.

На примере оранжевым выделены поля:

— Тогда нам надо добавить поля только сверху и снизу.


— Ты совершенно прав. Для этого есть возможность добавлять не сразу все
поля, а для каждой стороны отдельно с помощью специальных
свойства: margin-top, margin-right, margin-bottom, margin-left.
Давай немного перепишем наши стили и добавим поля только вверху и внизу
блока #with-margins:
<style>
#with-margins {
margin-top: 10px;
margin-bottom: 10px;
}
</style>

Теперь у нас не будет лишних полей справа и слева.


Эту запись можно упростить. Свойство margin можно добавить не с одним
значением, а с четырьмя сразу, точно так же, как мы уже делали для
границ.

В таком случае каждое значение отвечает за свою сторону в следующем


порядке:
o верх (top)

o право (right)

o низ (bottom)

o лево (left)

Давай попробуем задать разные поля для разных сторон:


<style>
#with-margins {
margin: 5px 10px 2px 40px;
}
</style>

Вот что получится:


o верхнее поле - 5px
o правое - 10px
o нижнее - 2px
o левое - 40px

Отрицательные поля

Важной особенностью полей является возможность задавать отрицательное


значение:
<style>
#with-margins {
margin: 5px -10px 2px 40px;
}
</style>

Отрицательные значения для полей стоит использовать, когда нужно


"подвинуть" ближе соседние элементы. Давай рассмотрим пример, где у нас
есть блок с тремя параграфами:
<!DOCTYPE html>
<html>
<head>
<style>
p {
border: 1px solid red;
}
</style>
</head>
<body>
<div>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
<p>Paragraph 3</p>
</div>
</body>
</html>
Здесь каждый из параграфов будет находиться на каком-то расстоянии друг
от друга. Чтобы уменьшить расстояние между параграфами, просто сделаем
для них отрицательное поле -15px:
<!DOCTYPE html>
<html>
<head>
<style>
p {
border: 1px solid red;
margin: -15px;
}
</style>
</head>
<body>
<div>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
<p>Paragraph 3</p>
</div>
</body>
</html>

Как видишь, каждый из параграфов стал на 15px ближе к своему соседу.

Для первого параграфа ничего не поменялось, потому что перед ним нет
параграфа с отрицательным полем.
С тем же успехом мы можем каждому параграфу указать margin-top: -15px. В
таком случае все три параграфа поднимутся вверх на 15px.
— Очень полезная особенность, попробую поэкспериментировать с ней.
— Да, но будь осторожен. Если ты укажешь слишком большое значение для
отрицательного поля, тогда элементы будут накладываться друг на друга и
перекрывать контент.

Отступы

Отступы — это расстояние между краем элемента (границей) и контентом


внутри него.
Отличий в использовании в сравнении со свойством margin нет (кроме
отрицательного значения — его задавать нельзя).
Все что нужно — это заменить margin на padding. Рассмотрим все тот же
пример:
<div>Блок 1</div>
<div id="with-paddings">Блок 2</div>
<div>Блок 3</div>
<style>
#with-paddings {
padding: 20px;
}
</style>

Отступы очень хорошо видно на изображении — они подсвечены зелёным.


Таким образом мы задали блоку #with-paddings отступы со всех сторон
размером 20px.

Давай попробуем задать разные отступы для все четырех сторон:


<style>
#with-paddings {
padding: 5px 2px 14px 32px;
}
</style>
Точно так же, как для margin существуют отдельные свойства для каждой
стороны, так они существуют и для padding: padding-top, padding-
right, padding-bottom, padding-left.

Уточним еще один момент. Представим, нам нужно задать одинаковые отступы
по вертикали (сверху и снизу) и по горизонтали (слева и справа). Сделать
это можно легко уже знакомой нам записью:
<style>
#with-paddings {
padding: 5px 10px 5px 10px;
}
</style>
Как видишь, сверху и снизу мы задали отступы 5px, слева и справа - 10px.
Но и эту запись можно упростить.

И для полей, и для отступов можно задавать два значения, тогда первое
будет отвечать за вертикаль, а второе — за горизонталь.
<style>
#with-paddings {
padding: 5px 10px;
}
#with-margins {
margin: 5px 10px;
}
</style>
Таким образом мы задаем поля и отступы сверху и снизу по 5px, а слева и
справа — по 10px.
Одному и тому же элементу можно одновременно добавить и padding,
и margin.

Например, ты добавляешь границу параграфу, весь контент находится слишком


близко к ней и порой такой текст тяжело читать. Ну и от края экрана тоже
хочется параграфы отодвинуть.
Давай зададим внутри параграфов отступы 10px, цвет границы синий
толщиной 2px, а поля слева и справа - 25px:
<!DOCTYPE html>
<html>
<head>
<style>
p {
border: 2px solid blue;
padding: 10px;
margin-left: 25px;
margin-right: 25px;
}
</style>
</head>
<body>
<div>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</div>
</body>
</html>

Значения по умолчанию

У некоторых HTML тегов значения полей и отступов по умолчанию не 0.


Например, у тега <p> начальное значение полей слева и справа 0, а поля
вверху и внизу равняются 1em. em, также как px и % - это единица
измерения в HTML.
Размер в em чем-то похож на % - он изменяется в зависимости от изменения
размера шрифта.
Например, если размер шрифта в параграфе равняется 16px, то размер
верхнего и нижнего полей будет 1em или 16px. Если размер шрифта изменится
и станет 18px, вертикальные поля тоже станут 18px.

Чтобы изменить это поведение, нужно самому указать, каким будет размер
полей.
Для того, чтобы убрать поля, нужно просто указать margin: 0px. Также
можно самому "привязывать" поля и отступы к размеру шрифта, указав
значение в em.
Например, чтобы сделать все поля у элементов <p> в половину размера
шрифта нужно просто указать следующее:
<style>
p {
margin: 0.5em;
}
</style>
— А есть ли еще какие-то элементы со значениями по умолчанию, не равными
0?
— Конечно, например у списков <ul> и <ol> значение по умолчанию для
вертикальных полей равно 1em, а левый отступ - 40px.

217. Размеры и позиционирование


Для указания размеров в CSS используется два вида единиц
измерения: относительные и абсолютные.

Абсолютные размеры

К абсолютным единицам измерения относятся:


o сантиметры (cm);
o миллиметры (mm);
o дюймы (in - 1in = 96px = 2.54cm);
o пункты (pt - 1pt = 1/72я от 1in);
o пиксели (px);

Размеры, указанные в абсолютных единицах, фиксированы — они будут


отображаться именно так, как указано.

Здесь пиксели мы отнесли к абсолютным размерам, но на практике стоит


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

Относительные размеры

Некоторые относительные единицы мы уже рассмотрели — это %, которые


рассчитывают размер элемента относительно размера родительского элемента,
и em - размер элемента рассчитывается относительно размера шрифта этого
же компонента. Среди других относительных единиц:
o vh - расшифровывается как view height и равняется 1% высоты окна
браузера; например, высота браузера составляет 2000px, тогда 1vh = 20px;
o vw- расшифровывается как view width и равняется 1% ширины окна браузера;
o rem - рассчитывается относительно размера шрифта корневого
элемента(<html>); по умолчанию размер шрифта у <html> равен 16px и 1rem =
16px, 0.5rem = 8px. Если изменить размер шрифта у <html> на 18px, то 1rem
станет равен 18px.
Для того, чтобы изменить размер HTML элемента с помощью CSS, используют
два свойства: width и height:
<style>
div {
width: 200px;
height: 50px;
border: 1px solid red;
}
p {
width: 50%;
height: 75%;
border: 1px solid green;
}
</style>
<div>
Это родительский блок с фиксированными размерами
<p>Размеры этого параграфа привязаны к размера родительского
блока</p>
</div>
Еще есть четыре свойства, которые влияют на ширину и высоту HTML
элементов: min-width, max-width, min-height, max-height.
Свойства с приставкой min- задают минимальную ширину или высоту элемента;
c приставкой max- - максимальную.
Параграф в следующем примере будет высотой 500px и шириной 400px даже
несмотря на то, что его текста недостаточно для этой высоты и ширина
может быть меньше ширины родительского элемента:
<style>
p {
border: 1px solid red;
min-height: 500px;
max-width: 400px;
}
</style>
<p>Совсем мало текста</p>
Мы применили свойства размеров к блочному элементу <p>. Блочный элемент
был выбран для примера потому, что свойства, связанные с размерами,
нельзя применять к строчным элементам. Их ширина будет определяться
шириной контента.

Позиционирование

Размеры играют очень важную роль во внешнем виде сайта. Но изменяя


размеры элементов, сразу бросается в глаза, что надо каким-то образом
менять их позиционирование относительно друг друга, а то все блочные
элементы выстроятся в колонку и все будет выглядеть как-то грустно.
Чтобы решить эту задачу, мы будем применять CSS свойство position.

Static
По умолчанию, значение этого свойства - static. С ним позиционирование
элемента не изменить, оно остается статическим.

Relative
Можно установить position в значение relative (в переводе с англ. -
относительный), чтобы более гибко менять расположение элемента.
Просто добавив такое свойство, ты не заметишь изменений. Чтобы они стали
очевидны, нужно добавить свойства top, right, bottom, left. Каждому из
этих свойств можно задать отступ в px, тем самым сдвигая элемент от его
начального положения:
<style>
#rel {
position: relative;
top: 30px;
left: 150px;
}
</style>
<div>Блок со статическим позиционированием</div>
<div id="rel">Блок с относительным позиционированием</div>

Absolute
Абсолютное позиционирование — еще один вариант, который устанавливается с
помощью перевода свойства position в значение absolute.
Положение элемента задается свойствами top, right, bottom, left и отсчет
ведется от родительского элемента, у которого
свойство position равняется relative:
<style>
#parent {
position: relative;
top: 30px;
left: 150px;
}
#abslt {
position: absolute;
top: 50%;
left: 14px;
}
</style>
<div id="parent">
Блок с относительным позиционированием
<div id="abslt">Блок с абсолютным позиционирование</div>
</div>
Значения top, right, bottom, left можно задавать не только в px, но и
в %, тогда расчет будет происходить в зависимости от размеров
родительского элемента. Ширину будут определять родительские
свойства left и right, а высоту - top и bottom.
— А что будет, если мы укажем position: absolute, а родительского
элемента не будет? Или его свойство position будет static?

— В таком случае отсчет будет вестись от края окна браузера.

Стоит еще добавить, что если элемент абсолютно позиционирован, другие


элементы будут вести себя так, словно этого элемента нет.

Fixed
У свойства position, есть еще одно значение - fixed. С его помощью
устанавливается фиксированное позиционирование.

Это значит, что HTML элемент будет привязан к конкретной точке на экране.
При прокрутке страницы он не изменит своего положения.
Значениями top, right, bottom, left элемент можно подвинуть относительно
верхнего левого угла экрана.

Этот вариант отлично подходит для верхнего меню навигации сайта, если
нужно его постоянно отображать.

218. Ссылки и псевдо-классы


Состояние элементов
В HTML ссылки могут быть в нескольких состояниях, например:

o ссылка была нажата


o ссылка не была нажата

o на ссылку был наведен курсор

Ты можешь добавить разные CSS стили каждому из этих состояний. Например,


обычный цвет текста ссылки черный, но при наведении на нее курсором,
текст меняет свой цвет на синий. Это называется динамическим эффектом.
Чтобы добавить различные динамические эффекты для разных состояний или
действий пользователя, в CSS существуют псевдо-классы.

Для использования псевдо-класса нужно знать его имя и назначение.

Применяются псевдо-классы в паре с селектором. Селектор нужен для того,


чтобы браузер понимал, к каким именно элементам мы добавляем стиль.
Псевдо-классы определяют условия, при которых к выбранным элементам будет
добавляться стили. Рассмотрим сразу на примере псевдо-класса :link,
который применяется для изменения стилей не посещенной (не нажатой)
ссылки:
<style>
a:link {
color: green;
}
</style>
<a href="https://js.coderslang.com/" id="linkId" class="linkClass">
Coderslang
</a>

Теперь текст любой ссылки, на которую еще не нажимал пользователь, будет


зеленым.

Для использования псевдо-класса абсолютно не важно, какой селектор


использовать. В предыдущем примере мы использовали псевдо-класс с
селектором по тегу. Точно также можно использовать его с селекторами по
классу или идентификатору:
<style>
#linkId:link {
color: green;
}
.linkClass:link {
color: violet;
}
</style>
<a href="https://js.coderslang.com/" id="linkId">
Псевдо-класс привязан через селектор по id
</a>
<a href="https://js.coderslang.com/" class="linkClass">
Псевдо-класс привязан через селектор по классу
</a>
— А какие свойства стилей можно использовать для элементов, которые
выбраны с помощью псевдо-класса?

— Ограничений нет — можно использовать все стили, которые применяются для


элементов, выбранных без использования псевдо-класса.
Логичным следующим шагов будет добавить стили для ссылки, если
пользователь ее посетил. Это делается с помощью псевдо-класса :visited.
Смотри:
<style>
a:link {
color: green;
}
a:visited {
color: grey;
}
</style>
<a href="https://js.coderslang.com/">Coderslang</a>
Теперь ссылка станет серой, после того как пользователь на нее нажмет.
А при наведении на ссылку давай поменяем ее цвет на красный с помощью
псевдо-класса :hover. Вот так:
<style>
a:link {
color: green;
}
a:visited {
color: grey;
}
a:hover {
color: red;
}
</style>
<a href="https://js.coderslang.com/">Coderslang</a>
Псевдо-класс :hover можно использовать не только со ссылками, но и с
другими элементами, например параграфами.
<style>
color: green;
p:hover {
color: red;
}
</style>
<p>
При наведении на этот параграф цвет текста изменится
с зеленого на красный
</p>
Обрати внимание — имя любого псевдо-класса начинается с символа :.
Положение элементов

С помощью псевдо-классов можно решить еще одну интересную задачу — выбор


конкретного дочернего элемента.

Начнем с простого — первого и последнего элементов.

Рассмотрим блок с тремя параграфами, в котором текст первого параграфа


должен быть красным, а последнего — зеленым.

С одной стороны можно добавить идентификаторы первому и последнему


параграфу:
<style>
#first {
color: red;
}
#last {
color: green;
}
</style>
<div>
<p id="first">Первый параграф</p>
<p>Второй параграф</p>
<p id="last">Третий параграф</p>
</div>
Но если ты дополнишь блок еще одним параграфом, идентификатор #last нужно
будет перенести. И так постоянно, с появлением новых параграфов.
Эту проблему легко решить используя псевдо-классы. Для выбора первого
дочернего элемента используй псевдо-класс :first-child, последнего
- :last-child:
<style>
p:first-child {
color: red;
}
p:last-child {
color: green;
}
</style>
<div>
<p>Первый параграф</p>
<p>Второй параграф</p>
<p>Третий параграф</p>
</div>

При этом стоит помнить, что элемент будет выбран только в том случае,
если он будет первым (или последним) в списке всех дочерних элементов.
Это значит, что будут учитываться элементы всех типов, а не только те,
которые указаны в селекторе.
Например, здесь текст первого параграфа не станет красным, потому что
первый дочерний элемент внутри <div> - это заголовок <h2>, а не
абзац <p>:
<style>
p:first-child {
color: red;
}
p:last-child {
color: green;
}
</style>
<div>
<h2>Первый дочерний элемент блока div</h2>
<p>Первый параграф</p>
<p>Второй параграф</p>
<p>Третий параграф</p>
</div>
Это очень важно, поэтому давай повторим еще раз. Хоть параграф с текстом
"Первый параграф" и является первым среди всех остальных элементов <p>,
он является вторым дочерним элементом внутри <div>. Таким образом стили
для селектора p:first-child не будут применять вообще.
— А как тогда выбрать второй дочерний элемент?
— Для выбора любого другого дочернего элемента используй псевдо-
класс :nth-child(n). Этот псевдо-класс особенный — вместо n надо
указывать число, которое равняться порядковому номеру нужного элемента.
Если тебе нужен первый элемент - n = 1, второй - n = 2 и так :
<style>
p:nth-child(2) {
color: red;
}
</style>
<div>
<p>Первый параграф</p>
<p>Второй параграф</p>
<p>Третий параграф</p>
</div>
Также с помощью псевдо-класса :nth-child(n) можно выбрать все четные или
нечетные элементы, передав вместо числа n аргумент even или odd.
С помощью even мы выбираем все четные элементы, с помощью odd - нечетные.
<style>
p:nth-child(odd) {
color: red;
}
p:nth-child(even) {
color: green;
}
</style>
<div>
<p>Первый параграф с красным текстом</p>
<p>Второй параграф с зеленым текстом</p>
<p>Третий параграф с красным текстом</p>
<p>Четвертый параграф с зеленым текстом</p>
</div>
— Интересно, а как выбрать каждый третий элемент, или четвертый?
Тогда тебе надо указать формулу, по которой будут считаться порядковый
номер элемента. Формула задается в виде an + b.
В этой формуле тебе нужно указать такие значения для a и b, чтобы при
изменении n начиная с нуля получать нужные порядковые номера элементов.
n указывать уже не нужно — оно будет подставляться браузером
автоматически.
Например, тебе нужно получить каждый третий элемент. Тогда формула будет
такой: 3n + 3. Когда n = 0, тогда результат будет 3, n = 1 - результат 6,
n = 2 - результат 9 и так .

Ко всем этим элементам будет применен заданных стиль. В нашем случае,


красный цвет:
<style>
p:nth-child(3n+3) {
color: red;
}
</style>
<div>
<p>Первый параграф</p>
<p>Второй параграф</p>
<p>Третий параграф с красным текстом</p>
<p>Четвертый параграф</p>
<p>Пятый параграф</p>
<p>Шестой параграф с красным текстом</p>
<p>Седьмой параграф</p>
<p>Восьмой параграф</p>
<p>Девятый параграф с красным текстом</p>
</div>

Псевдо классов существует намного больше, чем я тебе рассказал сегодня.


Но эти — основные. Убедись, что ты хорошо понял тему. Если не чувствуешь
себя уверенно, обязательно задай вопросы.

219. Блочная модель и расположение


HTML элементов на странице
Блочная модель

Каждый HTML элемент можно рассматривать как блок. Из этих блоков строятся
веб-страницы, так же как из кирпичей строятся дома.

Блочная модель — это основа веб-дизайна, которая описывает, из чего


состоят HTML блоки. Так же можно встретить название боксовая модель, от
англ. box.

Блок — это прямоугольная область вокруг HTML элемента. Состоит он из


нескольких частей, которые мы уже рассматривали по отдельности:
o content — внутреннее содержимое HTML элемента. Например, текст. Размер
это области задается с помощью свойств width, height, min-width, max-
width, min-height, max-height.
o padding — внутренние отступы, область между контентом и границей.
o border — граница HTML элемента.
o margin — внешний поля, область между границей и окружающими элементами.

Давай рассмотрим следующий блок:


<style>
div {
width: 100px; /* Ширина контента */
margin: 10px; /* Размер внешних полей */
border: 2px solid black; /* Параметры границы */
padding: 5px; /* Размер внутренних отступов */
}
</style>
<div>Блок</div>
— Какая будет ширина блока <div>?
— Думаю, 100px.
— Нет, это не так. 100px — это ширина контента. А для того, чтобы
получить ширину всего блока <div>, нужно к ширине контента добавить два
поля по 10px (левое и правое), две границы по 2px и два отступа по 5px.
Тогда ширина блока <div> будет равна 100 + 10*2 + 2*2 + 5*2 = 134px.

Высота HTML блоков рассчитывается по такой же формуле.


Но эта формула подсчета может быть очень неудобной. Например, если
указать ширину контента не в пикселях px, а в процентах %, то наперед
рассчитать ширину не получится. Проценты невозможно мгновенно перевести в
пиксели.
<style>
div {
width: 100%;
margin: 0px;
border: 2px solid black;
padding: 100px;
}
</style>
<div>Блок</div>
В этом примере ширина блока больше, чем 100% родительского компонента,
ведь в ней еще надо добавить 100px левого отступа, 100px правого отступа
и по 2px левая и правая границы. Из-за этого внутренний блок будет
вылазить за границу своего родителя. Также может появится нежелательная
горизонтальная прокрутка, которой очень не удобно пользоваться
посетителям сайта.

Точно такого же эффекта можно достичь и с использованием px:


<style>
.parent {
width: 200px;
height: 100px;
padding: 10px;
color: white;
background-color: rgb(56, 39, 127);
}
.child {
width: 200px;
padding: 40px;
height: 50px;
color: black;
background-color: rgb(252, 198, 0);
}
</style>
<div class="parent">
Parent
<div class="child">
Child
</div>
</div>

Здесь дочерний элемент вылазит за границу своего родителя, несмотря на


то, что у них задана одинаковая ширина:
Такое нежелательное поведение можно изменить с помощью свойства box-
sizing. Это свойство определяет, как рассчитывается ширина и высота
элемента — должны ли значения padding и border входить в указанную
ширину.
Значение по умолчанию — content-box. В таком случае
свойства width и height (включая min и max свойства) будут задавать
только размеры контента, без учета padding и border, которые будут
добавляться к размеру контента для вычисления размеров.
Второе значение - border-box. При этом значение
свойства width и height будет включать все три значения: ширину или
высоту, соответствующие границы и поля (горизонтальные или вертикальные
соответственно). Давай изменим значение этого свойства для последнего
примера и посмотри на результат:
<style>
.parent {
width: 200px;
height: 100px;
padding: 10px;
color: white;
background-color: rgb(56, 39, 127);
}
.child {
width: 200px;
padding: 40px;
height: 50px;
color: black;
box-sizing: border-box; /* изменяем способ подсчета ширины элемента
*/
background-color: rgb(252, 198, 0);
}
</style>
<div class="parent">
Parent
<div class="child">
Child
</div>
</div>

Теперь наш элемент .child не вылазит за границу элемента .parent, даже


если у них одинаковая ширина и есть поля.
— Но почему внутренний элемент уже своего родителя, ведь у них одинаковая
ширина?
— Потому что у родительского элемента есть еще отступы размером 10px.
Таким образом родительский элемент на самом деле шире и занимает 220px в
ширину.

Отображение элементов

Блочная модель применима не только к блочным элементам (<div>, <p>), но и


к строчным (<span>). В рамках блочной модели, строчные элементы
отличаются только тем, что их высота и ширина определяются автоматически
в зависимости от контента внутри них.
Это поведение тоже можно изменить. Для этого используется
свойства display. Список значений этого свойства очень большой, но многие
из них применяются редко и выборочно поддерживаются браузерами.
Самые важные значения свойства display:
o block - элемент ведет себя как блочный. Он начинается с новой строки,
можно указывать размеры, поля и отступы.
o inline - элемент ведет себя как строчный. Он может быть использован
внутри текста без переноса на новую строку и находиться на одной линии с
другими элементами.

Такому элементу нельзя указывать размеры, как и любому строчному


элементу. Внутри строчного элемента нельзя использовать блочные элементы.
Еще, у строчных элементов игнорируется значение верхнего и нижнего полей.
o inline-block - объединяет в себе некоторые свойства обоих вариантов.
Отличается от значения block тем, что у такого элемента нет переноса
строки до и после — он может находиться на одной линии с другими
элементами и текстом.
В отличие от inline, элементу inline-block можно указать размеры, а еще
добавить верхнее и нижнее поля.
o none - элемент не отображается, окружающие его элементы ведут себя, будто
его нет, и могут занять его место.
<style>
div {
display: inline;
}
span {
display: block;
}
p {
display: none;
}
</style>
<div>Блочный элемент ведет себя как строчный</div>
<span>А строчный теперь отображается с новой строки</span>
<p>Параграф и вовсе не отображается</p>
Есть еще два значения, которые мы рассмотрим в следующих
лекциях: flex и grid. Они используются для создания сложных и гибких
макетов, позволяют решить сложные CSS задачи и упрощают разработку
адаптивных сайтов для разных устройств.

220. Flexbox макет


Всю страницу можно разбить на контейнеры, которые объединяют различные
HTML элементы и делят страницу на логические области. Контейнером может
быть довольно большая область, например контейнер таблицы. Или совсем
маленькая область, например одно слово, если нужно в параграфе выделить
его и сделать жирным.

Обычные HTML контейнеры не могут изменять размеры своих дочерних


элементов. Поэтому придумали flexbox.
Flexbox макеты (с англ. flex - гибкий) позволяют эффективно размещать и
выравнивать свои дочерние элементы. Изменяя их высоту, ширину и порядок
внутренних элементов, flexbox контейнер оптимально заполняет свободное
место на странице в зависимости от размеров экрана и количества
элементов. Именно в возможности подстраиваться и заключается гибкость
таких контейнеров.

Flexbox - это не одно свойство, а целый модуль. Он состоит из flex


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

Все flex элементы размещаются внутри flex контейнера либо вдоль главной
оси, либо вдоль поперечной оси:

Flex контейнер

Для того, чтобы сделать элемент flex контейнером, необходимо добавить ему
свойство display со значением flex или inline-flex. В первом случае
элемент будет вести себя как блочный, во втором — как строчный.
Обычно для формирования flexbox модуля используется тег <div>. Он
подходит для определения и контейнера, и дочерних элементов.
Все вложенные блоки <div> станут flex элементами и будут располагаться
вдоль главной оси слева направо.
<style>
.container {
display: flex;
}
</style>
<div class="container">
<div>Flex элемент 1</div>
<div>Flex элемент 2</div>
<div>Flex элемент 3</div>
</div>

Flex контейнер: flex-direction

Для изменения оси или направления, в котором располагаются flex элементы,


нужно воспользоваться свойством flex-direction, которое принимает
следующие значения:
o row - значение по умолчанию. Элементы внутри контейнера располагаются
вдоль главной оси слева направо.
o row-reverse - элементы внутри контейнера располагаются вдоль главной оси
справа налево.
o column - элементы располагаются вертикально. В том порядке, в котором они
были перечислены в коде.
o column-reverse - элементы располагаются вертикально, но порядок
становится обратным.
<style>
.container {
display: flex;
flex-direction: column-reverse;
}
</style>
<div class="container">
<div>Flex элемент 1</div>
<div>Flex элемент 2</div>
<div>Flex элемент 3</div>
</div>

Flex контейнер: flex-wrap

Все flex элементы будут пытаться разместиться в одну линию. Будут


сжиматься до тех пор, пока это будет возможно, а когда им не будет
хватать места они вылезут за границу своего контейнера.
Чтобы избежать этого, можно использовать свойство flex-wrap, которое
разрешит перенос на новую строку элементов, не поместившихся в макет:

У свойства flex-wrap есть три возможных значения:


o nowrap - значение по умолчанию, все элементы будут располагаться в одну
линию.
o wrap - элементы будут переноситься на новую строку, если им не будет
хватать места.
o wrap-reverse - the elements will wrap to a new line, with the lines going
in reverse order.
Flex контейнер: justify-content

Оси нужны flex контейнеру для того, чтобы понимать вдоль какой линии
будут располагаться элементы, и как они будут размещены вдоль этой линии.
С помощью свойства justify-content изменяется расположение элементов
вдоль главной оси.
Изначально элементы пытаются находиться как можно левее к началу главной
оси, что соответствует значению flex-start.

Разберем все возможные значения:


Со значениями flex-start, flex-end, и center должно быть все понятно —
элементы располагаются вначале, посередине и в конце контейнера.
Со значением space-between - первый элемент располагается вначале
контейнера, последний в конце. Все остальные элементы равномерно, с
одинаковым расстоянием между ними, располагаются вдоль главной оси.
Значение space-around означает, что все элементы будут размещены с
одинаковым расстоянием между ними. Но у первого элемента пространства
слева меньше, чем справа. Точно так же, как и у последнего справа меньше
чем слева. Происходит это потому, у второго элемента тоже есть свое
пространство слева и справа, и поэтому справа у первого элемента будет
места в два раза больше, чем слева.
Полностью выравнять расстояние между элементами и краями контейнера можно
с помощью значения space-evenly.

Flex контейнер: align-items

Вдоль поперечной оси flex элементы по умолчанию растягиваются, за что


отвечает свойство align-items со значением stretch:

Все остальные значения не будут изменять размеры элементов, а будут


просто смещать их вдоль поперечной оси. Значения flex-
start, center и flex-end будут сдвигать элементы вдоль поперечной оси и
располагать их вверху, по-середине или внизу контейнера.
Еще одно значение, которые может показаться не очевидным с первого раза —
это baseline. С этим значением элементы внутри контейнера будут выравнены
по базовой линии текста в них — линии, которая проходит по нижней части
букв элемента. Таким образом, элементы выстраиваются так, чтобы текст в
каждом из них был на одной линии:
Flex контейнер: align-content

Если внутренние элементы растянулись на несколько строчек, когда у


контейнера значение свойства flex-wrap равняется wrap, то для
распределения этих линий вдоль поперечной оси, нужно использовать
свойство align-content:
Свойство flex-wrap работает с линиями внутри контейнера вдоль поперечной
оси точно так же, как свойство justify-content распределяет элементы
вдоль главной оси. По умолчанию, значение этого свойства — stretch — все
линии будут растянуты вдоль поперечной оси. Элементы также будут
растянуты на всю высоты строки, если их высота не фиксирована.

Flex элементы

Flex контейнер со всеми CSS свойствами отвечает за то, как буду


располагаться flex элементы. В этом и проявляется его гибкость. Он дает
возможность подстроить отображение внутренних элементов в зависимости от
внешних факторов.

Свойство flex-grow

Каждому элементу можно указать количества свободного места, которое он


может занять. Значениями свойства flex-grow, которые отвечает за это,
могут быть положительные числа без размерности, показывающее сколько
свободного места возьмет тот или иной элемент относительно остальных.

Начальное значение этого свойства - 0. В таком случае свободное место


остается свободным:

Если всем элементам указать flex-grow: 1, то тогда все свободное место


будет одинаково отдано под каждые элемент:
Если одному элементу указать flex-grow: 2, то ему будет отдано в 2 раза
больше свободного места, чем остальным.

<style>
.container {
display: flex;
border: 1px solid blue;
padding: 5px 0;
}
.item {
background-color: red;
padding: 10px;
flex-grow: 1;
margin: 0 5px;
}
</style>
<div class="container">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
</div>

Свойство flex-shrink

Противоположным для flex-grow будет свойство flex-shrink. Принимая точно


такое же значение, это свойство показывает во сколько раз относительно
других элемент будет уменьшаться, когда места в контейнере будет
недостаточно. Увидеть это можно, например, при уменьшении окна браузера.
Начальное значение flex-shrink — 1. Все элементы будут уменьшаться
одинаково. Если у какого-то элемента это значение выше, то такой элемент
будет уменьшаться быстрее.
Если указать элементу flex-shrink: 0, он не будет уменьшаться совсем:

<style>
.container {
display: flex;
border: 1px solid blue;
padding: 5px 0;
}
.item {
background-color: red;
padding: 10px;
width: 200px;
margin: 0 5px;
}
.larger-shrink {
flex-shrink: 2;
}
</style>
<div class="container">
<div class="item">1</div>
<div class="item larger-shrink">2</div>
<div class="item">3</div>
</div>
Свойство flex-basis

У любого элемента может быть какое-то начальное значение количества


места, которое он будет занимать до того, как свободное место будет
распределяться. За это отвечает свойство flex-basis, а размеры можно
указывать в каких либо размерностях: px, % и т.д. Начальное значение
- auto, с ним элемент будет занимать место согласно внутреннего контента:

<style>
.container {
display: flex;
border: 1px solid blue;
padding: 5px 0;
}
.item {
background-color: red;
padding: 10px;
margin: 0 5px;
flex-grow: 1;
}
.with-flex-basis {
flex-basis: 75px;
}
</style>
<div class="container">
<div class="item with-flex-basis">1</div>
<div class="item">2</div>
<div class="item">3</div>
</div>
В данном примере первый элемент будет больше остальных, поскольку его
начальная ширина больше, а свободное пространство одинаково
распределяется между элементами благодаря flex-grow: 1.
Все три свойства flex-grow, flex-shrink и flex-basis можно объединить в
одно - flex, где через пробел указываются все три значения: flex: 1 0
100px;.
Значение по умолчанию - 0 1 auto, что соответствует значениям по
умолчанию каждого свойства отдельно. Также можно указывать
значение auto и none.
o auto соответствует значениям 1 1 auto - элемент будет забирать все
возможное пространство. Он будет уменьшаться, как и все остальные
элементы. начальные размеры определяются контентом.
o none соответствует значениям 0 0 auto - в таком случае можно сказать, что
элемент не будет гибким — он не будет ни уменьшаться, ни увеличиваться, а
начальные размеры будут определяться контентом.
Также, необязательно указывать все три значения. Можно, например, указать
только flex-grow: flex: 1;. Тогда элемент будет занимать все свободное
пространство, а для свойств flex-shrink и flex-basis будут применять
значения по умолчанию.

Свойство order

По умолчанию flex элементы идут в том порядке, в котором они указаны в


коде.
Изменить положение элемента можно с помощью свойства order, где мы
указываем позицию элемента. Элементы с одинаковым значением
свойства order располагаются в том порядке, в котором они указаны в HTML
коде.
<style>
.container {
display: flex;
border: 1px solid blue;
padding: 5px 0;
height: 100px;
align-items: flex-start;
}

.item {
background-color: yellow;
padding: 10px;
margin: 0 5px;
flex: 1;
}

.item-2 {
order: -1;
}

.item-4 {
order: 2;
}

.item-5 {
order: 1;
}
</style>
<div class="container">
<div class="item item-1">1</div>
<div class="item item-2">2</div>
<div class="item item-3">3</div>
<div class="item item-4">4</div>
<div class="item item-5">5</div>
</div>

В этом примере второй элемент будет перенесен в начало контейнера, первый


и третий элементы — на втором и третьем месте (на самом деле эти элементы
просто остаются на своем месте), четвертый элемент — будет перенесен на
последнее место, а пятый элемент станет предпоследним.

Это свойство может быть очень полезным при разработке адаптивного сайта,
где понадобится изменить положение элемента в зависимости от размера
экрана. Но об адаптивной верстке будем говорить позже, сейчас время
решать практические задачи.

221. Grid макет


Flexbox — это отличное решение для построения относительно простых
макетов. HTML элементы таких сайтов обычно располагаются в одном
направлении — горизонтально (основная ось) или вертикально (поперечная
ось).
В более сложных макетах, где элементы могут одновременно размещаться в
двух направлениях, эффективнее будет применять Grid макет, который
специально разработан для двумерных макетов и работает одновременно
со строками и колонками. От сюда и название grid, что в переводе с англ.
означает сетка.
Как и в случае с flexbox, grid состоит из grid-контейнера и grid-
элементов, которые находятся внутри контейнера. Также вводится несколько
дополнительных понятий:
o grid-линия (grid line) - это разделительные линии, которые образуются на
стыке строк (линии строк) и на стыке колонок (линии колонок).
o grid-ячейка (grid cell) - самый маленький элемент, с котором работает
grid - область между двумя линиями строк и двумя линиями колонок.
o grid-трек (grid track) - область между двумя grid-линиями (колонка или
строка).
o grid-область (grid area) - область, окруженная 4-мя grid-линиями. Может
быть любого размера.

Grid-контейнер

Для того, чтобы сделать элемент grid-контейнером, необходимо добавить ему


свойство display со значением grid. Все вложенные HTML элементы станут
grid элементами:
<style>
.container {
display: grid;
}
</style>
<div class="container">
<div class="item">
<div class="not-an-item">1</div>
</div>
<div class="item">2</div>
<div class="item">2</div>
</div>
Элемент с классом container стал grid-контейнером. Все элементы с
классом item - grid-элементы. А вот элемент с классом not-an-item уже не
является grid-элементом, так как не являются прямыми наследниками grid-
контейнера.

Если все твои grid-элементы блочные, сразу ты не увидишь изменения в


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

Если grid-элементы будут строчными, то ты увидишь как они выстроятся в


одну колонку, и каждый будет себя вести как блочный HTML элемент.

Рассмотрим подробнее первый вариант:


<style>
.container {
padding: 5px;
background-color: #2589bd;
}
.item {
height: 40px;
margin: 5px;
background-color: #ffb900;
}
</style>
<div class="container">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>

В этом случае grid контейнер будет выглядеть так:


На изображении слева показано, как будет выглядеть наш элемент без
изменения свойства display. На изображении справа для .container мы
добавили свойство display: grid.
Как видишь, изменений нет. Дочерние элементы с классом .item вели себя,
как блочные (все начинались с новой строки и занимали всю ширину
родителя) и их поведение не изменилось, хотя контейнер стал grid-
контейнером.

Сделаем все вложенные элементы строчными:


.item {
height: 40px;
width: 40px; /* добавляем ширину, чтобы inline-block элементы
отображались */
margin: 5px;
display: inline-block; /* делаем дочерние элементы строчными */
background-color: #ffb900;
}
На изображения слева видно, как дочерние элементы выстроились в один ряд.
Это произошло потому, что это строчные HTML элементы.
А после того, как мы добавили свойство display: grid CSS
классу .container, все его дочерние элементы изменили свое положение. Они
начали вести себя как блочные HTML элементы.

По умолчанию создается только одна колонка для grid-элементов, что


соответствует обычному отображению блочных элементов.

Grid tracks

Количество столбцов и строк в grid макете изменяется с помощью


свойств grid-template-columns и grid-template-rows.

Важные особенности этих свойств:

o В качестве значений могут выступать пиксели, проценты или часть


свободного места в сетке
o Чтобы указать часть свободного места, используют размерность fr
o Чтобы колонка или строка заняли все свободное место в контейнере
автоматически, используют значение auto

Давай рассмотрим на примере:


<!DOCTYPE html>
<html>
<head>
<title>Grid layout</title>
<style>
.container {
display: grid;
grid-template-columns: 100px auto 100px;
}
</style>
</head>
<body>
<div class="container">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
<div class="item">6</div>
</div>
</body>
</html>

Мы создали grid-контейнер, в котором будет все элементы будут размещаться


в трех колонках.

Ширина первой и последней колонок 100px, а ширина второй колонки будет


автоматически равняться всему оставшемуся пространству:
Если ты хочешь установить один и тот же размер нескольким колонкам или
строкам подряд, необязательно повторять их. Для этого можно использовать
конструкцию repeat(количество_повторений, размер):
.container {
display: grid;
grid-template-columns: repeat(3, 1fr);
}

В стилях мы указали только три колонки, а строки не трогали.

Строки и колонки будут создаваться автоматически, когда появляется


контент, который не помещается в изначально определенный grid.
Размеры колонок и строк также будут определяться автоматически в
зависимости от контента внутри них. Таким колонкам и строкам grid макета
можно устанавливать размеры. Для этого есть два свойства: grid-auto-
rows и grid-auto-columns.

Давай для первого примера автоматически созданным строкам зададим высоту


100px:
.container {
display: grid;
grid-template-columns: 100px auto 100px;
grid-auto-rows: 100px;
}

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


таких строк и колонок.

Размер контента не всегда можно предсказать. Могут быть случаи, когда


контент не поместится в выделенный для него контейнер:
<style>
.container {
display: grid;
grid-template-columns: repeat(2, 200px);
grid-template-rows: 50px;
grid-auto-rows: 50px;
}
.item {
background: #ffb900;
}
</style>
<div class="container">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex
ea
commodo consequat. Duis aute irure dolor in reprehenderit in
voluptate
velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
occaecat cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum.
</div>
<div class="item">4</div>
</div>
В таком случае лучше будет указать максимальный и минимальный размеры с
помощью конструкции minmax(min-значение, max-значение):
.container {
display: grid;
grid-template-columns: repeat(2, 200px);
grid-template-rows: 50px;
grid-auto-rows: minmax(50px, auto);
}
Значение auto означает, что для максимальной высоты строки ограничений
нет и она будет зависеть только от высоты контента.
Каждой линии, разделяющей колонки и строки можно добавить имя.
Например, у нас есть три колонки для которых формируется 4 линии. Пусть
первой линии надо дать название start, второй column-2-start,
третей column-3-start и четвертой end. Делается это следующим образом:
.container {
display: grid;
grid-template-columns: [start] 100px [column-2-start] auto [column-
3-start] 100px [end];
}

На странице это выглядит так:


Вторая и третья линии одновременно являются и началом, и концом столбца.
По этому каждой линии можно давать несколько названий:
.container {
display: grid;
grid-template-columns: [start] 100px [column-1-end column-2-start]
auto [column-2-end column-3-start] 100px [end];
}
— Зачем давать этим линиям название?

— Названия линий очень удобно использовать, когда прийдется указать


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

Grid области

Обычно, большие HTML контейнеры делятся на разные логические области.


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

Представим, что наша страница состоит из 4 колонок и 4 строк. Всю первую


строку такой сетки будет занимать заголовок. Первую колонку оставшихся
строк будет занимать , а все остальное — контент.

Всю эту структуру можно создать с помощью свойства grid-template-areas,


которое разбивает сетку на нужные блоки:
.container {
display: grid;
grid-template-areas:
"header header header header"
"nav content content content"
"nav content content content"
"nav content content content";
}
Таким образом мы разделили HTML контейнер на 4 строки и 4 колонки. Мы
разбили страницу на логические области, с помощью одного свойства grid-
template-areas.

Если вместо названия блока поставить точку, данная ячейка будет считаться
"пустой" и в ней ничего не будет отображаться:
.container {
display: grid;
grid-template-areas:
"header header header header"
"nav . content content"
"nav . content content"
"nav . content content";
}
С помощью ключевого слова none может пропустить ячейку и она не будет
относиться к какой-либо области:
.container {
display: grid;
grid-template-areas:
"header header header header"
"nav content content none"
"nav content content none"
"nav content content none";
}
На практике логические области grid макета будут использоваться, чтобы
определить, как HTML элементы будут располагаться относительно друг
друга.

Grid линии

Для изменения толщины grid линий используются свойства column-gap и row-


gap.

Учти, что с помощью этих свойства ты сможешь изменить толщину линий


только между колонками и строками, но не внешние края:
.container {
display: grid;
grid-template-columns: 100px auto 100px;
grid-auto-rows: minmax(100px, auto);
column-gap: 20px;
row-gap: 40px;
}
Также толщину линий можно одновременно задать с помощью одного
свойства gap. Это свойство принимает два значения сразу. Первое —
соответствует row-gap, второе — column-gap. Если будет указано только
одно значение, это означает что row-gap = column-gap:
.container {
display: grid;
grid-template-columns: 100px auto 100px;
grid-auto-rows: minmax(100px, auto);
gap: 40px 20px;
}

Горизонтальное выравнивание элементов grid макета

Изменить положение grid-элементов вдоль горизонтальной оси (строки) grid-


ячейки можно с помощью свойства justify-items. Всего есть четыре
значения:
o start - выравнивает элементы, чтобы они были с левого края их ячейки
o center - выравнивает элементы, чтобы они были посередине их ячейки
o end - выравнивает элементы, чтобы они были с левого края их ячейки
o stretch - значение по умолчанию; элементы будут заполнять всю ширину из
ячеек

Вертикальное выравнивание элементов

Свойство align-items изменяет положение grid-элементов вдоль


горизонтальной оси (колонки) внутри grid-ячейки. Значения такие же, как и
для свойства justify-items:
o start - выравнивает элементы, чтобы они были у верхнего края их ячейки.
o center - выравнивает элементы, чтобы они были посередине их ячейки.
o end - выравнивает элементы, чтобы они были с нижнего края их ячейки.
o stretch - значение по умолчанию; элементы будут заполнять всю высоту из
ячеек.
Свойства align-items и justify-items объединяются в одну запись в
свойстве place-items. Первое значение устанавливает свойство align-items,
а второе - justify-items. Если второе значение не указано, тогда первое
будет применяться к обоим свойствам - и вертикального, и горизонтального
выравнивания.

Горизонтальное выравнивание grid

Изменить положение grid-ячеек вдоль горизонтальной оси внутри grid-


контейнера можно с помощью свойства justify-content. Всего есть четыре
значения:
o start - выравнивает сетку, чтобы она была с левого края контейнера.
o end - выравнивает сетку, чтобы она была с правого края контейнера.
o center - выравнивает сетку посередине контейнера.
o space-between - добавляет одинаковое расстояние между колонками, крайние
колонки находятся по бокам контейнера.
o space-evenly - одинаковое расстояние между колонками и краями контейнера.
o space-around - одинаковое расстояние по обе стороны от каждой колонки.
o stretch - расширяет элементы для полного заполнения сеткой контейнера.

Вертикальное выравнивание grid

Свойство align-content делает то же самое, что и justify-content, только


в вертикальной плоскости. Значения тоже точно такие же:
o start - выравнивает сетку, чтобы она была у верхнего края контейнера.
o end - выравнивает сетку, чтобы она была у нижнего края контейнера.
o center - выравнивает сетку посередине контейнера.
o space-between - добавляет одинаковое расстояние между строчками, крайние
строчки находятся по краям контейнера.
o space-evenly - одинаковое расстояние между строчками и краями контейнера.
o space-around - одинаковое расстояние по обе стороны от каждой строки.
o stretch - расширяет элементы для полного заполнения сеткой контейнера.

Свойства align-content и justify-content объединяются в одну запись в


свойстве place-content. Первое значение устанавливает свойство align-
content, а второе - justify-content. Если второе значение не указано,
тогда первое будет применяться к обоим свойствам - и вертикального, и
горизонтального выравнивания.

Расположение grid-элемента

Мы уже разобрались как элементы автоматически располагаются по grid. Но


каждому элементу можно указать, где он будет начинаться и где
заканчиваться. Для этого есть четыре свойства:
o grid-column-start - определяет колонку, с которой начнется элемент.
o grid-column-end - определяет конечную колонку, на которой закончится
элемент.
o grid-row-start - определяет строку, с которой начнется элемент.
o grid-row-end - определяет конечную строку, на которой закончится элемент.

Для каждого свойства возможен один из четырех вариантов значений:


o <номер_линии> или <имя_линии> - например, элемент должен начинаться со
второй колонки, тогда номер будет 2. Или можно использовать имена линий,
которые мы рассматривали ранее. Например, column-2-start.
o span <количество_треков> - например, нужно чтобы элемент занимал две
колонки из трех. Тогда значение будет span 2.
o span <имя_линии> - элемент будет растягиваться до того момента, пока не
упрется в линию с указанным именем.
o auto - элемент будет автоматически занимать нужное количество колонок или
строк, или по умолчанию 1.

Рассмотрим следующий пример:


<style>
.container {
display: grid;
grid-template-columns: [start] 100px [column-1-end column-2-start]
auto [column-2-end column-3-start] 100px [end];
grid-template-rows: 100px 100px;
}
.item {
grid-column-start: column-2-start;
grid-column-end: span 2;
grid-row-start: 2;
grid-row-end: 3;
}
</style>
<div class="container">
<div class="item">1</div>
</div>
Единственный grid-элемент будет располагаться во второй строчке — между
линиями 2 и 3. Элемент растянется на две колонки начиная с линии column-
2-start:
Для сокращения записи можно использовать свойства grid-column и grid-row.
Первое объединяет свойства grid-column-start и grid-column-end. Второе
- grid-row-start и grid-row-end. Каждому свойству можно передать два
значения, указанные через символ /. Если второе значение, отвечающее за
конечное положение, отсутствует, элемент будет растянут только на 1 трек:
.item {
grid-column: column-2-start / span 2;
grid-row: 2 / 3;
}
Свойство grid-area позволить предыдущий пример вообще записать в одну
строчку:
.item {
grid-area: 2/ column-2-start / 3 / span 2;
}
Свойства grid-area объединяет следующие свойства grid-row-start, grid-
column-start, grid-row-end, grid-column-end. Значения нужно перечислять
именно в этой последовательности. Если последовательность нарушить,
элемент будет отображаться, однако его положение будет не таким, как
задумывалось.

Если указать в качестве линии имя несуществующее линии, элемент не будет


отображаться, так как просто не известно, где ему начинаться или
заканчиваться. Если указать элементу номер линии, который превышает
указанное количество строк, то нужное количество строк будет
автоматически добавлено.
Если ты указал для контейнера свойство grid-template-area и твой элемент
должен занимать всю эту область, тебе нет смысла указывать, когда твой
элемент должен начаться, а когда закончится. Просто укажи название
области — твой элемент растянется на всю это область:
.container {
display: grid;
grid-template-areas:
"header header header header"
"nav content content none"
"nav content content none"
"nav content content none";
}

.header {
grid-area: header;
}

Выравнивание элемента

С помощью свойств justify-self и align-self можно изменить положение


одного элемента внутри grid-ячейки.
Значения точно такие же, как и для свойств grid-контейнера justify-
items и align-items: start, end, center и значение по умолчанию
— stretch. Два эти свойства можно объединить с помощью свойства place-
self:
<style>
.container {
display: grid;
grid-template-columns: 100px auto 100px;
grid-template-rows: repeat(2, 50px);
}
.item-placed-one {
place-self: start end;
}
.item-placed-two {
justify-self: center;
align-self: center;
}
.item-placed-three {
justify-self: center;
align-self: end;
}
</style>
<div class="container">
<div class="item item-placed-one">1</div>
<div class="item">2</div>
<div class="item item-placed-two">3</div>
<div class="item">4</div>
<div class="item item-placed-three">5</div>
<div class="item">6</div>
</div>

222. Селекторы и комбинаторы


Чтобы применить CSS стили к HTML элементам нужно сделать 2 вещи:
o написать CSS

o выбрать подходящие HTML элементы


В простейшем случае, для выбора нужных HTML элементов, мы можем
использовать аттрибут style. Он сразу добавит стили HTML элементу в
котором находится и никак не будет влиять на другие элементы.
Но, у атрибута style есть большая проблема. Мы должны писать его заново
для каждого нового HTML элемента. Если на странице 100 заголовков и нам
нужно все их сделать синими, то атрибут style нужно будет повторить 100
раз. Это долго, скучно и бессмысленно.

Чтобы выбрать группу HTML элементов и применить к ним CSS стили —


используются селекторы.

Селектор — это правило, с помощью которого разработчик говорит браузеру,


к каким элементам надо применить стили. Селекторы могут быть простыми и
составными.

Простые селекторы

o селекторы по типу - к ним относятся все селекторы с использованием имени


тега: div, p, span и так .
<style>
div {
color: red;
}
</style>
<div>Example</div>
o селектор по классу - будут выбраны все элементы которые соответствуют
указанному классу.
<style>
.red-text {
color: red;
}
</style>
<div class="red-text">Example</div>
o селектор по id - будет выбран элемент, которые соответствует указанному
идентификатору. Один идентификатор может применяться только к одному
элементу в HTML документе.
<style>
#red-text {
color: red;
}
</style>
<div id="red-text">Example</div>

Кроме селекторов по типу HTML тега, классу или id элемента, есть еще
несколько:
o универсальный селектор - выберет все HTML элементы. У них может быть
любой типа, класс или идентификатор. Выглядит универсальный селектор, как
"звездочка" *:
<style>
* {
color: red; /* текст всех трех div элементов будет красного цвета */
}
</style>
<div>Example 1</div>
<div id="red-text">Example 2</div>
<div class="red-text">Example 3</div>
o селекторы по аттрибутам - будут выбраны все элементы, у которых указан
атрибут или атрибут равен указанному значению. Давай рассмотрим следующий
пример:
<a href="https://coderslang.com/">Coderslang App</a> <!-- Ссылка 1 -
->
<a href="https://js.coderslang.com/" target="_blank">Learn JS</a>
<!-- Ссылка 2 -->

Мы можем добавить несколько селекторов по аттрибутам:


/* будут выбраны все ссылки, у которых есть аттрибут target - Ссылка
2 */
a[target] {
color: red;
}

/* будут выбраны все ссылки, у которых есть аттрибут href =


https://coderslang.com/ - Ссылка 1 */
a[href="https://coderslang.com/"] {
color: green;
}

/* будут выбраны все ссылки, у которых аттрибут href начинается с


https - Ссылка 1 и Ссылка 2 */
a[href^="https"] {
font-size: 20px;
}

/* будут выбраны все ссылки, у которых аттрибут target содержит


blank - Ссылка 2 */
a[target*="blank"] {
text-decoration: none
}

/* будут выбраны все ссылки, у которых аттрибут href заканчивается


на com - Ссылка 1 и Ссылка 2 */
a[href$=".com"] {
font-style: italic;
}

Составные селекторы

Составные селекторы отличаются от простых, тем, что объединяют несколько


правил. Например, можно использовать вместе тип HTML тега и класс.
Создадим HTML документ с двумя тегами <div> и двумя тегами <p>.
<style>
red-text {
color: red;
}
</style>

<div class="red-text">Example 1</div>


<div>Example 2</div>
<p class="red-text">Example 3</p>
<p>Example 4</p>
Задача: всем блокам div с классом red-text установить размер шрифта 25px.

Мы создадим составной селектор, просто перечислив все нужные селекторы по


порядку. Пробелы не нужны.
div.red-text {
font-size: 25px;
}
Селектор div.red-text сделает как раз то, что нам нужно. Он выберет
все <div> элементы с классом red-text. А font-size: 25px; установит
размер шрифта равным 25px.

Список селекторов

Бывает необходимость применить стили сразу к нескольким элементам. Для


этого можно создать новый класс, а можно просто сгруппировать элементы в
список селекторов. Чтобы сделать это достаточно перечислить селекторы
через запятую. Тогда любой элемент, соответствующий хотя бы одну
селектору, получить стили.
Задача: Нужно всем элементам добавить цвет текста красный, указав стиль
только один раз (не повторяя для каждого элемента отдельно) и не
используя универсальный селектор:
<div>Example 1</div>
<p>Example 2</p>
<span class="red-text">Example 3</span>
<h2 id="red-text">Example 4</h2>
Решение:
div, p, .red-text, #red-text {
color: red;
}

Комбинаторы

Комбинаторы — это специальные селекторы, которые выбирают элементы по


некоторой комбинации.
Например, нам надо получить все <span> элементы, которые находятся в
параграфах, и сделать насыщенность их шрифта 600:
<span>GRID Layout</span>
<p>
<span>grid</span> – generates a block-level grid
</p>
<p>
<span>inline-grid</span> – generates an inline-level grid
</p>
Конечно, можно нужным <span> добавить класс, но можно все сделать немного
проще.
Для этого мы используем комбинатор потомков: сначала указываться
родительский селектор, потом ставится пробел, потом селектор потомка
(дочернего элемента), который нам и нужен:
p span {
font-weight: 600;
}
Запись p span означает, что стиль будет применен ко всем span элементам
внутри параграфа. Уровень вложенности не имеет значения.
<span>GRID Layout</span>
<p>
<div>
<span>grid</span> – generates a block-level grid
</div>
</p>
<p>
<span>inline-grid</span> – generates an inline-level grid
</p>
И в первом примере, и во втором font-weight изменится и у текста grid, и
у inline-grid.
Для того, чтобы выбрать только непосредственные дочерние элементы, нужно
воспользоваться child комбинатор. Вместо пробела между селекторами
ставится знак >:
p > span {
font-weight: 600;
}

Родственные комбинаторы

Родственные комбинаторы нужны, когда есть необходимость стилизовать


элементы на одном уровне. Для этого есть общий родственный
комбинатор и смежный родственный комбинатор.
Общий родственный комбинатор выглядит следующим образом: элемент A ~
элемент B. В результате будут выбраны все элементы B, которые следуют за
элементом А:
<style>
h2 ~ .selected {
color: red;
}
</style>

<div>previous blocks</div>
<div class="selected">won't be styled</div>
<h2>Grid container properties:</h2>
<div class="selected">grid-template-columns</div>
<div>gap</div>
<div>grid-template-areas</div>
<div class="selected">justify-items</div>
В данном примере текст будет красным только в двух блоках с
классом selected, которые находятся после <h2>.
Смежный родственный комбинатор выглядит похоже: элемент A + элемент B. В
результате будут выбраны все элементы B, которые следуют сразу за
элементом А (смежные с ним):
h2 + .selected {
color: red;
}
В результате такого комбинатора текст будет красным только в одном блоке,
который следует сразу за заголовком <h2>.

Приоритеты стилей

К одному и тому же элементы можно добавить сколько угодно CSS стилей. Это
можно сделать различными способами: внешние, внутренние стили, строчные
стили. Их можно указать с помощью классов или идентификаторов, с помощью
селектора по типу или составного селектора.

Из-за этого часто происходят конфликты и одному и тому же элементы могут


быть назначены одинаковые стили с разными значениями:
<style>
div {
color: red;
}
.description {
color: green;
}
</style>
<div class="description">Какого же цвета должен быть текст?</div>
На примере выше, одному и тому же элементы мы два раза указали цвет: с
помощью селектора по типу — указываем цвет red, а с помощью селектора по
классу - green.
Как же понять, какой цвет применит браузер при отображении? Для этого у
каждого селектора есть свой так называемый "вес". Чем больше вес у
селектора, тем выше его приоритет при применении. Вес селектора по типу
- 1, по классу - 10. И на примере выше текст будет отображен с
цветом green.

Немного усложним наш пример и добавим еще несколько селекторов:


<style>
div {
color: red;
}
#warning {
color: orange;
}
.description {
color: green;
}
</style>
<div id="warning" class="description" style="color: brown;">Какого
же цвета должен быть текст?</div>
Мы добавили еще два значения для свойства color: с помощью селектора по
id orange и с помощью строчных стилей brown. В итоге текст будет
отображен с цветом brown - у строчных стилей самый высокий приоритет с
весом 1000. У селектора по id вес 100 - выше чем у селектора по классу,
но все же ниже, чем у сточных стилей.

Подсчет веса

Еще немного изменим наш пример:


<style>
div {
color: brown;
}
div div {
color: red;
}
.parent div {
color: orange
}
div#warning.description {
color: violet
}
</style>
<div class="parent">
<div id="warning" class="description">Какого же цвета должен быть
текст?</div>
</div>

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


какой будет у текста цвет. Для этого нужно всего лишь подсчитать вес
составного селектора.
Составной селектор может состоять из простых селекторов: id, class и имя
тега. Давай воспринимать каждый простой селектор как ячейку. За
присутствие простого селектора в составном будем добавлять 1 в
соответствующую ячейку. Рассмотрим на примере первого селектора div:
Теперь, когда мы объединим значения все трех ячеек, то получим 001, ну
или 1. Именно тот вес, который и должен быть у селектора по типу.

Проставим вес остальным селекторам:

В итоге мы видим, что у селектора div#warning.description самый высокий


вес, равный 111 и текст будет отображен цветом violet. Если же у
нескольких селекторов все таки выходит одинаковый вес, будут применяться
конфликтующие стили селектора, который был указан позднее всего.

!important

Бывает ситуации, когда у разработчика нет желания или возможности


разбираться в приоритетности селекторов — ему просто нужно чтобы цвет
текста (или другой стиль) был именно такой и все. Для этого используется
определение !important:
<style>
div {
color: red !important;
}
div {
color: green;
}
</style>
<div style="color: orange">Очень важное сообщение</div>
Здесь текст всегда будет красным. Стиль, которому добавлена
инструкция !important "перебивает" даже строчные стили, хотя у них самый
большой вес.
Но не используй запись !important слишком часто — твои коллеги будут
очень злится. Используй ее только в экстренных случаях, когда
конфликтующие стили нельзя переназначить другим способом.

223. Семантический HTML


Все, что до этого момента мы изучали, помогало нам решить одну большую
задачу - как выглядит наша веб-страница. Но не менее важным остается
другая сторона медали - как выглядит наш документ.
Для решения этой задачи служит отдельный подход, который называется
семантическая верстка. Согласно этого подхода, написание HTML кода должно
опираться на смысловое предназначение каждого блока. Это важно по
нескольким причинам:
o доступность. Для большинства людей браузер является отличным способом
просмотра страниц. Но для людей с нарушениями зрения такой вариант не
подходит и они используют скринридеры - инструмент, который “зачитывает”
содержимое страницы. При сементическом подходе скриндер лучше понимает,
какой именно сейчас блок и как об этом сказать пользователю. Таким
образом ты делаешь большому количеству людей свой сайт удобным.
o поисковая выдача. Поисковым работам легче понимать где что находится на
странице при поиске. Таким образом вырастает вероятность поисковой выдачи
страницы по определенному запросу.
o стандарты. Ну и для разработки семантическая верстка удобная, когда речь
идет о понимании коллегами структуры документа, который ты написал.

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

Структурные теги

Структурные теги помогают организовать основные части страницы, группируя


все остальные элементы в логические узлы.

header, footer, main

Какие логические блоки ты можешь выделить в следующем документе:


<!DOCTYPE>
<html>
<head>
<title>Семантика наше все</title>
</head>
<body>
<div id="header">Хедер документа</div>
<div id="main">Главный контент документа</div>
<div id="footer">Футер документа</div>
</body>
</html>
В документе можно выделить три логических блока: #header, #main, #footer.
Но что пользователь, что поисковый робот видит в документе просто три
блока <div>, хотя по идентификаторам можно понять, какие из блоков за что
отвечают. И тут нам на помощь приходит семантика:
<!DOCTYPE>
<html>
<head>
<title>Семантика наше все</title>
</head>
<body>
<header>Хедер документа</header>
<main>Главный контент документа</main>
<footer>Футер документа</footer>
</body>
</html>
Мы заменили использование тегов div на теги <header>, <main>, <footer>.
Каждый из этих тегов ведет себя точно также, как и div, при этом уже
имеет какое-то смысловое значение. Давай поговорим про каждый из них
подробнее:
o <header> - начало любого смыслового блока или всей страницы. Поскольку
элемент может использоваться в начале любого блока, количество их не
ограничено на одну страницу. Обычно это элементы повторяются на каждой
странице. Также навигацию по сайту стоит размещать внутри этого блока.
o <main> - основной блок страницы, принято, чтобы содержимое этого блока не
повторялось на других страницах, было уникальным.
o <footer> - противоположный тегу <header> - указывает на конец смыслового
блока или всей страницы.
section vs article vs div
Теперь надо уточнить, что такое этот смысловой блок. Указывается он с
помощью тега <section>, но не стоит его путать с блоком <div> и их все
заменять на <section>. По факту <section> объединяет в себе другие блоки
и внутри него желательно добавлять заголовок.
Кроме <section> существует еще тег <article>. Как и <section> - это
какая-то отдельная смысловая единица, которой стоит добавить заголовок.
Чаще всего используется для статьи, новости, комментария.
Как же тогда выбрать что использовать: <section> или <article>? И как
тогда быть с <div>?
Если ты не можешь добавить заголовок этому блок или разделу, тогда тебе
скорее всего нужен <div>. Таким блоком может быть, например, контейнер
модального окна и даже его логические части:
<div class="modal">
<div class="modal__overlay"></div>
<div class="modal__content"></div>
</div>
Если по смыслу ты можешь вынести этот блок на другой сайт - тогда
это <article>, какая-нибудь статья, для понимания смысла которой тебе
особо не важен контекст:
<article class="post">
<h1>New Tesla Car</h1>
<p>You will be able to drive about 1200 miles with new Tesla car</p>
</article>
На примере выше в блоке <article> добавлена статья про новый автомобиль
Tesla. Такая статья может быть вынесена на любой другой сайт и при этом
не потеряет своего смысла.
Соответственно, если вынести на другой сайт раздел нельзя, значит он
действительно является логической частью этой страницы и тебе стоит
использовать тег <section>:
<section class="product-description">
<header>Cool product</header>
<!-- some description -->
<section>
<section class="product-comments">
<header>People love our product</header>
<!-- some comments -->
<section>
К стати, на примере выше мы использовали тег <header> для обозначения
начала смыслового блока - в данном случае мы добавили заголовки блока.

aside
Для выделения второстепенного для страницы контента используй
тег <aside>. Этот тег часто ошибочно используют для какого-нибудь
контента, расположенного сбоку:
<main>
<p>JavaScript - очень интересный язык программирования</p>
<aside>
<p>
<a href="*">Регистрируйся</a> на онлайн конференцию по Python
</p>
</aside>
<p>Изучив JavaScript вы сможете разрабатывать сайты, мобильные
приложения, игры и многие другие вещи</p>
</main>

nav
Для размещения навигации, тоже есть отдельный тег - <nav>. Но стоит в нем
размещать только основную навигацию сайта, а дополнительные ссылки не
нужно:
<!DOCTYPE html>
<html>
<head>
<title>Семантика наше все</title>
</head>
<body>
<header>
<nav>
<!-- Эти ссылки представляют внутреннюю навигацию оборачиваем nav --
>
<ul>
<li>
<a href="*">Главная</a>
</li>
<li>
<a href="*">О Нас</a>
</li>
<li>
<a href="*">Каталог</a>
</li>
</ul>
</nav>
</header>
<main>
<!-- Эту ссылку не нужно оборачивать в тег nav -->
Подпишись на наш <a href="*">Facebook</a> чтобы быть в курсе
последних новостей
</main>
</body>
</html>

Теги контента

С контентом все намного проще. Везде, где формируются какие-либо списки,


нужно использовать списки <ul> и <ol>:
<!-- семантически неверно -->
<div>
<div>1. Элемент 1</div>
<div>2. Элемент 2</div>
<div>3. Элемент 3</div>
</div>

<!-- семантически верно -->


<ol>
<li>Элемент 1</li>
<li>Элемент 2</li>
<li>Элемент 3</li>
</ol>
Для абзацев, например, внутри статьи, надо использовать тег <p>. А если
тебе надо добавить цитату на страницу, используй тег <blockquote>:
<p>Как однажды сказал Виктор Гюго:</p>
<blockquote>Бог сотворил кошку для того, чтобы у человека был тигр,
которого можно погладить.</blockquote>

Выделение строчных элементов

Мы уже как-то рассматривали с тобой теги для выделение текста: <b> -


который делает тест жирным и <i> - отображаем текст курсивом. Но
семантические теги открывают намного больше возможностей:
o <strong> - для выделения важных слов. Внешне пользователь не увидит
отличий от тега <b>, это сугубо семантика.
o <em> - для выделенных слов - отображается курсивом.
o <abbr> - используется для аббревиатур:
<p>Ты должен знать <abbr title="Cascading Style Sheets">CSS</abbr>,
чтобы стать FrontEnd разработчиком</p>

Здесь при наведении на CSS появится подсказка с расшифровкой


аббревиатуры.
o <mark> - выделяет текст, по умолчанию изменяя его задний фон на
цвет yellow.
В этой лекции мы рассказали об основных семантических тегах, которые тебе
предстоит использовать. Весь список семантических тегов ты можешь
увидеть здесь.

224. Адаптивная верстка


Существует огромного количество устройств, на которых можно просматривать
веб страницы - от мобильных телефонов до огромных телевизоров и экранов.
И чем больше таких устройств твой сайт будет поддерживать, тем удобнее
будет пользователям. Сайт будет отображаться без искажений и не будет
вылазить за размеры экранов.

Виды верстки

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


адаптивных сайтов, давай поговорим о том, какие виды верстки вообще
существуют.

Фиксированная верстка
Самый простой способ верстки. При таком подходе размеры элементов
фиксируются с помощью свойств width и height, размеры при этом
указываются в px. Но нам такая верстка не подходит - как только элемент
не будет помещаться по ширине в границы экрана, снизу появится
горизонтальная прокрутка, а это очень не удобно для пользователя.

Резиновая верстка
При таком подходе элементы сайта могут менять свои размеры в зависимости
от размера окна браузера. Добиться этого можно с помощью свойств max-
width и min-width, а также указывая размеры элементов в процентах.

Адаптивная верстка (adaptive)


Если подстраивать элементы под размер экран, на котором отображается
страница - это уже адаптивная верстка. К особенностям этого подхода можно
отнести то, что подстраивать можно не только размеры элементов, а и
размер размер шрифта, отступы, цвет и все остальные стили, которые только
могут понадобится. Делается это с помощью media запросов (@media), о
которых мы поговорим немного позже.

Отзывчивая верстка (responsive)

Отзывчивая верстка объединяет в себе адаптивный и резиновый подходы.


Здесь элементы будут растягиваться и сжиматься пока это позволительно для
отображения на экране, но при этом эти рамки можно изменяться, адаптируя
под размеры экрана.

Media запросы

С помощью media запросов можно создавать адаптивные макеты, которые в


зависимости от размера экрана могут изменять стили элементов.

Давай рассмотрим следующий пример:


<!DOCTYPE>
<html>
<head>
<title>Media</title>
<style>
@media all and (max-width: 1400px) {
div {
width: 1200px;
}
}

@media all and (max-width: 760px) {


div {
width: 540px;
}
}
</style>
</head>
<body>
<div>Lorem ipsum</div>
</body>
</html>
Разберем стили в этом примере. С помощью правила @media мы указали, что
сейчас будут указаны стили для определенного размера экрана. Дальше мы
добавляем условия которые определяют ситуации в которых будут применяться
эти стили.
Правило @media никогда не существует само по себе. Ему всегда нужны
ограничения. В условии необходимо указать типы устройств, логические
операторы и media-функцию.
Оба медиа запроса будут применятся для всех типов устройств (носителей) -
за это отвечает условие all. После этого следует логический оператор and,
который указывает на объединение условий.
Дальше следует media-функция, которые указывают на технические
характеристики устройства, на котором отображается документ. Наши стили
мы будем использовать в зависимости от максимальной ширины устройства.
Определяем размеры - в первом случае стили применяются для устройств с
шириной не более 1400px, во втором - 760px.
Если устройство шириной меньше, чем 760px - все элементы <div> будут
шириной 540px. Если ширина устройства в диапазоне от 760px до 1400px -
все элементы <div> будут шириной 1200px.
Если же ширина устройства больше 1400px, то в данном случае никаких
изменений мы не указали и будут применены значения по умолчанию - блок
будет занимать всю ширину родительского блока.

Носители
Кроме значения all для определения носителей, которое применяет стили для
всех устройств, возможны следующие значения:
o screen – экран монитора;
o print – принтеры и другие печатающие устройства;
o projection – проекторы;
o handheld – смартфоны и аналогичные им аппараты;
o braille – устройства, основанные на системе Брайля, которые предназначены
для чтения слепыми людьми;
o embossed – принтеры, использующие для печати систему Брайля;
o speech – речевые синтезаторы, а также программы для воспроизведения
текста вслух;
o tty – устройства с фиксированным размером символов;
o tv – телевизоры.

Для указания нескольких видов устройств можно перечислить их через


запятую:
@media screen, print {
div {
width: 200px;
}
}
Здесь для экранов мониторов и принтеров ширина
элемента <div> будет 200px, вне зависимости от размеров устройств - мы не
указывали параметров ширины и, соответственно, нет ограничений. Для всех
других устройств (телевизоры, проекторы и так ) стили применяться не
будут.

Логические операторы
Кроме логического оператора and можно еще использовать not:
@media not print {
div {
width: 200px;
}
}

@media not all and (min-width: 400px) and (max-width: 800px) {


div {
color: red;
}
}
Первый media запрос сделает элементы <div> шириной 200px для всех
устройств, кроме принтеров. Второй запрос будет менять цвет текста на
красный для все устройств, когда их ширина меньше 400px или больше 800px.

media-функции
Как мы уже говорили ранее, media-функции указывают технические
характеристики устройств, для которых нужно изменить стили. Большинство
из них используют приставки min- и max-. Ранее мы уже использовали max-
width и min-width, но также можно зафиксировать какой-то конкретные
размер с помощью значения width (хотя применения такому особо нету).

Логично предположить, что можно определять нужные устройства и по высоте:


@media screen and (min-height: 200px) and (max-height: 500px) {
div {
color: red;
}
}
К стати, функции width и height работают со всеми носителями,
кроме speech.
Еще одна функция это aspect-ratio (min-aspect-ratio, max-aspect-ratio) -
определяет соотношение ширины и высоты отображаемой области устройства:
@media screen and (min-aspect-ratio: 1/4) and (max-aspect-ratio:
3/4) {
div {
color: red;
}
}
Применять можно для следующих
носителей handheld, print, projection, screen, tty, tv.
width, height и aspect-ratio определяют отображаемую область устройства и
работают со всеми носителями. Функции device-width, device-
height и device-aspect-ratio определяют всю доступную высоту и ширину
экрана устройства или печатной страницы.
Можно переопределять стили и в зависимости от ориентации экрана. Для
этого используй функцию orientation, у которой может быть два
значения: landscape (горизонтальное) или portrait (вертикальное).
Работает для следующих
носителей: handheld, print, projection, screen, tty, tv:
@media print and (orientation: landscape) {
div {
font-size: 16px;
}
}

@media print and (orientation: portrait) {


div {
font-size: 25px;
}
}

@media al and (min-aspect-ratio: 1/4) and (max-aspect-ratio: 3/4) {


div {
color: red;
}
}

Есть еще другие варианты функций, которые определяют разрешение экранов


или монохромность и к ним тебе стоит обратится тогда, когда тебе надо
будет.

Относительные размеры

Хорошей техникой при создании сайтов, которые хорошо будут отображаться


на разных устройствах, является использование относительных единиц.
Относительные единицы можно использовать для указания
свойств width, height (max- и min- тоже), margin, padding. Для всех этих
устройств можно использовать следующие относительные единицы: %, vw, vh и
т.д.
Также я советую тебе использовать относительные размеры и для размера
шрифта. При использовании rem и медиа запросов можно легко изменять
размеры на всей странице под разные размеры экрана:
div {
font-size: 2rem;
}

p {
font-size: 1.4rem;
}
@media screen and (max-width: 1400px) {
html: {
font-size: 19px;
}
}

@media screen and (max-width: 720px) {


html: {
font-size: 16px;
}
}
Можно использовать еще и em размерности, привязав размер шрифта к размеру
шрифта родительского компонента.

Mobile first

Уже в 2015 году количество запросов Google с мобильных устройств


равнялось количеству запросов с компьютеров. В связи с такой тенденцией
Google немного изменил алгоритм поисковой выдаче. Теперь, чтобы сайт
получал лучшие позиции он должен в первую очередь быть дружелюбным по
отношению к мобильным устройствам. От сюда и название к подходу.

Основные требования к этому подходу:

o важную информацию необходимо показать в самом начале;

o размеры станицы должны быть минимальными - скорость загрузки при


мобильном интернете может быть не очень высокой;

o должны загружаться только те ресурсы, которые используются на странице -


это влияет как на скорость загрузки, так и на стоимость: меньше сайт и
меньше ресурсов - меньше мобильный трафик и, соответственно, меньше
затрат для посетителя сайта;

o контент на разных версиях сайта (для мобильных экранов и компьютеров)


должен быть идентичным.

Библиотеки и фреймворки
Вокруг было темно. Я едва ли мог открыть глаза, как в голове зазвучал
знакомый голос.

— Доброе утро! - сказала Сигма


— И вам не хворать. Может отложим обучение на пару часов? Я бы еще
поспал.

— Сколько можно откладывать? На часы посмотри!

Я протер глаза и взглянул на будильник. Был полдень. Сквозь тонкую щель


между шторами пробивался плотный луч света.
— Ох и занесло меня. Действительно пора.

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


— Ты уже неплохо справляешься с задачами на обычном JS. Его можно
выполнять в любом браузере. - сказала Сигма

Недостаток обычного JavaScript в том, что большое веб-приложение на нем


написать сложно. А поддерживать и улучшать еще сложнее и дороже.

Необходимость создавать большие сайты и сложные веб-приложения - это одна


из основных причин появления JS библиотек и фреймворков. У них под
капотом реализовано множество функций, которые тебе иначе пришлось бы
писать с нуля и тратить на это кучу времени.

Давай начнем с различий между JavaScript библиотеками и фреймворками.

Библиотеки и фреймворки

Попробуем написать простой скрипт, в котором будет 4 функции. Они


описывают простейшие математические операции. Сложение, вычитание,
умножение и деление:
function add(a, b) {
return a + b;
}

function subtract(a, b) {
return a - b;
}

function multiply (a, b) {


return a * b;
}

function divide (a, b) {


return a / b;
}

Даже такой небольшой набор функций уже можно назвать библиотекой.


По сути, библиотека - это кем-то написанный код, который ты можешь
подключить к своему и использовать. И зачастую этот код облегчает тебе
жизнь, ведь теперь у тебя нет необходимости писать его самому.
— А фреймворк - это разве что-то другое?
Фреймворк — это тоже уже кем-то написанный код, который ты тоже можешь
использовать, чтобы облегчить себе жизнь.

Отличие между фреймворком и библиотекой состоит в том, кто чем управляет.

В случае с библиотекой ты сам определяешь поток своего приложения. Если


вернуться к нашей математической библиотеке - ты сам решишь когда тебе
нужно использовать функцию сложения, какие у нее буду аргументы. Ты даже
сам решишь нужно ли тебе вообще вызывать функцию сложения, или ты будешь
работать только с делением.

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

Библиотека React

React - это JavaScript библиотека для работы с UI (user interface


- пользовательский интерфейс). Эта библиотека была создана программистами
из Facebook и впервые опубликована в 2013.

С помощью React ты сможешь создавать веб-приложения, которые меняют


отображение без перезагрузки страницы. Их еще называют одностраничными
веб-приложениями или SPA от single-page application.

Такой подход увеличивает скорость реакции приложения на действия


пользователей, поскольку браузер будет менять только какие-то отдельные
компоненты приложения (после заполнения форм, применения фильтров,
добавления товаров в корзину).
Для расширения функциональности веб-приложения можно также использовать
другие библиотеки. Redux для управления состоянием приложения, React
Router - для управления роутингом (навигацией и перемещением пользователя
по страницам веб приложения).

У React есть несколько особенностей:


o React компоненты - наверное основная особенность React. Весь UI,
построенный с помощью React, состоит из компонентов, каждый из которых
представляет собой часть интерфейса. Объединяя компоненты, ты сможешь
создать завершенный UI.
o Cинтаксис JSX - это специальный вид JavaScript, который включает в себя
HTML. Добавление HTML в JS сильно упрощает разработку и чтение кода.
Например, нам не нужно добавлять теги script, чтобы объявить JavaScript
переменные или функции.
const Header = () => {
const title = 'Скорее бы приступить к коду';

return <h2>{title}</h2>
};
o Виртуальный DOM (Virtual Dom). Самые затратные операции в веб приложении
связаны с изменениями в DOM дереве - добавление какого-либо класса
элемента, или замена одних элементов другими. Для оптимизации этих
операций в React используется виртуальный DOM. Это объект, в котором
хранится информация и состоянии UI. Так React всегда знает какие элементы
как отображаются. Виртуальный DOM - это копия реального DOM. Все
изменения сначала происходят в этой копии (например удаление элемента
списка после нажатия кнопки), потом React рассчитывает разницу между
двумя объектами, обновляет реальный DOM. После обновления реального DOM
пользователь видит изменения.

Первое React приложение

Для создания первого React приложения мы воспользуемся самым простым


способом - npm модулем Create React App.
— Помниться, чтобы написать мой первый Hello, World!, понадобилось 7
лекций. А сейчас мы создаем приложение на Реакте?
— Все так. Прогресс в программировании - как взлет ракеты. Сложнее всего
набрать начальную скорость.

Первым делом установим нужный npm пакет. Открой терминал и выполни


команду
npm install -g create-react-app
Флаг -g - это сокращение от --global, то есть глобальной установки. Он
даст тебе возможность использовать команду create-react-app для создания
новых React проектов.
— Установка закончена, что делаем дальше?

— Теперь самый ответственный момент. Ты ведь знаешь, что в


программировании две основные проблемы?
— Какие?

— Именование переменных и инвалидация кеша.


— Не уверен, что понимаю о чем ты. Что может быть проще чем назвать новую
переменную?

— Не бери в голову. Со временем тебе все станет понятно.


Имя проекта ты можешь выбрать любое. Я бы советовала начать с чего-то
простого. Например, my-first-app.

Выполни в терминале команду


create-react-app my-first-app

и дождись ее завершения.

Это может занять пару минут в зависимости от производительности твоего


компьютера.
Как только проект будет создан, ты сможешь сразу запустить свое первое
приложение на React.js.

План такой:
o Найди директорию my-first-app
o Запусти терминал внутри этой папки и выполни команду npm run start
o Проект запустится и React приложение будет доступно по
адресу http://localhost:3000
o Переход на [http://localhost:3000](http://localhost:3000) должен
произойти автоматически. Если это не произойдет, напечатай адрес в
браузере самостоятельно.

JSX и элементы
На поверхности, то есть на http://localhost:3000, твое первое React
приложение выглядит очень просто. Но, если ты попробуешь копнуть глубже,
то увидишь много нового.
Шаблон React приложения начинается с файла App.js.

Внутри ты увидишь такой код:


function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
Тут объявлена функция App, которая возвращает что-то похожее на HTML.

Это "что-то", называется JSX или JavaScript Syntax Extension. Не смотря


на внешнее сходство с HTML, функционально это скорее JS.

JSX

Давай рассмотрим небольшой пример кода:


const element = <h1>Hello, world!</h1>;
С одной стороны может показать, что константа element - строка. Но это не
так.
— Конечно не так, в JavaScript все строки обернуты в кавычки.

— Правильно. Строки в JS, нужно обернуть в одинарные, двойные или


обратные кавычки.
Но это и не HTML разметка. Это - простейшие пример JSX кода. При
компиляции эта JSX конструкция превращается в следующий код:
var element = React.createElement("h1", null, "Hello, world!");
Писать каждый раз React.createElement было бы не очень удобно, поэтому
JSX (и не только), часто называют синтаксическим сахаром.

Синтаксический сахар - это такая конструкция языка программирования,


которая делает код более простым для понимания, но не обязательна и может
быть переписана с помощью более прямолинейных решений.
— Чем-то похоже на возможность написать стрелочную функцию в одну строку
и опустить ключевое слово return.

— Именно так. Не обязательно, но удобно.


Как видно из примера выше, "HTML часть" JSX элемента превращается в вызов
функции React.createElement.
Первый аргумент — это тип элемента. В данном случае — это заголовок h1.
Вторым аргументом идут свойства элемента (properties или props). О них мы
будем говорить много и подробно в следующих лекциях. Сейчас мы никакие
свойства не передаем, поэтому второй аргумент функции null.
Все последующие аргументы - это дочерний элементы текущего. Их может быть
множество. Но пока только один — текст Hello, world!

Давай рассмотрим более сложный пример:


// JSX
const jsxElement = <h1>Hello, <b>world!</b></h1>;

// compiles into
let createdElement = React.createElement(
"h1",
null,
"Hello, ",
React.createElement("b", null, "world!")
);
Теперь у заголовка h1 два дочерних элемента: текст "Hello, " и тег b со
своим внутренним текстом.
При этом тег b тоже был заменен на вызов функции React.createElement с
нужными аргументами.
— А я могу самостоятельно узнать во что трансформируются другие примеры
JSX кода?
— Можешь. И это будет очень полезно. Во что компилируются все JSX
сниппеты, ты можешь легко посмотреть в онлайн компиляторе Babel.

Отображение элемента

Мы разобрали, как создать базовый элемент. В простейшем случае - это


заголовок или параграф из одной строки без вложенных тегов. Теперь давай
разберемся как увидеть его на странице, открыв браузер.
— Вроде это просто, мне даже код не нужно было писать прошлый раз.
Было просто потому, что этот код написали за тебя создатели create-react-
app. Теперь будем разбираться как им это удалось.
Чтобы твои React элементы появились на веб-странице, нужна
библиотека react-dom. Она поможет добавить наш React элемент в реальное
DOM дерево. Тогда мы сможем увидеть его в браузере.
Саму библиотеку нам нет необходимости устанавливать - она уже входит
в create-react-app. Нам остается только ее использовать:
import React from 'react';
import ReactDOM from 'react-dom';

const element = <p>Hello, world!</p>;

ReactDOM.render(
element,
document.getElementById('root')
);

Давай разберемся построчно с примером выше.


o В первую очередь мы импортируем React. Это нужно делать всегда, когда ты
работаешь с JSX в JavaScript файле.
o Следующим модулем мы импортируем ReactDOM для рендеринга JSX элементов в
браузере.

o Потом пишем уже знакомый тебе JSX.


o Вызываем метод ReactDOM.render и начинается магия 🪄
Функция ReactDOM.render в нашем примере принимает 2 аргумента. Мы
вставляем наш React элемент element, как дочерний для указанного
контейнера.
— А откуда возьмется этот контейнер?
Контейнер мы передаем в качестве второго аргумента. Зачастую это
блок div с идентификатором root.
— Нет, я имею в виду, где в принципе объявлен этот div#root?
— Если ты еще находишься в проекте my-first-app, ищи
файл public/index.html. Это - шаблон для реакт приложения. Без JSX он не
функционален, но в него можно добавить мета теги, шрифты или скрипты
аналитики. В структуре файлов приложения, созданного с помощью create-
react-app можно увидеть использование ReactDOM.render в одном
единственном месте - src/index.js. Больше не нужно, так как React
приложению достаточно вызвать метод ReactDOM.render только один раз,
отображая все приложение.

Встраиваемые выражения

Предположим, мы хотим создать элемент, который будет показывать имя


пользователя.

Имя пользователя должно хранится в переменной, что бы мы могли его в


любой момент поменять. Делается это довольно просто:
import React from 'react';
import ReactDOM from 'react-dom';

const name = 'Hero!';


const greeting = <p>Hello, {name}!</p>;

ReactDOM.render(
element,
document.getElementById('root')
);
Теперь во время отображения этого элемента на экране
вместо {name} появится Hero! - все между фигурными скобками
интерпретируется как JS-выражение.
— Практически как шаблонные строки в обычном JavaScript. Только там мы
добавляли знак доллара перед открывающей фигурной скобкой.

— Верно. Таким образом можно встраивать в JSX не только строки, но и


любые JavaScript выражения, например функции (а потом сразу же их
вызывать):
import React from 'react';
import ReactDOM from 'react-dom';
const getName = () => 'Hero!';
const greeting = <p>Hello, {getName()}!</p>;

ReactDOM.render(
element,
document.getElementById('root')
);

Атрибуты в JSX

Принципиальных отличий между атрибутами элементов в JSX и HTML нет.

Давай попробуем добавить изображение:


import React from 'react';
import ReactDOM from 'react-dom';

const image = <img src="https://learn.coderslang.com/js-test-13.png"


/>;

ReactDOM.render(
image,
document.getElementById('root')
);
Как видно, добавление атрибута src для тега img ничем не отличается от
того, что мы с тобой уже делали не один раз. Но с JSX У нас появляется
возможность использовать JS выражения для атрибутов:
const altText = 'Coderslang';
const image = <img src="https://learn.coderslang.com/js-test-13.png"
alt={altText} />;
Тут мы используем фигурные скобки, чтобы добавить встраиваемое выражение.
В результате атрибут alt будет равен Coderslang.

Альтернативно, можно было бы просто написать "Coderslang" в двойных


кавычках.
Но помни, используй либо кавычки, либо скобки. Если использовать и то, и
и другое одновременно, то значение атрибута будет интерпретировано как
строка:
const altText = 'Coderslang';
// Здесь в качестве alt пользователь увидит строку altText, а не
Coderslang
const image1 = <img src="https://learn.coderslang.com/js-test-
13.png" alt={"altText"} />;
// Здесь в качестве alt пользователь увидит строку {altText}, а не
Coderslang
const image2 = <img src="https://learn.coderslang.com/js-test-
13.png" alt="{altText}" />;

Внимательно посмотри на пример выше. Это одна из самых распространенных


ошибок начинающих React программистов.

Одинарные теги в JSX

При написании одинарных тегов в JSX из обязательно нужно закрывать.


Например, тег img:
const image = <img src="" />
В HTML ты мог привыкнуть, что тег img одинарный и использовал его
так: <img>, но в JSX перед закрывающим символом > нужно добавить
символ / , чтобы закрыть тег.

С одной стороны это может показаться неудобным, но с другой стороны в JSX


появляется возможность использовать двойные теги как одинарные. Это
позволит сократить запись:
import React from 'react';
import ReactDOM from 'react-dom';

const someBlock = <div />;

ReactDOM.render(
someBlock,
document.getElementById('root')
);
А в HTML пришлось бы использовать два тега <div>. Открывающий и
закрывающий.

Сложные элементы

На всех примерах, которые мы рассматривали, элемент представлял из себя


один тег. Но с такими элементами далеко не уедешь - современные
интерфейсы очень сложные и элементы состоят из множества тегов. Они могут
быть как вложенными, так и расположенными на одном уровне.

Представь, что тебе нужно создать React элемент, который будет


представлять из себя статью состоящую из заголовка и одного абзаца. Как
бы ты это сделал?
— Пожалуй, попробовал бы написать функцию, которая принимает два
параметра text и title, и возвращает JSX из заголовка и параграфа.
const article = (title, text) => (
<h2>{title}</h2>
<p>{text}<p/>
);

— Неплохо. Принять два параметра нам действительно нужно. Иначе мы не


сможем сделать компонент универсальным. Но к сожалению твой код вызовет
ошибку.
— Плохо. А в чем проблема?

— Давай вернемся немного назад. Что происходит во время компиляции JSX?


Во что он преобразовывается?
— JSX будет сгенерирован в вызов метода React.createElement.
— Верно. И первым аргументом React.createElement всегда будет тип нашего
элемента.
В твоем же примере кода их получилось два: h2 и p.
Чтобы решить эту проблему достаточно обернуть эти теги в третий. Очень
часто для этого используют <div>, но так как мы создаем элемент для
статьи, то больше подойдет тег <article>:
const article = (title, text) => (
<article>
<h2>{title}</h2>
<p>{text}<p/>
</article>
);

— Понятно.

— Супер. Позже мы рассмотрим еще один вариант решения этого нюанса.

А пока запомни: React элемент должен представлять из себя один элемент с


любым количеством дочерних элементов

RJS React компоненты


Компоненты в React - это строительные блоки, из которых собирается UI. По
сути - компоненты - это функции, которые возвращают элементы, например:
// HelloWorld.js

function HelloWorld() {
return <div>Hello world!</div>
}

export default HelloWorld;


Здесь мы создали компонент HelloWorld и экспортировали его. Теперь его
можно импортировать и использовать в других JavaScript файлах.
Такой компонент называется функциональным, потому что представляет из
себя функцию. При компиляции мы увидим уже знакомый нам
метод React.createElement:
function HelloWorld() {
return React.createElement("div", null, "Hello world!");
}

Теперь нам надо использовать наш компонент. Раньше мы использовали только


элементы, которые представляют из себя теги HTML. Но также элементы могут
описывать и наши кастомные компоненты, то есть те, которые мы создали
сами:
// index.js

import React from 'react';


import ReactDOM from 'react-dom';
import HelloWorld from './HelloWorld.js';

const element = <HelloWorld />;

ReactDOM.render(
element,
document.getElementById('root')
);

Обязательное требование к компонентам - их название должно начинаться с


заглавной буквы. Это необходимо, что бы React не принял твой компонент за
обычный тег, которого на самом деле не существует.
Запись <HelloWorld /> в JSX является эквивалентом вызова
функции React.createElement. При компиляции element будет выглядеть
следующим образом:
var element = React.createElement(HelloWorld, null);
Как видишь, тип нашего элемента - HelloWorld. Второй аргумент сейчас
равен null, но там должны быть свойства элемента. Пришло время
разобраться, что это такое и как с ними работать.

Свойства элементов

Давай попробуем решить довольно простую задачу - создать


компонент Greeting, который будет отображать заголовок h1 с
текстом Hello, {name}!.
Начнем с файла Greeting.js, где мы создадим и экспортируем
компонент Greeting:
// Greeting.js
function Greeting() {
return <h1></h1>
}

export default Greeting;


Дальше, компонент Greeting мы используем в файле index.js:
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Greeting from './Greeting.js';

ReactDOM.render(
<Greeting />,
document.getElementById('root')
);
Таким образом мы создали компонент Greeting, который возвращает пока что
пустой заголовок <h1>, и в файле index.js выводим его на экран.
Теперь нам надо передать нашему компоненту Greeting имя пользователя,
которе будет выводиться на экран. Делается это следующим образом:
ReactDOM.render(
<Greeting name='John Doe' />,
document.getElementById('root')
);
Компоненту Greeting мы передали свойство name со значением John Doe.
Синтаксис точно такой же, как при указании атрибутов. Помнишь как мы
добавляли атрибут src для элемента img?
Следующим шагом будет получение этого свойства внутри
компонента Greeting:
function Greeting(props) {
console.log(props); // { name: 'John Doe' }
return <h1></h1>
}
Все свойство, которые ты передаешь компоненту, попадают в
объект props (от англ. properties). Все что тебе остается - получить их
из этого объекта и использовать:
function Greeting(props) {
return <h1>Hello, {props.name}!</h1>
}

Но и не стоит забывать, что объекты можно деструктурировать, тем более


что с помощью значений по умолчанию можно обезопасить компонент от того,
что свойство не будет передано:
function Greeting({ name = 'Hero'}) {
return <h1>Hello, {name}!</h1>
}
В таком случае, если при использовании компонента Greeting ты забудешь
передать свойство name , на экране ты увидишь не Hello, undefined!,
а Hello, Hero!.

В качестве свойств можно передавать не только строки, но и любой другой


тип данных:
// User.js
function User({
age,
name,
hasDog,
hasCat
}) {
return (
<div>
<div>First name: {name.firstName}</div>
<div>Last name: {name.lastName}</div>
<div>Age: {age}</div>
<div>Has dog: {hasDog}</div>
<div>Has cat: {hasCat}</div>
</div>
);
};

export default User;

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import User from './User.js';

ReactDOM.render(
<User
age={27}
hasDog
hasCat={false}
name={{firstName: 'Jane', lastName: 'Doe'}}
/>,
document.getElementById('root')
);
Обрати внимание на свойства hasDog и hasCat . У обоих свойств значение
булевое: у hasDog - true, у hasCat - false. Если значение свойства
является true - тебе не нужно его указывать явно, достаточно только
указать имя свойства, а значение будет присвоено автоматически.
Также с помощью свойств можно передавать и переменные. Например ты можешь
вынести объект name из предыдущего примера в отдельную переменную:
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import User from './User.js';
const name = {firstName: 'Jane', lastName: 'Doe'};

ReactDOM.render(
<User
age={27}
hasDog
hasCat={false}
name={name}
/>,
document.getElementById('root')
);

Рассмотрим еще такой пример:


// User.js
function User({
firstName,
lastName,
}) {
return (
<div>
<div>First name: {firstName}</div>
<div>Last name: {lastName}</div>
</div>
);
};

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import User from './User.js';

const data = { firstName: 'John', lastName: 'Doe' };

ReactDOM.render(
<User {...data} />,
document.getElementById('root')
);
Здесь мы применили спред (spread) оператор (...) для того, чтобы передать
свойства firstName и lastName компоненту User.

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


по очереди:
ReactDOM.render(
<User firstName={data.firstName} firstName={data.lastName} />,
document.getElementById('root')
);

Стоит также помнить одно простое правило - свойства компонентов нельзя


изменять:
// User.js
function User({
firstName,
lastName,
}) {
firstName = "Jane"; // так делать нельзя
return (
<div>
<div>First name: {firstName}</div>
<div>Last name: {lastName}</div>
</div>
);
};

Конечно, без возможности изменять данные для отображения элемента


невозможно построить крутой интерактивный UI. Но в этом нам помогут не
свойства, а состояние компонента. С ним мы познакомимся в следующей
лекции.

Что может возвращать компонент

До сих пор мы рассматривали компоненты, которые возвращают элементы.


function UserName({ firstName, lastName }) {
return <span>{firstName} {lastName}</span>
};

Но компоненты могут возвращать и другие компоненты:


function User({ firstName, lastName }) {
return <UserName firstName={firstName} lastName={lastName} />
};

Также компоненты могут возвращать строки и числа. В таком случае они


будут отображаться в браузере как текстовые узлы:
function NumberComponent() {
return 5;
};

function StringComponent() {
return 'some string';
};
Что бы ничего не отображать в браузере, можно вернуть из
компонента null, true или false. Булевые значения зачастую используются
для условного рендеринга.
function NothingComponent() {
return null;
}

Также можно возвращать массивы и фрагменты. Но об этих варианты, как и об


условном рендеринге я расскажу тебе чуть позже.
RJS Классовые компоненты и состояние
Классовый компонент

Давай попробуем создать компонент TimeCounter. Сейчас этот компонент


должен отображать значение переменной count:
import React from react;

function TimeCounter() {
let count = 0;

setInterval(() => {
count += 1;
}, 1000);

return (
<div>{count}</div>
);
}

export default ClickCounter;


Обрати внимание на setInterval - раз в секунду он должен увеличивать
значение переменной count на 1.
Наверное ты ожидаешь, что раз в секунду на странице в браузере цифра тоже
будет увеличиваться на 1, так это отображение значения переменной count.
Но это не произойдет.
React компонент будут перерисовываться только при изменении свойств или
состояния. count мы явно берем не из свойств (у нашего
компонента TimeCounter нет вообще свойств).

Теперь надо разобраться с состоянием. В чем-то состояние похоже на


свойства, но полностью контролируется компонентом, в котором оно
объявлено. До появления React Hooks (хуки), состояние можно добавить
только классовому компоненту.
Мы обязательно рассмотрим хуки в следующих лекциях, так как это подход,
на котором пишут современные React приложения. Однако понимание классовых
компонентов помогает лучше понимать как все работает.
Начнем с того, что перепишем наш компонент на классовый. Делать будем это
пошагово. Сначала будем рендерить пустой div:
import React from react;

class TimeCounter extends React.Component {


render() {
return <div />
}
}
Классовый компонент должен создавать, расширяя класс React.Component. У
классового компонента должен быть обязательно реализован метод render.
Как следует из названия, за отрисовку компонента отвечает именно он,
также он вызывается каждый раз, когда компонент нужно перерисовать.
Теперь добавим нашему компоненту состояние, в котором будем хранить нашу
переменную count, откуда и будем ее брать для отображения:
import React from react;

class TimeCounter extends React.Component {


constructor(props) {
super(props);

this.state = {
count: 0
}
}

render() {
return <div>{this.state.count}</div>
}
}
Состояние мы объявили в конструкторе (this.state).
Если у компонента объявлен конструктор, мы обязаны передать
свойства props родительскому классу (super(props)).
Можно и не использовать constructor для объявления состояния, если ты не
работаешь в нем с props:
import React from react;

class TimeCounter extends React.Component {


state = {
count: 0
}

render() {
return <div>{this.state.count}</div>
}
}

Изменение состояние

Также добавим метод компонент, который будет увеличивать count на 1:


import React from react;

class TimeCounter extends React.Component {


state = {
count: 0
}

handleCountIncrement = () => {
this.setState({ count: this.state.count + 1 });
}

render() {
return <div>{this.state.count}</div>
}
}
Метод handleCountIncrement мы объявили с помощью стрелочной функции. Это
позволяет использовать this внутри этого метода.
Для изменения полей состояния необходимо использовать метод setState.
Этот метод нам доступен благодаря тому, что наш компонент был создан из
класса React.Component. В таком случае после изменения состояние
компонент будет перерисован.

Если изменять состояние напрямую, компонент перерисован не будет:


handleCountIncrement = () => {
this.state.count += 1; // так делать не надо
}
Метод setState принимает объект с полями состояния, которые необходимо
изменить. Если поле не нужно менять, можно их и не указывать в этом
объекте:
state = {
fieldOne: 1,
fieldTwo: 2,
fieldThree: 3
}

handleFieldTwoUpdate = () => {
this.setState({ fieldTwo: 5 })
}
В результате вызова метода handleFieldTwoUpdate состояние будет
следующим:
state = {
fieldOne: 1,
fieldTwo: 5,
fieldThree: 3
}
Теперь нам надо добавить логику вызова метода handleCountIncrement один
раз в секунду. И здесь нам помогут методы жизненного цикла.
Методы жизненного цикла

Каждый раз, при первоначальном рендере нашего компонента мы должны


запускать setInterval.
Первоначальный рендер также называется монтирование (с англ. mount).
Такое название получилось потом, что компонент по сути монтируется
(встраивается) в DOM дерево. В этот момент вызывается метод жизненного
цикла componentDidMount:
import React from react;

class TimeCounter extends React.Component {


state = {
count: 0
}

componentDidMount() {
this.intervalID = setInterval(
this.handleCountIncrement,
1000
);
}

handleCountIncrement = () => {
this.setState({ count: this.state.count + 1 });
}

render() {
return <div>{this.state.count}</div>
}
}
Как только на компонент будет мортирован в DOM дерево будет вызван
метод componentDidMount, который запустит таймер и сохранит id таймера в
переменную this.intervalId.

Следующим шагом будет удаление таймера после того, как компонент будет
удален из DOM дерева.
Для этого нам надо использовать метод жизненного
цикла componentWillUnmount, который будет вызываться перед тем, как наш
компонент будет размонтирован (с англ unmount):
import React from react;

class TimeCounter extends React.Component {


state = {
count: 0
}

componentDidMount() {
this.intervalID = setInterval(
this.handleCountIncrement,
1000
);
}

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

handleCountIncrement = () => {
this.setState({ count: this.state.count + 1 });
}

render() {
return <div>{this.state.count}</div>
}
}

Таким образом мы рассмотрели два метода жизненного цикла компонента.


Каждый из ниx может сработать только один раз - или в момент
монтирования, или в момент размонтирования.

Но жизненный цикл компонента это не только монтирование и


размонтирование.
Во время всего жизненного цикла компонент может изменяться. Сразу после
каждого изменения компонента у него срабатывает метод жизненного
цикла componentDidMount.
Давай после каждого изменения компонента выводить в консоль слово tick:
import React from react;

class TimeCounter extends React.Component {


state = {
count: 0
}

componentDidMount() {
this.intervalID = setInterval(
this.handleCountIncrement,
1000
);
}

componentDidUpdate() {
console.log('tick');
}

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

handleCountIncrement = () => {
this.setState({ count: this.state.count + 1 });
}

render() {
return <div>{this.state.count}</div>
}
}
Теперь после каждого изменения, а оно происходит один раз в секунду, в
консоль будет выводится слово tick.
Остается не раскрытым один вопрос - как использовать свойства в классовом
компоненте. Все довольно просто - давай выведем свойства в консоль при
монтировании компонента:
import React from react;

class TimeCounter extends React.Component {


// ...

componentDidMount() {
// выводим свойства в консоль
console.log(this.props);
this.intervalID = setInterval(
this.handleCountIncrement,
1000
);
}
// ...
}

Давай сделаем так, что бы интервал счетчика задавался извне, через


свойства:
import React from react;
class TimeCounter extends React.Component {
// ...

componentDidMount() {
const { interval = 1000 } = this.props;

this.intervalID = setInterval(
this.handleCountIncrement,
Interval
);
}
// ...
}
Здесь мы деструктуризируем свойство interval и используем его в качестве
второго аргумента метода setInterval.
Обрати внимание, что мы обезопасили наш код, присвоив
переменной interval начальное значение в 1000 мс. Вдруг кто-то забудет
передать значение для этого важного свойства.

RJS Жизненный цикл React компонента


Мы уже рассмотрели наиболее используемые методы жизненного цикла. Все их
можно сгруппировать в три этапа жизненного цикла.

Давай рассмотрим все методы жизненного цикла подробнее в рамках каждого


этапа.

Этап 1: монтирование

Первым этапом будет монтирование компонента. В этом этапе могут быть


вызваны следующие методы (перечислены в порядке вызова):
o constructor()
o getDerivedStateFromProps()
o render()
o componentDidMount()
Первым методом, который будет выполняться в классов компоненте,
будет constructor. Часто в этом методе выполняется инициализация
состояния компонента.
getDerivedStateFromProps()
Дальше запускается метод getDerivedStateFromProps. Этот метод
предназначен для обновления состояния: из этого метода ты должен вернуть
или объект для обновления состояния, или null, чтобы ничего не обновлять.
class SomeComponent extends React.Component {
constructor(props) {
super(props);

this.state = {
name: 'John Doe',
}
};

static getDerivedStateFromProps(props, state) {


if(props.name !== state.name){
// изменяем состояние на основании свойства
return {
name: props.name
};
}

return null; // Не изменяем состояние


}

render(){
return (
<h1>Меня {this.state.name}</h1>
);
}
}
Как видно на примере выше, это метод получает для аргумента текущие
свойства компонента и предыдущее состояние. В нашем примере мы изменяем
поле состояния name только если оно не соответсвует такому же поля в
свойствах.
Сразу после getDerivedStateFromProps запускается метод render. С ним мы
уже знакомы - он возвращает JSX. После этого наш компонент монтируется в
DOM.
И уже после монтирования этого выполняется метод componentDidMount. В
этом методе нужно выполнять любые действия, связанные с наличием узла в
DOM. Также этот метод отлично подходит для выполнения запросов:
class UserName extends React.Component {
state = {
userName: undefined,
}

componentDidMount() {
const fetchedUserName = await fetch('getUserName');
this.setState({ userName: fetchedUserName });
}

render(){
return (
<h1>Пользователь {this.state.userName}</h1>
);
}
}
Дальше, при изменении свойств (props) или состояния (state), запускается
этап обновления.

Этап 2: обновление

Список методов второго этапа выглядит так:


o static getDerivedStateFromProps()
o shouldComponentUpdate()
o render()
o getSnapshotBeforeUpdate()
o componentDidUpdate()
Первым методом, который будет вызван на этом этапе
будет getDerivedStateFromProps(). Работает он точно также, как на этапе
монтирования.
shouldComponentUpdate()
Перейдем теперь к следующему методу - shouldComponentUpdate(). С помощью
этого метода можно проверить, нужно ли нам выполнять следующий рендер на
основании новых свойств и состояния. Их
имена shouldComponentUpdate получает в качестве своих аргументов.

Этот метод может быть очень полезен для избежания ненужных рендеров.
Давай рассмотрим пример:
// App.js
class App extends React.Component {
state = {
counterOne: 0,
counterTwo: 0,
}

setCounterOne = () => {
this.setState({ counterOne: this.state.counterOne + 1 })
}

setCounterTwo = () => {
this.setState({ counterTwo: this.state.counterOne + 2 })
}

render() {
return (
<div>
<CounterOne
value={this.state.counterOne}
onClick={this.setCounterOne}
/>
<CounterTwo
value={this.state.counterOne}
onClick={this.setCounterOne}
/>
</div>
);
}
}

// CounterOne.js
class CounterOne extends React.Component {
render() {
console.log('render');
return (
<div>
<h1>Counter one</h1>
<div>Value: {this.props.value}</div>
<button onClick={this.props.onClick}>Count</button>
</div>
);
}
}

// CounterTwo.js
class CounterTwo extends React.Component {
shouldComponentUpdate(nextProps) {
// Рендерим компонент только если изменилось свойство value

if (nextProps.value !== this.props.value) {


return true;
} else {
return false;
}
}

render() {
console.log('render');
return (
<div>
<h1>Counter two</h1>
<div>Value: {this.props.value}</div>
<button onClick={this.props.onClick}>Count</button>
</div>
);
}
}
Компонент CounterOne будет перерисовываться каждый раз, когда
перерисовывается компонент App. Даже если изменилось только
поле counterTwo состояния App.
Для компонента CounterTwo все несколько иначе. Он будет перерисовываться
только в случае изменения свойства value, которое равняется
полю counterTwo состояния App. Изменение свойства counterOne никак не
повлияет на рендер компонента CounterTwo.
В нем мы объявили метод shouldComponentUpdate(). В методе мы получаем
новые свойства в качестве аргумента nextProps.
мы сравниваем поле value из новых свойств с таким же полем из текущих
свойств (this.props). Если они отличаются - мы возвращаем true и у
компонента будет вызван метод render().
Если же поле value не изменилось, компонент не будет обновлен и этап
будет завершен.

Таким образом мы можем оптимизировать наше веб приложение, исключив


ненужные рендеры компонентов, которые на самом деле не изменились.
getSnapshotBeforeUpdate()
Продолжим в месте, где мы возвращаем true из
метода shouldComponentUpdate() (или просто не объявили его, что
равносильно возврату true постоянно).
Дальше выполняется метод render(). После рендера, будет вызван, если
объявлен, конечно, метод getSnapshotBeforeUpdate().
Этот метод получает два аргумента - prevProps и prevState (свойства и
состояние, которые применялись для предыдущего рендера).

Обычно его используют для получения какой-то информации из DOM -


положение прокрутки, размер или положение компонента и т.д.

Рассмотрим пример с получением текущего положения прокрутки:


class InfinteScrollWrapper extends React.Component {
getSnapshotBeforeUpdate(prevProps, prevState) {
// Возвразаем значениe вертикальное прокрутки окна
// Это значение будет переданно в
if (prevProps.list.length < this.props.list.length) {
return window.scrollY; // допустим, оно равняется 543
}

return null;
}

componentDidUpdate(prevProps, prevState, snapshot) {


// третим параметром получаем snapshot
// это то, что возвращается из getSnapshotBeforeUpdate

// теперь мы можем использовать это значение, например для корекции


прокрутки
// учти, что в этом месте список уже обновлен (дальше в лекции
объясняется почему)
console.log(snapshot); // в консоли увидим 543
}

render() {
return (
<div>Здесь мы булем рендерить огромный список из
{this.props.list.length} элементов</div>
)
}
}
Если тебе пришлось использовать этот метод, не забывай возвращать из него
или null, или значение снэпшота, которое ты сможешь потом использовать.
Все, что возвращается из метода getSnapshotBeforeUpdate, попадает в
метод componentDidUpdate.
componentDidUpdate()
Метод componentDidUpdate вызывается сразу после обновления компонента. Он
получает ряд аргументов, которые могут помочь тебе в реализации какой-то
логики (например, запроса для получения каких-то дополнительных
данных): prevProps - свойства предыдущего рендера, prevState - состояние
предыдущего рендера, snapshot - значение возвращаемое
из getSnapshotBeforeUpdate().

Рассмотрим следующий пример:


class Stars extends Component() {
state: {
list: [],
page: 1,
}

async componentDidMount() {
const { page } = this.state;
const fetchedList = await fetch(`/getStarsList/${page}`);

this.setState({ list: fetchedList });


}

async componentDidUpdate(prevProps, prevState) {


if (prevState.page !== this.state.page) {
const { page } = this.state;
const fetchedList = await fetch(`/getStarsList/${page}`);

this.setState({ list: fetchedList });


}
}

setPage = (newPage) => {


this.setState({ page: newPage });
}

render() {
const { page, list } = this.state;
return (
<div>
<div>Текущая страница: {page}</div>
<div>Элементов на странице: {list.length}</div>
<button onClick={() => this.setPage(1)}>Страница 1</button>
<button onClick={() => this.setPage(2)}>Страница 2</button>
</div>
);
}
}
Компонент Stars реализует загрузку списка звезд в зависимости от
страницы. Такой функционал ты наверняка встречал на различных форумах и
интернет магазинах. Это называется пагинацей - разбиение больших списков
на страницы для оптимизации запросов и рендера.
Изначально список пустой (поле list состояния компонента), а страница,
для которой нам надо получить данные - 1 (поле page состояния
компонента). В методе componentDidMount() мы получаем список звезд для
страницы 1 и отображаем количество элементов после их загрузки.
При нажатии на кнопку Страница 2, мы изменяем поле состояния page. После
этого срабатывает метод componentDidUpdate(). В нем мы проверяем,
поменялось ли значение поля page . Если поменялось - получаем список для
страницы 2 и обновляем отображение компонента.
В этом примере нам обязательно нужно проверять изменение именно
поля page, ведь кроме этого поля у нас изменяется поле list, а делать
новые запросы при изменении этого поля нам не нужно.
Обрати внимание, что после каждого обновления полей list и page этап
обновления запускается заново. Отличие для этих полей заключается лишь в
логике: для поля page мы выполняем и render, и запрос; для поля list -
только render.
Этап 3: размонтирование

Последний этап - размонтирование - запускается в момент удаления


компонента из DOM дерева.

Здесь мы немного забегаем наперед, но эта часть важна для понимания


жизненного цикла компонента: компонент может быть удален из DOM дерева
при переходе на другую страницу или при условном рендеринге, когда мы
решаем какой компонент рендерить при выполнении каких-то условий.
На этом этапе непосредственно перед удалением компонента из DOM
вызывается метод componentWillUnmount(). Этот метод нужен, когда
необходимо разорвать какие-то постоянные соединения или отписаться от
каких-либо событий:
class WithScrollLog extends React.Component {
componentDidMount() {
window.addEventListener('scroll', console.log);
}

componentWillUnmount() {
window.removeEventListener('scroll', console.log);
}

render() {
return <div>Какой-то очень высокий блок</div>
}
}
Открой консоль и обрати внимание когда мы убираем убираем слушатель с
события scroll с помощью метода removeEventListener

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


Обработчик в функциональном компоненте

Прежде чем перейти к событиям в React, давай вспомним как мы это делали в
HTML.
Представим, что у нас есть кнопка с текстом Click me. И в консоль будет
выводится фраза Button clicked:
<button onclick="handleClick()">Click me</button>
<script>
function handleClick() {
console.log('Button clicked');
}
</script>
Должно быть все ясно, как белый день. Пишем функцию handleClick где
выводим текст в консоль. Потом устанавливаем обработчик клика для кнопки
с помощью onclick="handleClick()"

А теперь перейдем к React. Напишем для начала реализацию такого


функционального компонента:
import React from 'react';

function Button() {
const handleClick = () => {
console.log('Button clicked');
}

return (
<button onClick={handleClick}>Click me</button>
)
}

Давай попробуем найти самые важные для нас отличия.


Первое из них — это атрибут onclick — он превратился в свойство onClick.
Свойства событий в React именуются в стиле camelCase.
Следующее отличие это само значение свойства onClick. Ранее для
атрибуты onclick мы передавали строку, которая соответствовала записи
вызова нужной функции (к названию handleClick добавлялись круглий
скобки () вызова функции). Здесь мы передаем имя функции, как мы
передавали любую JS переменную в свойствах.

Важно, что бы функция не вызывалась сразу - иначе она будет срабатывать


не при нажатии на кнопку, а при монтировании компонента.
Обработчик в классовом компоненте

Давай теперь функциональный компонент перепишем на классовый:


import React from 'react';

class Button extends React.Component {


handleClick() {
console.log('Button clicked');
}

render() {
return (
<button onClick={this.handleClick}>Click me</button>
);
}
}
Здесь обработчик нажатия вынесен в метод класса, поэтому для его
использования мы должны взять его из объекта this.
А теперь допустим нам надо выводить в консоль значение
свойства clickMessage. Перепишем наш компонент:
import React from 'react';

class Button extends React.Component {


handleClick() {
const { clickMessage = "Button clicked" } = this.props;
console.log(clickMessage);
}

render() {
return (
<button onClick={this.handleClick}>Click me</button>
);
}
}
На первый взгляд все правильно. Но при нажатии на кнопку это код вызовет
ошибку, которая звучит следующим образом: Cannot read property 'props' of
undefined.

Это происходит, потому что методы класса в JavaScript по умолчанию не


привязаны к контексту.
Для исправление этой ошибки раньше было принято явно привязывать методы в
контексту класса с помощью bind:
import React from 'react';

class Button extends React.Component {


constructor(props) {
super(props);
// вот и привязка
this.handleClick = this.handleClick.bind(this);
}

handleClick() {
const { clickMessage = "Button clicked" } = this.props;
console.log(clickMessage);
}

render() {
return (
<button onClick={this.handleClick}>Click me</button>
);
}
}

Но с появлением стрелочных функции в этом больше нет необходимости:


import React from 'react';

class Button extends React.Component {


handleClick = () => {
const { clickMessage = "Button clicked" } = this.props;
console.log(clickMessage);
}

render() {
return (
<button onClick={this.handleClick}>Click me</button>
);
}
}

В стрелочных функциях контекст привязан по умолчанию.

Передача аргументов в обработчики

Давай рассмотрим случай, когда у нас две кнопки, а обработчик один.


Задача следующая: обработчик должен выводить в консоль идентификатор
кнопки, а затем слово clicked:
import React from 'react';

class Button extends React.Component {


handleClick = (id) => {
console.log(`${id} clicked`);
}
render() {
return (
<div>
<button onClick={() => this.handleClick('First button')}>Click
me</button>
<button onClick={() => this.handleClick('Second button')}>Or click
me</button>
</div>
);
}
}
Здесь мы объявили для свойства onClick анонимную функцию, которая при
вызове вызывает наш обработчик с заранее определенными аргументами, в
данном случае своеобразным id.

Однако стоит учитывать, что при каждом изменении компонента (если бы у


нас было изменяемые состояние или свойства) такие функции будут
переопределятся. В нашем примере это ни на что не влияет, однако если бы
вместо кнопок были другие компоненты React, они бы без необходимости тоже
перерисовывались. Например:
import React from 'react';

function Button(props) {
console.log('I was rerendered, but nothing changed');
return (
<button onClick={props.onClick}>{props.label}</button>
);
}

class App extends React.Component {


state = {
clicks: 0,
};

handleClick = (id) => {


this.setState({ clicks: this.state.clicks });
console.log(`${id} clicked`);
}

render() {
return (
<div>
<Button
onClick={() => this.handleClick('First button')}
label='Click me'
/>
<Button
onClick={() => this.handleClick('Second button')}
label='Or click me'
/>
</div>
);
}
}
Когда мы увидим в консоли строку I was rerendered, but nothing changed?
Мы увидим ее при монтировании компонента, при чем увидим два раза для
каждой использования компонента Button внутри компонента App .
А дальше при нажатии на любую из кнопок, при чем снова таки два раза для
каждого компонента Button.
При нажатии на любую кнопку вы обновляем состояние компонента App -
увеличиваем на 1 значение поля clicks. Из-за этого
компонент App перерисовывается и для обоих кнопок свойство onClick каждый
раз новое, ведь создается новая анонимная функция. А при изменение
свойств компонент перерисовывается.

Поля ввода

Поля ввода необходимы для получения информации от пользователя. Давай


выполним довольно простую задачу - ввод данных пользователем в форме
логина и сохранение их в состояние. Полей будет два: почта и пароль. Плюс
кнопка Login:
import React from 'react';

class LoginForm extends React.Component {


state = {
email: '',
password: '',
};

render() {
const { email, password } = this.state;

return (
<form>
<input
value={email}
placeholder="Enter email"
/>
<input
value={password}
placeholder="Enter password"
type="password"
/>
<button>Login</button>
</form>
);
}
}
Мы добавили верстку нашей формы. Каждому полю ввода мы добавили
placeholder, точно также, как мы это делали для обычных HTML страниц.
Значение полей мы указываем с помощью свойства value для каждого поля
ввода. Сами значение берутся из состояния с начальными значениями в виде
пустых строк.

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


введенное значение в состояние.

Давай напишем обработчик сначала только для поля почты:


import React from 'react';

class LoginForm extends React.Component {


state = {
email: '',
password: '',
};

handleEmailChange = (event) => {


const { target: { value } } = event;

this.setState({ email: value });


};

render() {
const { email, password } = this.state;

return (
<form>
<input
value={email}
placeholder="Enter email"
onChange={this.handleEmailChange}
/>
<input
value={password}
placeholder="Enter password"
type="password"
/>
<button>Login</button>
</form>
);
}
}
При вводе пользователем текста в поле срабатывает событие onChange.
Именно этому свойству мы и указали наш обработчик handleEmailChange.
Аргументом функции обработчика является объект события (event).
В этом объекте нам доступны уже знакомые нам
поля: target , stopPropagation, preventDefault и так . Соответственно,
введенное пользователем значение email мы берем из
поля event.target.value и сохраняем в состояние компонента.

Добавим соответсвующий обработчик для поля с паролем:


import React from 'react';

class LoginForm extends React.Component {


state = {
email: '',
password: '',
};

handleEmailChange = (event) => {


const { target: { value } } = event;

this.setState({ email: value });


};

handlePasswordChange = (event) => {


const { target: { value } } = event;

this.setState({ password: value });


};

render() {
const { email, password } = this.state;

return (
<form>
<input
value={email}
placeholder="Enter email"
onChange={this.handleEmailChange}
/>
<input
value={password}
placeholder="Enter password"
type="password"
onChange={this.handlePasswordChange}
/>
<button>Login</button>
</form>
);
}
}
Теперь нам осталось только отправить нашу форму на проверку. Для этого
тегу form добавим обработчик события submit:
import React from 'react';
class LoginForm extends React.Component {
state = {
email: '',
password: '',
};

handleSubmit = (event) => {


event.preventDefault();
console.log(this.state);
}

handleEmailChange = (event) => {


const { target: { value } } = event;

this.setState({ email: value });


};

handlePasswordChange = (event) => {


const { target: { value } } = event;

this.setState({ password: value });


};

render() {
const { email, password } = this.state;
return (
<form onSubmit={this.handleSubmit}>
<input
value={email}
placeholder="Enter email"
onChange={this.handleEmailChange}
/>
<input
value={password}
placeholder="Enter password"
type="password"
onChange={this.handlePasswordChange}
/>
<button>Login</button>
</form>
);
}
}
При нажатии на кнопку у формы возникнет событие submit.
Мы вызвали метод preventDefault, что бы страница не перезагружалась. В
самом обработчике handleSubmit мы просто вывели наше состояние в консоль,
однако именно здесь можно организовать отправку данных на сервер для
проверки.

В следующей лекции мы рассмотрим другие элементы форм, а пока давай


перейдем к практике.

RJS Формы в React


Контролируемые и неконтролируемые поля

Давай вспомним пример из предыдущей лекции:


import React from 'react';

class LoginForm extends React.Component {


state = {
email: '',
password: '',
};

handleEmailChange = (event) => {


const { target: { value } } = event;

this.setState({ email: value });


};

handlePasswordChange = (event) => {


const { target: { value } } = event;

this.setState({ password: value });


};

render() {
const { email, password } = this.state;

return (
<form>
<input
value={email}
placeholder="Enter email"
onChange={this.handleEmailChange}
/>
<input
value={password}
placeholder="Enter password"
type="password"
onChange={this.handlePasswordChange}
/>
<button>Login</button>
</form>
);
}
}

Давай подробнее разберем как работают в этом примере поля ввода.

Начальное значение каждого поля мы берем из состояния приложения.

После ввода текста в любое из полей мы сохраняем новое значение в


состояние, а полю ввода передается новое значение для отображения (потому
что оно все так же берется из состояния).
Такие поля называются контролируемыми. В них мы постоянно контролируем
отображаемое значение.
Логично предположить, что если существуют контролируемые поля, то должны
быть и неконтролируемые.

В неконтролируемых полях отображаемое внутри поля значение контролируется


не веб-приложением, а браузером.

Мы же можем сохранять значение неконтролируемого поля только для


последующего использования.

Давай перепишем предыдущий пример с использованием неконтролируемых


полей:
import React from 'react';

class LoginForm extends React.Component {


state = {
email: '',
password: '',
};

handleEmailChange = (event) => {


const { target: { value } } = event;
this.setState({ email: value });
};

handlePasswordChange = (event) => {


const { target: { value } } = event;

this.setState({ password: value });


};

render() {
return (
<form>
<input
placeholder="Enter email"
onChange={this.handleEmailChange}
/>
<input
placeholder="Enter password"
type="password"
onChange={this.handlePasswordChange}
/>
<button>Login</button>
</form>
);
}
}

Как видишь, теперь мы не передаем значение полям ввода - только сохраняем


введенные в состояния.

Дальше мы можем использовать из данные из состояния, например, для


отправки на сервер.
— Как мне выбрать какой подход использовать?
Тут нет каких-то общих правил. Все зависит от поставленых задач. Например
в форме регистрации выше мы спокойно можем ограничится неконтролируемыми
полями. А вот если нам надо сделать поле ввода с кнопкой сброса - тогда
тут подойдет только контролируемое поле:
import React from 'react';

class ClearInput extends React.Component {


state = {
address: '',
};

handleAddressChange = (event) => {


const { target: { value } } = event;

this.setState({ address: value });


};

handleClear = () => {
this.setState({ address: '' });
};

render() {
const { address } = this.state;

return (
<div>
<input
value={address}
placeholder="Address email"
onChange={this.handleAddressChange}
/>
<button onClick={this.handleClear}>Clear address</button>
</div>
);
}
}
На примере выше поле ввода адреса контролируемое. Благодаря этому при
нажатии на кнопку Clear address мы очищаем поле address состояния и в
самом поле сбросится введенное значение.

textarea

В HTML для ввода текста можно использовать не только поле ввода input, но
и textarea для ввода большого количества текста.
Давай вспомним, как используется тег textarea в HTML:
<textarea>Some text inside text area</textarea>
Значение элемента textarea задается дочерним текстовым узлом.
В React с этим элементом прийдется работать как с input и устанавливать
значение с помощью атрибута value:
import React from 'react';

class Feedback extends React.Component {


state = {
feedback: '',
};

handleFeedbackChange = ({ target: { value } }) => {


this.setState({ feedback: value });
};

render() {
const { feedback } = this.state;

return (
<textarea
value={feedback}
placeholder="Leave your feedback"
onChange={this.handleFeedbackChange}
/>
);
};
}
Как видно на примере выше - использование textarea ничем не отличается от
использование input.
Использование select , кстати, тоже мало чем отличается. Давай
рассмотрим.

select

Давай напишем React компонент, который будет представлять из себя


выпадающий список стран:
import React from 'react';

class CountrySelect extends React.Component {


render() {
return (
<select>
<option value="Australia">Australia</option>
<option value="Belgium">Belgium</option>
<option value="Cambodia">Cambodia</option>
</select>
);
};
}
Теперь добавим этому компоненту логики. Она будет следующая: при выборе
страны значение должно сохраняться в состояние и оттуда передаваться
в select.
Все как у контролируемого input .
import React from 'react';

class CountrySelect extends React.Component {


state = {
selectedCountry: undefined
};

handleCountrySelect = (event) => {


const { target: value } = event;
this.setState({ selectedCountry: value });
};

render() {
const { selectedCountry } = this.state;
return (
<select value={selectedCountry} onChange={this.handleCountrySelect}>
<option value="Australia">Australia</option>
<option value="Belgium">Belgium</option>
<option value="Cambodia">Cambodia</option>
</select>
);
};
}
Всем элементам в примере выше - input, select и textarea - передается
свойство value с необходимым значением, и обработчик события изменения
этого значения onChange.
Ты наверное заметил, что в select мы передаем только одно значение. Как
же тогда быть с select, внутри которого можно выбрать несколько значение?
Давай немного изменим компонент CountrySelect, чтобы было возможно
выбрать несколько значений одновременно:
import React from 'react';

class CountrySelect extends React.Component {


state = {
selectedCountries: ["Australia", "Cambodia"]
};

handleCountrySelect = (event) => {


const {
target: { value }
} = event;
const { selectedCountries } = this.state;
if (selectedCountries.includes(value)) {
const index = selectedCountries.indexOf(value);

const newList = [...selectedCountries];


newList.splice(index, 1);

this.setState({ selectedCountries: newList });


} else {
const newList = [...selectedCountries, value];

this.setState({ selectedCountries: newList });


}
};

render() {
const { selectedCountries } = this.state;
console.log("selectedCountries", selectedCountries);
return (
<select
Multiple
value={selectedCountries}
onChange={this.handleCountrySelect}
>
<option value="Australia">Australia</option>
<option value="Belgium">Belgium</option>
<option value="Cambodia">Cambodia</option>
</select>
);
}
}
Чтобы разрешить выбирать несколько значение в выпадающем списке, небе
нужно добавить в select свойство multiple со значением true.
Как видишь, selectedCountries у нас теперь массив. Именно его мы и
передаем в качестве значения.
Но при обработке события onChange мы все равно получаем только одно
значение - то, по которому юзер кликнул. По этому в обработчике нам нужно
делать дополнительную проверку перед обновлением состояния.
В нашем случае это выглядит так: если selectedCountries уже содержит
выбранную пользователем страну, то она из это списка будет удалена, если
не содержит - добавлена.
Удаляется элемент с помощью метода slice. Для этого сначала была сделана
копия массива selectedCountries.
Копия нужна потому что состояние нельзя изменять напрямую, а
метод slice изменяет исходный массив.
По этой же причине при добавлении страны в selectedCountries мы также
делаем копию массива.

checkbox

Теперь разберем, как используются checkbox поля в React формах.

Представь, что тебе нужно реализовать чекбокс, чтобы определить согласен


ли пользователь с правилами и условиями использования веб-приложения.
class Confirm extends React.Component {
state = {
confirm: false,
};

handleConfrimChange = (event) => {


const { target: { checked } } = event;
this.setState({ confirm: checked });
};

render() {
return (
<div>
<label for="confirm">I agree to the terms of sevice</label>
<input
type="checkbox"
id="confirm"
checked={this.state.confirm}
onChange={this.handleConfrimChange}
/>
</div>
);
}
}
Для добавления checkbox поля мы указываем соответсвующее значение
атрибута type элемента input.
Значение поля теперь задаем с помощью атрибута checked. Этот атрибут
принимает булевые значения и для этого мы используем
поле confirm состояния компонента.
Изначально мы хотим, чтобы наше поле было не отмечено, для этого мы
указываем начальное значение false.
При срабатывании события onChange (которое возникает, когда пользователь
кликает по полю), вызывается метод handleConfrimChange.
Если текущее значение атрибута checked будет false, то в
методе handleConfrimChange мы получим объект события event, которые будет
содержать поле target.checked c противоположным значением - true.

Соответсвенно мы спокойно можем сохранить это поле в состояние для


изменения его значения.

RJS Условный рендеринг


React был разработан для создания сложных интерфейсов, с разнообразной
логикой и переходами.

Но насколько бы ни было сложным твое React приложение — это всегда


одностраничное приложение (SPA - single page application).
— Я уверен, что видел React приложение в котором менялся URL в браузере.
Там еще рассказывали о навигации в реакте.
— Конечно, есть дополнительная библиотека, которая обеспечивает различное
отображение для разных путей. И мы обязательно о всем поговорим. Но даже
с использованием этой библиотеки React останется одностраничным
приложением.

Понятнее будет рассмотреть на примере.

Давай представим что у нас есть две страницы:


o страница логина

o и страница профиля пользователя


При обычном подходе у тебя было бы две разные страницы - одна с
адресом /your-application/login и /your-application/profile.
Первой мы бы отображали пользователю страницу /your-applicatrion/login ,
а после успешного логина перенаправим на страницу /your-
application/profile. Все работает отлично.
Но у React приложения по умолчанию всего одна страница - /our-
application.

Таким образом нам надо будет отображать либо страницу логина, либо
страницу профиля.

Давай напишем компоненты для каждой страницы.

Начнем с формы логина.


// src/pages/Login.js

class Login extends React {


state = {
email: '',
password: '',
};

handleInputChange = (field) => (event) => {


const { target: { value } } = event;
this.setState({ [field]: value });
}

render() {
const { handleLogin } = this.props;
const { email, password } = this.state;

return (
<div>
<input
value={email}
placeholder="Email"
onChange={this.handleInputChange('email')}
/>
<input
value={password}
type="password"
placeholder="Password"
onChange={this.handleInputChange('password')}
/>
<button
disabled={!email || !password}
onClick={handleLogin}
>
Login
</button>
</div>
);
}
}

export default Login;

Разберем форму логина.


Тут все просто - два поля и кнопка.
Кнопка будет неактивна, пока не будут заполнены оба поля
- email и password.
Как только она станет активна, мы сможем нажать на нее и вызвать
метода handleLogin. Этом метод мы берем из свойств, а значит этот метод
нужно передать из родительского компонента.
Напишем теперь компонент Profile:
// src/pages/Profile.js

class Profile extends React {


state = {
userData: {
firstName: 'John',
lastName: 'Doe',
},
};

render() {
const { userData: { firstName, lastName } } = this.state;

return (
<div>
<h2>Welcome</h2>
<div>First name: {firstName}</div>
<div>Last name: {lastName}</div>
</div>
);
}
}

export default Profile;


Эта страница просто отображает данные пользователя которые хранятся
в state.
Теперь все надо собрать вместе. Делать это мы будем в уже знакомом тебе
компоненте App:
// src/App.js

import LoginPage from './pages/Login';


import ProfilePage from './pages/Profile';

class App extends React.Component {


state = {
userLoggedIn: false,
}

handleLogin = () => {
this.setState({ userLoggedIn: true });
}

render() {
return <LoginPage handleLogin={this.handleLogin} />;
}
}
Изначально наше приложение должно отображать форму логина. Поэтому мы в
методе render() возвращаем компонент LoginPage.
Чтобы форма логина работала, в свойствах мы передадим
функцию handleLogin для формы логина.
В методе handleLogin мы изменяем значение поля
состояния userLoggedIn на true. Это поле и говорит нам, что пользователь
залогинился в приложении.
А когда пользователь залогинен, то мы должны показать ему
компонент ProfilePage. Давай внесем нужные нам изменения в
метод render():
// src/App.js

import LoginPage from './pages/Login';


import ProfilePage from './pages/Profile';

class App extends React.Component {


state = {
userLoggedIn: false,
}
handleLogin = () => {
this.setState({ userLoggedIn: true });
}

render() {
const { userLoggedIn } = this.state;

if (userLoggedIn) {
return <ProfilePage />;
}

return <LoginPage handleLogin={this.handleLogin} />;


}
}
Разберем как теперь работает компонент App.
При первом рендере мы покажем пользователю форму логина. Это происходит
потому, поле userLoggedIn равняется false. Соответственно условие if не
выполняется и выполнится второй return, который и возвращает LoginPage.
Когда на форме пользователь нажмет кнопку Login , в состоянии
компонента App поле userLoggedIn изменится с false на true. Как ты уже
знаешь, при изменении состояния компонент будет перерисован.
При повторном вызове метода render условие if выполняется и теперь мы
возвращаем компонент ProfilePage.

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

Условный рендеринг может быть не только на уровне компонентов, когда мы


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

Встроенные условия

Давай рассмотрим встроенные условия на примере компонента Profile.


После блока Last name добавим блок Email.
Показывать его будет только в том случае, если есть соответсвующее поле
в userData:
// src/pages/Profile.js

class Profile extends React {


state = {
userData: {
firstName: 'John',
lastName: 'Doe',
},
};

render() {
const { userData: { firstName, lastName, email } } = this.state;

return (
<div>
<h2>Welcome</h2>
<div>First name: {firstName}</div>
<div>Last name: {lastName}</div>
{email && <div>Email: {email}</div>}
</div>
);
}
}

Мы специально не добавили поле email в объект userData. Соответственно


его значение в методе render() будет undefined, условие И (&&), которое
стоит после блока Last name не будет выполняться и блок Email не будет
показан.
Чтобы добавить встроенное условие в JSX, мы пользуемся тем же подходом,
что и для обычных переменных — окружаем и переменные и встроенные условия
фигурными скобками({}).
Также вместо логического оператора И можно использовать оператор ИЛИ.
Давай посмотрим на его применение:
// src/pages/Profile.js

class Profile extends React {


state = {
userData: {
firstName: 'John',
lastName: 'Doe',
},
};

render() {
const { userData: { firstName, lastName, email } } = this.state;

return (
<div>
<h2>Welcome</h2>
<div>First name: {firstName}</div>
<div>Last name: {lastName}</div>
<div>Email: {email || 'no email specified'}</div>
</div>
);
}
}
Здесь блок Email будет отображаться всегда. Но после двоеточия мы увидим
или значение переменной email, если оно будет присутствовать в state, или
строку no email specified, если поле email будет не определено.

Еще для условного рендеринга можно использовать тернарный оператор.


Снова немного изменим блок Email:
// src/pages/Profile.js

class Profile extends React {


state = {
userData: {
firstName: 'John',
lastName: 'Doe',
},
};

render() {
const { userData: { firstName, lastName, email } } = this.state;

return (
<div>
<h2>Welcome</h2>
<div>First name: {firstName}</div>
<div>Last name: {lastName}</div>
<div>{email ? `Email: ${email}` :'no email specified'}</div>
</div>
);
}
}

Здесь если переменная email будет определена, то мы увидим блок Email:


..., иначе — строку no email specified.

Переменные-элементы

Компоненты также можно сохранять в переменные для условного рендеринга.

Давай рассмотрим пример:


import Image from 'components/Image';
import Monogram from 'components/Monogram';

class Profile extends React {


state = {
userData: {
firstName: 'John',
lastName: 'Doe',
},
};

render() {
const { userData: { firstName, lastName, avatarUrl } } = this.state;

const userAvatar = avatarUrl


? <Image url={avatarUrl} />
: <Monogram firstName={firstName} lastName={lastName} />;

return (
<div>
<h2>Welcome</h2>
{userAvatar}
<div>First name: {firstName}</div>
<div>Last name: {lastName}</div>
</div>
);
}
}
Итак, если бы у нас в состоянии был адрес аватарки (avatarUrl), тогда мы
отобразили компонент Image . Но у нас его нет, поэтому мы будем
показывать монограмму (Monogram).
Переменную userAvatar мы можем использовать как и любую другую переменную
в render.
Кстати, этот пример можно немного переписать, разгрузив метод render.
Попробуем вынести часть функционала в функцию getAvatar:
import Image from 'components/Image';
import Monogram from 'components/Monogram';

class Profile extends React {


state = {
userData: {
firstName: 'John',
lastName: 'Doe',
},
};

getAvatar = () => {
const { userData: { firstName, lastName, avatarUrl } } = this.state;

if (avatarUrl) {
return <Image url={avatarUrl} />;
}

return <Monogram firstName={firstName} lastName={lastName} />;


}

render() {
const { userData: { firstName, lastName } } = this.state;

return (
<div>
<h2>Welcome</h2>
{this.getAvatar()}
<div>First name: {firstName}</div>
<div>Last name: {lastName}</div>
</div>
);
}
}
В методе getAvatar мы применяем условный рендеринг. Сам метод возвращает
нужный нам компонент, а вызов метод происходит непосредственно в
выражении return метода render (методы вызываются точно так же, как
используются любые переменные).

Прeдотвращение рендера

При применении различных условий помни, что React может проигнорировать


правую часть логических операторов (&& или ||), в зависимости от левой
части.
В таком случае именно левая часть оператора будет отображаться в
браузере:
function ComponentA() {
return <div>{0 && "some text"}</div>; // на экране мы увидим 0
}

function ComponentB() {
return <div>{false && "some text"}</div>; // на экране мы ничего не
увидим
}

function ComponentC() {
return <div>{undefined && "some text"}</div>; // на экране мы ничего
не увиди
}

function ComponentD() {
return <div>{0 || null}</div>; // на экране мы ничего не увидим
}

function ComponentE() {
return <div>{0 || true}</div>; // на экране мы ничего не увидим
}
Только для компонента ComponentA мы что-то увидим на экране. Для всех
остальных компонентов ничего не будет отрисовано.
В результате логических операторов React должен был бы
отобразить false , undefined, null или true. Но не одно их этих значений
не имеет графического отображения.

Это не строки, не компоненты и не элементы.

Соответственно ты можешь вернуть любое из этих значение, если хочешь что-


то скрыть во время рендеринга компонента:
class SomeComponent extends React.Component {
state = {
count: 0
}

componentDidMount() {
this.intervalID = setInterval(
this.handleCountIncrement,
1000
);
}

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

handleCountIncrement = () => {
this.setState({ count: this.state.count + 1 });
}

render() {
console.log(this.state.count);
return null;
}
}
При использовании SomeComponent пользователи ничего не увидят на
устройстве, потому что в методе render мы возвращаем null.

Но методы жизненного цикла продолжают работать.


В консоли ты сможешь раз в секунду увидеть новое число - свойство
состояния count. Вывод в консоль продолжит работать до тех пор, пока
активен интервал, который мы запустили в componentDidMount.

RJS Списки и фрагменты


При работе с интерфейсами нам часто приходится отображать различные
списки или таблицы.
Для примера, пусть есть список каких-то городов и нам нужно этот список
отрисовать:
const cities = ['New York', 'London', 'Sydney', 'Paris'];

function CitiesList() {
return (
<ul>
<li>{cities[0]}</li>
<li>{cities[1]}</li>
<li>{cities[2]}</li>
<li>{cities[3]}</li>
</ul>
);
}

Пример выше показывает как делать точно не надо.


Да, в браузере ты увидишь список из четырех элементов. Но это не
оптимально: код повторяется, при изменении количества элементов
в cities тебе нужно менять и CitiesList.

В React для отображения списка можно использовать массив.


Чтобы получить массив элементов <li> для рендера нам надо будет
использовать метод map:
const cities = ['New York', 'London', 'Sydney', 'Paris'];

function CitiesList() {
const listItems = cities.map(city => <li>{city}</li>);

return (
<ul>
{listItems}
</ul>
);
}
Здесь с помощью метода map мы проходим по списку городов и формируем
новый массив listItems, который состоит из элементов li.
Каждый элемент содержит название города в качестве внутреннего текста.
Сам listItems мы уже используем в фигурных скобках как и любую другую
переменную для рендера.

Этот код можно немного упростить:


const cities = ['New York', 'London', 'Sydney', 'Paris'];

function CitiesList() {
return (
<ul>
{cities.map(city => <li>{city}</li>)}
</ul>
);
}
Здесь мы не создаем никаких промежуточных переменных, а используем map в
выражении return.

Когда React рендерит списки, ему очень тяжело понять где какой элемент.
Из-за этого когда возникнет необходимость перерисовать список, потому что
он изменился (появился новый элемент или был удален какой-то старый),
React обновляет в DOM дереве абсолютно все элементы списка:
class CitiesList extends React.Component {
state = {
cities: ['New York', 'London', 'Sydney', 'Paris'],
};

handleRemoveLast = () => {
const { cities } = this.state;
const copy = [...cities];
copy.pop();
this.setState({ cities: copy });
}
render() {
const { cities } = this.state;
return (
<div>
<ul>
{cities.map(city => <li>{city}</li>)}
</ul>
<button onClick={this.handleRemoveLast}>Remove last</button>
</div>
);
}
}
При нажатии на кнопку Remove last из списка городов будет удален
последний.

Список будет перерисован и React будет вынужден удалить все элементы


списка из DOM дерева и монтировать их снова.

Эту проблему можно решить с помощью ключей.

Ключи помогают React сопоставить элементы "до" и "после", и изменять


только те, которые действительно изменились.

Ключи должны быть уникальными. В нашем случае подойдет название города:


class CitiesList extends React.Component {
state = {
cities: ['New York', 'London', 'Sydney', 'Paris'],
};

handleRemoveLast = () => {
const { cities } = this.state;
const copy = [...cities];
copy.pop();
this.setState({ cities: copy });
}

render() {
const { cities } = this.state;
return (
<div>
<ul>
{cities.map(city => <li key={city}>{city}</li>)}
</ul>
<button onClick={this.handleRemoveLast}>Remove last</button>
</div>
);
}
}
Каждому элементу li мы добавили атрибут key.

Теперь React сможет легко определять, какой элемент пропал из списка и


будет только его удалять из DOM дерева.

Когда тяжело сделать ключ уникальным, можно использовать индексы массива:


const cities = ['New York', 'London', 'Sydney', 'Paris'];

function CitiesList() {
return (
<ul>
{cities.map((city, index) => <li key={index}>{city}</li>)}
</ul>
);
}
Здесь мы использовали индексы, которые передаются как второй аргумент
колбэка map , в качестве ключей.
Но это плохой подход, поскольку порядок элементов в массиве может
поменяться, и тогда будут тратится лишние ресурсы на манипуляции с DOM.
— А как сделать все хорошо и правильно?

— На деле работа списками в React выглядит немного иначе.

Давай представим, что с сервера наше приложение получает список


криптовалют с их текущими ценами.

Напишем функцию, которая будет иммитировать такой запрос:


// src/services/cryptoService.js

export default async function() {


return [
{
id: 1,
name: "Bitcoin",
price: 50000,
},
{
id: 2,
name: "Ethereum",
price: 2000,
},
{
id: 3,
name: "Binance Coin",
price: 600,
},
];
}
Теперь напишем основу для компонента CryptoCoins, который будет
отображать список криптовалют:
// src/components/CryptoCoins.js

import cryptoService from "../services/cryptoService.js"

class CryptoCoins extends React.Component {


state = {
coins: [],
};

async componentDidMount() {
const coins = await cryptoService();
this.setState({ coins });
}

render() {
return null;
}
}
Как только компонент CryptoCoins будет монтирован, мы отправим запрос на
сервер для получения списка монет. Для этого мы используем написанный
ранее cryptoService.
Полученный результат сохраняем в состояние в поле coins.

Теперь нам надо отобразить наши данные. Отображать мы будет их в таблице,


для этого подготовим ее заголовок:
// src/components/CryptoCoins.js

import cryptoService from "../services/cryptoService.js"


class CryptoCoins extends React.Component {
state = {
coins: [],
};

async componentDidMount() {
const coins = await cryptoService();
this.setState({ coins });
}
render() {
return (
<table>
<tr>
<th>ID</th>
<th>NAME</th>
<th>PRICE</th>
</tr>
</table>
);
}
}

Следующий шаг - тело таблицы.

В нем надо для каждого элемента списка монет добавить ряд.


// src/components/CryptoCoins.js

import cryptoService from "../services/cryptoService.js"

class CryptoCoins extends React.Component {


state = {
coins: [],
};

async componentDidMount() {
const coins = await cryptoService();
this.setState({ coins });
}

render() {
const { coins } = this.state;
return (
<table>
<tr>
<th>ID</th>
<th>NAME</th>
<th>PRICE</th>
</tr>
{coins.map(coinData => (
<tr key={coindData.id}></tr>
))}
</table>
);
}
}
Для каждой монеты мы добавляем в таблицу элемент tr.
Обязательно указываем ключ key для каждого ряда.
Чтобы ключ мы уникален, мы используем поле id из объекта, описывающего
монету.

Последним шагом будет добавление колонок:


// src/components/CryptoCoins.js

import cryptoService from "../services/cryptoService.js"

class CryptoCoins extends React.Component {


state = {
coins: [],
};

async componentDidMount() {
const coins = await cryptoService();
this.setState({ coins });
}

render() {
const { coins } = this.state;
return (
<table>
<tr>
<th>ID</th>
<th>NAME</th>
<th>PRICE</th>
</tr>
{coins.map(coinData => (
<tr key={coindData.id}>
<td>{coindData.id}</td>
<td>{coindData.name}</td>
<td>{coindData.price}</td>
</tr>
))}
</table>
);
}
}
Внутри каждого ряда мы добавили три колонки с соответствующими данными
монеты: id, name, price.

На этом таблица закончена.


Фрагменты
Давай немного перепишем наш первый компонент - список городов - создав
два списка: ul и ol.
import React from 'react';

const cities = ['New York', 'London', 'Sydney', 'Paris'];

function CitiesList() {
return (
<ul>
{cities.map(city => <li>{city}</li>)}
</ul>
<ol>
{cities.map(city => <li>{city}</li>)}
</ol>
);
};

Как думаешь, будет ли работать такой код?


— Точно не знаю. Но если не будет работать, то похоже из-за того, что мы
возвращаем два элемента вместо одного?

— Верно. Компонент в React должен возражать один элемент.


Здесь же мы возвращаем их два, поэтому в консоли мы увидим ошибку
- Adjacent JSX elements must be wrapped in an enclosing tag.

Но что, если нам не нужен дополнительный элемент-обертка?


— Не знаю, не думал что можно выбирать.

— Если очень хочется, то можно использовать фрагменты вместо оберток


пустышек.

С помощью фрагментов мы можем легко возвращать несколько элементов или


компонентов без создания новых узлов в DOM дереве:
import React, { Fragment } from 'react';

const cities = ['New York', 'London', 'Sydney', 'Paris'];


function CitiesList() {
return (
<Fragment>
<ul>
{cities.map(city => <li>{city}</li>)}
</ul>
<ol>
{cities.map(city => <li>{city}</li>)}
</ol>
</Fragment>
);
};

Такой код работает правильно и DOM не будет перегружен лишними узлами.

Также для фрагментов существует упрощенная запись, не требующая


дополнительного импорта:
import React, { Fragment } from 'react';

const cities = ['New York', 'London', 'Sydney', 'Paris'];

function CitiesList() {
return (
<>
<ul>
{cities.map(city => <li>{city}</li>)}
</ul>
<ol>
{cities.map(city => <li>{city}</li>)}
</ol>
</>
);
};
Здесь мы заменили компонент <Fragment></Fragment> на <></>. Эти две
записи эквивалентны, ты можешь выбрать ту которая тебе больше нравится.
— Похоже на то как в однострочной стрелочной функции можно писать return,
а можно и не писать.

Обрати внимание еще на один момент мы два раза повторяем один и тот же
код.

Давай вынесем создание списка в отдельный компонент:


import React, { Fragment } from 'react';

const cities = ['New York', 'London', 'Sydney', 'Paris'];


function Items({ listType }) {
return cities.map(city => <li key={`${listType}-
${city}`}>{city}</li>);
}

function CitiesList() {
return (
<Fragment>
<ul>
<Items listType="ul" />
</ul>
<ol>
<Items listType="ol" />
</ol>
</Fragment>
);
};

Как думаешь - такой код будет работать? И ответ - да, будет.


Компонент Items не будет генерировать никакой ошибки, так как этот
компонент возвращает массив, а React знает что с ними делать.
Давай в компоненте Items после каждого элемента li добавим горизонтальную
линию-разделитель:
function Items({ listType }) {
return cities.map(city => (
<>
<li key={`${listType}-${city}`}>{city}</li>
<hr />
</>
));
}
Здесь нам уже прийдется для каждого элемента массива добавить фрагмент.
Для каждого элемента массива будет вызван метод React.createElement и в
качестве первого аргумента будет передан React.Fragment.
Но все ли верно с нашим кодом? Здесь мы указываем key для тега li.
Но ведь если список будет изменять, то у нас изменится не только
элемент li, а весь фрагмент, в который он входит.
Например мы удалим какой-то город из массива cities. Тогда React удалит
не только соответствующий li из DOM дерева, но и hr . А это равносильно
удалению всего фрагмента.
В таком случае лучше добавить key для всего фрагмента:
import React, { Fragment } from 'react';

function Items({ listType }) {


return cities.map(city => (
<Fragment key={`${listType}-${city}`}>
<li>{city}</li>
<hr />
</Fragment>
));
}
Один нюанс — ты не можешь использовать свойства с упрощенной записью
фрагмента (<></>). Поэтому, чтобы добавить key, нужно использовать полную
запись Fragment.

RJS Дочерние компоненты и PropTypes


Children

Компоненты в React - отличный способ писать красивый код, который легко


переиспользовать.
Давай представим себе такой компонент: это будет какая-то карточка с
заголовком и текстом. В нашем приложении мы будем использовать ее в самых
разных местах.
Заголовок карточки мы будем задавать с помощью свойства title.
Начнем с одного элемента <div> и тега <h2> внутри:
function ContentCard({ title }) {
return (
<div>
<h2>{title}</h2>
</div>
);
}

Отлично, теперь надо добавить контент этой карточке. Но заранее он нам не


известен, мы каким-то образом должны его передать.
Для этого давай зайдем с другой стороны и посмотрим, как мы будем
использовать ContentCard в компоненте App.
function App() {
return (
<>
<ContentCard title="Данные пользователя">
<div>Имя: Джон</div>
<div>Фамилия: Доу</div>
</ContentCard>
<ContentCard title="История дейсвия">
<ul>
<li>01.01.1999 - Регистрация</li>
<li>02.01.1999 - Вход</li>
</ul>
</ContentCard>
</>
);
}
Здесь компонент ContentCard используется два раза.

В каждом случае у него есть открывающий и закрывающий тег.

А между тегами мы указали контент для каждой карточки.

Осталось это контент разместить внутри карточки.


Получить контент в компоненте ContentCard мы можем с помощью
свойства children:
function ContentCard({ title, children }) {
return (
<div>
<h2>{title}</h2>
<div>{children}</div>
</div>
);
}
Все дочерние элементы, которые мы указали внутри ContentCard собираются
вместе в свойство children , которое мы просто используем как переменную.
Такой подход называется композиция. Благодаря ему тебе не прийдется
постоянно копировать код, который мы написали в компоненте ContentCard,
что бы насоздавать кучу практически одинаковых компонентов.
У нас будет один компонент ContentCard.

И если нам надо будет внести изменения во все карточки, мы сделаем это
просто в одном месте.

PropTypes

Чем больше будет твое приложения, тем у тебя больше будет компонентов с
самым различным набором свойств. И когда их станет слишком много, тебе и
твоим коллегам будет все тяжелее понимать какие свойства нужно передать
тому или иному компоненту, какого типа должны они быть.
В React для этого предусмотрен специальных механизм. Давай рассмотрим
просто пример - компонент UserProfile.

Сейчас нам совсем не важно, что рендерит этот компонент, нам нужны только
его свойства:
function UserProfile({
firstName,
lastName,
age,
...rest
}) {
return null;
}
Здесь мы деструктурировали некоторые свойство, остальные собрали в
объект rest c помощью оператора ...

В таком случае весь список свойств нам не очевиден, а хотелось бы все


таки понимать что к чему.
Для этого компоненту UserProfile нам надо добавить свойство propTypes:
import PropTypes from 'prop-types';

function UserProfile({
firstName,
lastName,
age,
...rest
}) {
return null;
}

UserProfile.propTypes = {
firstName: PropTypes.string,
lastName: PropTypes.string,
email: PropTypes.string,
age: PropTypes.number,
};
Свойство propTypes - это объект. Ключами такого объекта должны быть
свойства компонента, а значениями - их тип, взятый из объекта PropTypes.
Таким образом мы указали, что наш компонент может получить четыре
свойства - строки firstName, lastName и email , а также число age.

Что будет, если в компонент мы передадим свойство с неверным типом?


Например в свойстве age вместо числа будет строка:
function App() {
return (
<UserProfile age="some string" />
);
}
В этом случае в консоли будет выведено предупреждение: Failed prop type:
Invalid prop age of type string supplied to UserProfile, expected number.

Таким образом ты (или твой коллега) может заметить ошибку и исправить.


Также обрати внимание - кроме свойства age мы не передали никаких
дополнительных свойств.
И касательно этого никакого предупреждения мы не увидим. Так происходит,
потому что все свойства, указанные в propTypes не являются обязательными
по умолчанию.

Но часто бывает, что компонент не сможет правильно работать без каких-то


свойств.
Давай сделаем свойство email обязательным добавив модификатор isRequired
import PropTypes from 'prop-types';

function UserProfile({
firstName,
lastName,
age,
...rest
}) {
return null;
}

UserProfile.propTypes = {
firstName: PropTypes.string,
lastName: PropTypes.string,
email: PropTypes.string.isRequired,
age: PropTypes.number,
};
Теперь, если ты забудешь передать свойство email в консоли появится
соответствующее предупреждение: Failed prop type: The prop email is
marked as required in UserProfile, but its value is undefined.
Точно таким же образом можно указать любое свойство как isRequired. Это
работает одинаково для всех типов.
Давай немного изменим структуру свойств UserProfile и все поля будет
передавать внутри объекта data.
Тогда нам надо переписать propTypes:
import PropTypes from 'prop-types';

function UserProfile({
data: {
firstName,
lastName,
age,
...rest
}
}) {
return null;
}
UserProfile.propTypes = {
data: PropTypes.object,
};
Мы указали объект data как единственное свойство и выставили
тип PropTypes.object

Ошибки не будет, но такой подход не информативен.


Если тебе известны поля объекта data, лучше использовать
не PropTypes.object, а указать форму этого объекта (типы всех вложенных
полей) с помощью PropTypes.shape:
import PropTypes from 'prop-types';

function UserProfile({
data: {
firstName,
lastName,
age,
...rest
}
}) {
return null;
}

UserProfile.propTypes = {
data: PropTypes.shape({
firstName: PropTypes.string,
lastName: PropTypes.string,
email: PropTypes.string.isRequired,
age: PropTypes.number,
}),
};
PropTypes.shape - это метод которые принимает в качестве аргумента
объект. Этот объект является схожим с самим propTypes - ключи, это
свойства этого объекта, а значения - тип, указанный с помощью PropTypes.
Теперь React будет проверять поля свойства data и в случае необходимости
выбрасывать предупреждения.

Точно также мы можем проверять и массивы в React.


Например у нас будет второе свойство permissions. Это будет массив строк,
в котором мы будет передавать части приложения, к которым есть доступ у
пользователя:
import PropTypes from 'prop-types';

function UserProfile({
data: {
firstName,
lastName,
age,
...rest
},
permissions,
}) {
return null;
}

UserProfile.propTypes = {
data: PropTypes.shape({
firstName: PropTypes.string,
lastName: PropTypes.string,
email: PropTypes.string.isRequired,
age: PropTypes.number,
}),
permissions: PropTypes.arrayOf(PropTypes.string),
};
Здесь мы используем метод PropTypes.arrayOf, что бы показать - все
элементы внутри permissions должны быть строками.

Точно так же мы можем указать, что элементы должны быть числами,


объектами или даже другими массивами.
Теперь давай добавим тип для свойства role. Это поле - строка, но у нее
может быть только два значения: admin или user.
import PropTypes from 'prop-types';

function UserProfile({
data: {
firstName,
lastName,
age,
...rest
},
permissions,
role,
}) {
return null;
}

UserProfile.propTypes = {
data: PropTypes.shape({
firstName: PropTypes.string,
lastName: PropTypes.string,
email: PropTypes.string.isRequired,
age: PropTypes.number,
}),
permissions: PropTypes.arrayOf(PropTypes.string),
role: PropTypes.oneOf(['admin', 'user']).isRequired,
};
Метод PropTypes.oneOf принимает массив в качестве аргумента, а элементы
этого массива это значения, которые может принимать это свойство.
Также бывает, что переданное свойство может разных типов. Чтобы расширить
спектр допустимых типов используй метод PropTypes.oneOfType:
import PropTypes from 'prop-types';

function UserProfile({
data: {
firstName,
lastName,
age,
...rest
},
permissions,
role,
extraField,
}) {
return null;
}

UserProfile.propTypes = {
data: PropTypes.shape({
firstName: PropTypes.string,
lastName: PropTypes.string,
email: PropTypes.string.isRequired,
age: PropTypes.number,
}),
permissions: PropTypes.arrayOf(PropTypes.string),
role: PropTypes.oneOf(['admin', 'user']).isRequired,
extraField: PropTypes.oneOfType([
PropTypes.array,
PropTypes.bool,
PropTypes.func,
]),
};
Поле extraField может быть массивом, функцией или принимать булевое
значение, так как в oneOfType мы указали
типы: PropTypes.array, PropTypes.func и PropTypes.bool соответственно.
Для свойства children можно не указывать тип.

Сделать это стоит только в одном случае - если тебе надо ограничить это
свойство одним элементом:
import PropTypes from 'prop-types';

function WithChildren({ children }) {


return null;
}

WithChildren.propTypes = {
children: PropTypes.element.isRequired
};

function App() {
return (
<WithChildren>
<div>Первый дочерний элемент</div>
<div>Второй дочерний элемент</div>
</WithChildren>
);
}
В консоль мы увидем предупреждение: Failed prop type: Invalid prop
children of type array supplied to WithChildren, expected a single
ReactElement.
Это предупреждение говорит нам, что children должен быть элементом.

Следующий код такого предупреждения не вызовет:


import PropTypes from 'prop-types';
function WithChildren({ children }) {
return null;
}

WithChildren.propTypes = {
children: PropTypes.element.isRequired
};

function App() {
return (
<WithChildren>
<div>
<div>Первый дочерний элемент</div>
<div>Второй дочерний элемент</div>
</div>
</WithChildren>
);
}

defaultProps

Бывает нужно какому-то свойству указать значение по умолчанию, на случай,


если оно не было передано.

Добавить значение по умолчанию можно двумя способами.

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


import PropTypes from 'prop-types';

function UserProfile({
role = 'user',
}) {
return null;
}

UserProfile.propTypes = {
role: PropTypes.oneOf(['admin', 'user']).isRequired,
};
Здесь мы свойству role указываем значение user, если такое свойство не
передали.

Но в React есть встроенный механизм для добавления дефолтных свойств:


import PropTypes from 'prop-types';

function UserProfile({ role }) {


return null;
}

UserProfile.propTypes = {
role: PropTypes.oneOf(['admin', 'user']).isRequired,
};

UserProfile.defaultProps = {
role: 'user',
};
Если тебе нужно указать значение свойства по умолчанию, нужно добавить
свойство defaultProps компонента.
Как и propTypes - это объект. Ключи объекта - это свойства (их названия).
Значения ключей - соответствующие значения которые будут использованы по
умолчанию, то есть если какие-то свойства не будут переданы.

RJS Стили в React


Мы уже немного научились писать React компоненты, логику их работы и
отображения. Но кроме правильно работающего приложения, хочется что бы
оно было красивым.

Давай рассмотрим, как добавлять React компонентам CSS стили.


Строчный CSS

В React, как и в обычном HTML коде, можно добавлять стили с помощью


атрибута style.

Отличие заключается в том, что здесь стили указываются в объекте.

Ключами такого объекта будут свойства CSS:


function FlexWrapper({ children }) {
return (
<div style={{
display: 'flex',
justifyContent: 'center',
backgroundColor: '#e3e3e3',
}}>
{children}
</div>
);
}
Если свойство стилей состоит из нескольких слов, записывать надо в
camelCase (как свойства justifyContent и backgroundColor).

Это важное отличие от HTML, который не чувствителен к регистру.

Также можно выносить объект стилей в отдельную переменную:


const wrapperStyles = {
display: 'flex',
justifyContent: 'center',
backgroundColor: '#e3e3e3',
};

function FlexWrapper({ children }) {


return (
<div style={wrapperStyles}>
{children}
</div>
);
}
Здесь мы вынесли стили FlexWrapper в переменную wrapperStyles и передали
ее в свойстве style элемента <div>.

Также с помощью строчных стилей в React можно формировать CSS,


основываясь на свойствах или логике:
class FlexWrapper extends React.Component {
state = {
justifyContent: 'flex-start',
};
getStyles = () => {
const { backgroundColor = '#e3e3e3' } = this.props;
const { justifyContent } = this.state;
return {
display: 'flex',
justifyContent,
backgroundColor,
};
};

handleAlignButtonClick = (val) => () => {


this.setState({ justifyContent: val });
};

render() {
const { children } = this.props
return (
<>
<div style={this.getStyles()}>
{children}
</div>
<button
onClick={this.handleAlignButtonClick('flex-start')}
>
Align Left
</button>
<button
onClick={this.handleAlignButtonClick('flex-end')}
>
Align Right
</button>
</>
);
}
}
Обрати внимание на метод getStyles. Этот метод формирует объект стилией.
o Свойство display всегда равно flex.
o Значение свойства backgroundColor мы берем из свойств компонента, при
этом указываем значение по умолчанию #e3e3e3.
o Свойство justifyContent мы берем из состояния, потому что этот стиль мы
можем изменять с помощью нажатий кнопки.

Внешние стили

В React можно спокойно вынести стили в отдельный файл. Формат стилей


никак не изменится:
/* FlexWrapper.css */

div {
background-color: #e3e3e3;
}

.flex-wrapper {
display: flex;
justify-content: center;
}

import './FlexWrapper.css';

function FlexWrapper({ children }) {


return (
<div className="flex-wrapper">
{children}
</div>
);
}

После того, как ты вынес стили в отдельный CSS файл, их нужно


импортировать в файл компонента.
import './FlexWrapper.css';

После этого нужно добавить CSS селектор.


Здесь мы используем класс flex-wrapper.
<div className="flex-wrapper">
Для добавления класса элементу мы заменяем атрибут class на className,
потому что class — это зарезервированное слово в JS.

В CSS файле ты можешь использовать любые селекторы, которые мы изучали в


разделе CSS.

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

Из-за этого такой способ лучше использовать для добавления глобальных


стилей.
Для добавления стилей отдельному компоненту лучше использовать CSS
модули.

CSS модули

Начнем с создания CSS модуля.


Файл стилей из предыдущего примера стоит переименовать
на FlexWrapper.module.css.

При импорте такого файла, мы получаем объект со стилями.

Ключами такого объекта будут классы, объявленные в CSS модуле:


/* FlexWrapper.module.css */

.flex-wrapper {
background-color: #e3e3e3;
display: flex;
justify-content: center;
}

import styles from './FlexWrapper.module.css';

function FlexWrapper({ children }) {


return (
<div className={styles['flex-wrapper']}>
{children}
</div>
);
}
Стили, добавленные таким образом будут применяться только для
компонента FlexWrapper.

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


Какой-то такой результат ты увидишь при исследовании элемента в dev
tools:
<div class="FlexWrapper_flex-wrapper_er4tr"></div>
Сгенерированный класс будет начинаться с названия компонента, в котором
он применяется.
Дальше следует символ _ и класс, который мы объявили в CSS модуле. Потом
снова следует _ и какой-нибудь хеш.

Часто приходится добавлять несколько классов компоненту.

Давай объявим в CSS модуле несколько классов.


/* FlexWrapper.module.css */

.flex-wrapper {
background-color: #e3e3e3;
display: flex;
justify-content: center;
}
.title {
font-size: 34px;
}
Чтобы добавить несколько CSS классов используя CSS модули придется
объединить их названия в одну строку, которую мы передадим в className
import styles from './FlexWrapper.module.css';

function FlexWrapper({ children }) {


return (
<div className={`${styles['flex-wrapper']} ${styles.title}`}>
{children}
</div>
);
}

Сгенерированный HTML будет выглядеть так:


<div class="FlexWrapper_flex-wrapper_er4tr
FlexWrapper_title_45trgf"></div>
С использованием классов, можно менять стили в зависимости от логики
работы приложения.
Например, тут мы добавили класс error, который меняет font-size:
/* FlexWrapper.module.css */

.flex-wrapper {
background-color: #e3e3e3;
display: flex;
justify-content: center;
}

.error {
font-size: 34px;
}
В React компоненте, мы хотим добавлять этот класс к элементу <div>,
только если определено свойство hasError.
import styles from './FlexWrapper.module.css';

function FlexWrapper({ children, hasError }) {


return (
<div
className={`${styles['flex-wrapper']} ${hasError ? styles.error} :
''`}
>
{children}
</div>
);
}
Если свойство hasError будет принимать истинные значения, мы добавим
класс error элементу <div>. В противном случае — нет.

Но можно сделать себе жизнь немного проще.


Для добавления нескольких классов можно использовать
библиотеку classnames:
import styles from './FlexWrapper.module.css';
import classnames from 'classnames';

function FlexWrapper({ children, hasError }) {


return (
<div
className={classnames(styles['flex-wrapper'], {[styles.error]:
hasError})}
>
{children}
</div>
);
}
Из classnames мы импортируем метод, который получает в качестве
аргументов классы, а возвращает строку, которую ранее мы сами
формировали.

Если у нас есть классы, которые мы добавляем на основании какого-то


условия, надо передать объект в качестве аргумента.
o Ключами такого объекта будут классы (в нашем случае это styles.error).
o Значениями — будут булевые флаги. Если true — класс будет добавлен,
если false — то нет.

RJS React хуки


До сих пор, для работы с внутренним состоянием нам приходилось писать
классовые компоненты. Так действительно было до версии React 16.8. Именно
в этой версии были представлены так называемые хуки (англ. hook - крюк).

Хуки были разработаны для упрощения классовых компонентов. Зная всего


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

useState

Первым хуком который мы рассмотрим, будет useState. Его, как и остальные


хуки, нужно импортировать из библиотеки react.
Начнем с базовой структуры компонента EmailInput. В нашем случае, это
будет один <input> для ввода имейла и функция handleInputChange для
обработки введенных данных.
import React from 'react';

function EmailInput() {
const handleInputChange = () => {};

return (
<input
placeholder="Enter email"
value={undefined}
onChange={handleInputChange}
/>
);
}

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


email.
Состояния в функциональном компоненте по умолчанию нет. Мы добавляем его
с помощью хука useState, который является функцией.
Эта функция принимает один аргумент — начальное значение состояния. У нас
это будет пустая строка, так как в самом начале поле <input> должно быть
пустым.
Давай используем useState:
import React , { useState } from 'react';

function EmailInput() {
const emailState = useState('');
const handleInputChange = () => {};

return (
<input
placeholder="Enter email"
value={undefined}
onChange={handleInputChange}
/>
);
}
Хук useState возвращает массив из двух элементов.
Первым элементом этого массива будет само значение состояния (изначально
пустая строка, потому что именно ее мы передали в качестве аргумента
для useState), вторым элементом — функция, которую нужно использовать для
изменения этого значения:
const emailState = useState('');
const [emailValue, setEmailValue] = emailState;

Эквивалентный код классового компонента будет выглядеть так:


class EmailInput extends React.Component {
state = {
emailValue: '',
}

setEmailValue = (newValue) => {


this.setState({ emailValue: newValue });
}

render() {
return null;
}
}
setEmailValue - это метод, который переданный аргумент сохраняет в
качестве нового значения состояния emailState. emailValue - это и есть
значение состояния. В классовом компоненте ему будет
эквивалентно this.state.emailValue.
Давай закончим наш функциональный компонент с использованием
хука useState:
import React , { useState } from 'react';

function EmailInput() {
const [emailValue, setEmailValue] = useState('');

const handleInputChange = (e) => {


const { target: { value } } = e;
setEmailValue(value);
};

return (
<input
placeholder="Enter email"
value={emailValue}
onChange={handleInputChange}
/>
);
}
Здесь мы не создавали отдельную переменную emailState , а сразу
деструктурировали массив, который вернул useState и создали
переменные emailValue и setEmailValue.
Переменную emailValue мы сразу передаем в поле ввода в качестве значения.
Функцию setEmailValue мы используем внутри метода handleInputChange —
сначала мы получаем из объекта события введенное значение, потом с
помощью setEmailValue сохраняем его в emailValue.

После такого, как будет вызван метод обновления состояния, React


перерендерит наш компонент.
— Что делать, если мне нужно несколько полей состояния?
— Ты можешь вызвать setState столько раз, сколько нужно.
Давай переделаем наш EmailInput на LoginForm добавив еще одно поле для
ввода пароля.
import React , { useState } from 'react';

function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');

const handleInputChange = (key) => (e) => {


const { target: { value } } = e;
if (key === 'email') {
setEmail(value);
} else {
setPassword(value);
}
};

const handleSubmit = (e) => {


e.preventDefault();
console.log(email, password)
}

return (
<form onSubmit={handleSubmit}>
<input
placeholder="Enter email"
value={email}
onChange={handleInputChange('email')}
/>
<input
placeholder="Enter password"
value={password}
onChange={handleInputChange('password')}
/>
<button>Submit</button>
</form>
);
}
У нас теперь есть два поля: email и password.
Для каждого есть метод, обновляющий их значения: setEmail и setPassword.
Состояние, созданное с помощью useState может быть любого типа: строка,
число, объект, массив и т.д

Давай посмотрим на пример с объектом:


function User() {
const [userData, setUserData] = useState({
firstName: 'John',
lastName: 'Doe',
});

const changeName = () => {


const copy = {...userData};
copy.firstName = 'Jane';
setUserData(copy);
}

return (
<div>
<div>Full name: {userData.firstName} {userData.lastName}</div>
<button onClick={changeName}>Change Name</button>
</div>
);
}

Как и в случае с состоянием классового компонента - изменять его напрямую


нельзя.
Изменить состояние React компонента можно только через специальные
методы.
Именно из-за этого в методе changeName мы сначала создали копию
объекта userData, затем изменили поле firstName и весь объект передали в
метод setUserData.

useEffect

Если с состоянием все более-менее понятно, то с методами жизненного цикла


все немного сложнее.
Методов жизненного цикла в React довольно много:
основные componentDidMount, componendDidUpdate, componentWillUnmount и
несколько дополнительных.
Предлагаю разобраться с ним постепенно. Три метода, которые мы выделили
как основные, можно заменить с помощью одного единственного
хука useEffect.
useEffect — это функция, которая получает два аргумента.
o Первый аргумент (обязательный) - функция, которая должна быть выполнена
при определенных обстоятельствах. Часто ее называют действием.

o Второй аргумент (необязательный) - условие, при которые будет выполняться


действие.
Давай добавим нашему компоненту EmailInput хук useEffect который будет
выводить в консоль текущее значение электронного адреса:
import React , { useState, useEffect } from 'react';

function EmailInput() {
const [emailValue, setEmailValue] = useState('');

useEffect(() => console.log(`Current email: ${emailValue}`);

const handleInputChange = (e) => {


const { target: { value } } = e;
setEmailValue(value);
};

return (
<input
placeholder="Enter email"
value={emailValue}
onChange={handleInputChange}
/>
);
}
Сразу после рендера и при каждом изменении значения поля мы будем видеть
в консоли строку Current email: ... .
Это происходит, потому что после каждого рендера, включая первый
срабатывает наш useEffect.
А срабатывает он потому, что мы не указали никаких условий для его
выполнения - второй аргумент просто отсутствует. Это
означает, useEffect здесь выполняет одновременно и
роль componentDidMount , и роль componentDidUpdate.
Попробуем использовать хук useEffect только в режиме componentDidMount.
Для этого нужно передать вторым аргументом в функцию useEffect массив.

В качестве элементов массива мы должны перечислить переменные, при


изменении которых мы хотим выполнять действие.

Если элементы массива изменились - действие нужно выполнить, если нет -


действие выполняться не будет.

После каждого рендера React сам будет сравнивать элементы массива.

Сложно! Давай пока тогда разберемся, что сделать, чтобы действия не


выполнялось при каждом рендере.

Если мы передадим в качестве второго аргумента пустой массив, после


каждого рендера реакт будет сравнивать пустой массив с пустым массивом
предыдущего рендера.
Элементы двух пустых массивов равны между собой (их нет!). Значит
действие выполняться не будет:
import React , { useState, useEffect } from 'react';

function EmailInput() {
const [emailValue, setEmailValue] = useState('');

useEffect(
() => console.log(`Current email: ${emailValue}`,
[]
);

const handleInputChange = (e) => {


const { target: { value } } = e;
setEmailValue(value);
};

return (
<input
placeholder="Enter email"
value={emailValue}
onChange={handleInputChange}
/>
);
}
Теперь строку Current email: ... мы увидим в консоли только один раз -
после первого рендера. Любые последующие изменения не будут вызывать это
действие.
А сейчас давай добавим нашему хуку зависимость от состояния emailValue.
Для этого достаточно добавить переменную emailValue в массив
зависимостей, который мы передаем вторым параметром useEffect:
import React , { useState, useEffect } from 'react';

function EmailInput() {
const [emailValue, setEmailValue] = useState('');
useEffect(
() => console.log(`Current email: ${emailValue}`,
[emailValue]
);

const handleInputChange = (e) => {


const { target: { value } } = e;
setEmailValue(value);
};

return (
<input
placeholder="Enter email"
value={emailValue}
onChange={handleInputChange}
/>
);
}
Теперь наше действие будет срабатывать при первичном рендере, и при
последующих рендерах если, изменилось состояние emailValue.
Получается, что совсем ничего не меняется в логике работы, если
сравнивать с первым примером, где мы не передавали второй аргумент
в useEffect.
Давай тогда перенесем useEffect в компонент LoginForm:
import React , { useState } from 'react';

function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');

useEffect(
() => console.log(`Current email: ${email}`,
[email]
);
const handleInputChange = (key) => (e) => {
const { target: { value } } = e;
if (key === 'email') {
setEmail(value);
} else {
setPassword(value);
}
};

const handleSubmit = (e) => {


e.preventDefault();
console.log(email, password)
}
return (
<form onSubmit={handleSubmit}>
<input
placeholder="Enter email"
value={email}
onChange={handleInputChange('email')}
/>
<input
placeholder="Enter password"
value={password}
onChange={handleInputChange('password')}
/>
<button>Submit</button>
</form>
);
}
Здесь у нас уже два поля, которые могут изменяться. Но в зависимостях для
хука мы указали только состояние email. Из-за это при изменении пароля в
консоль не будет выводится строка Current email: ....
В реальных задачах часто будет необходимо делать какие-то действия при
монтировании элемента, у каких-то других должны быть зависимости.
Ты можешь добавлять необходимое количество useEffect для решения
поставленных задач.
import React , { useState } from 'react';

function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');

// 1
useEffect(() => {
console.log('Component mounted');
}, []);

// 2
useEffect(() => {
if (email.includes('@')) {
console.log('Valid email');
} else {
console.log('Invalid email');
}
}, [email]
);

// 3
useEffect(() => {
if (password.length > 5) {
console.log('Valid password');
} else {
console.log('Password is too short');
}
}, [password]
);
const handleInputChange = (key) => (e) => {
const { target: { value } } = e;
if (key === 'email') {
setEmail(value);
} else {
setPassword(value);
}
};

const handleSubmit = (e) => {


e.preventDefault();
console.log(email, password)
}
return (
<form onSubmit={handleSubmit}>
<input
placeholder="Enter email"
value={email}
onChange={handleInputChange('email')}
/>
<input
placeholder="Enter password"
value={password}
onChange={handleInputChange('password')}
/>
<button>Submit</button>
</form>
);
}
Здесь мы использовали три useEffect.
1. Первый срабатывает при монтировании компонента и выводит в консоль
строку Component mounted.
2. Второй срабатывает при монтировании, а дальше при изменении поля email.
После проведения проверок на наличие символа @ в строке выводит одну из
строк в конcоль: Valid email или Invalid email.
3. Третий срабатывает при монтировании, а дальше при изменении
поля password. Если длина пароля больше 5 символов, в консоль выводится
строка Valid password. В обратном случае в консоли мы увидим
строку Password is too short.

componentWillUnmount

Последний из основных метод жизненного цикла, который мы не разобрали


- componentWillUnmount.

Давай рассмотрим такой пример:


class WithScrollListent extends React.Component {
componentDidMount() {
window.addEventListenet(console.log);
}

componentWillUnmount() {
window.removeEventListenet(console.log);
}

render() {
return null;
}
}

При монтировании компонента мы добавляем слушатель события прокрутки,


перед размонтирование - этот слушатель мы удаляем.
Давай тоже самое сделаем с помощью useEffect:
import React, { useEffect } from 'react';

function WithScrollListent() {
useEffect(() => {
window.addEventListenet(console.log);
return () => window.removeEventListenet(console.log);
}, []);

render() {
return null;
}
}

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


Из функции, которую мы передали в useEffect, мы возвращаем функцию. Это
функция и будет вызвана перед размонтированием компонента. И именно
внутри нее удаляем слушатель события прокрутки.
Из этого выходит одно из правил, которое стоит помнить про useEffect -
функция, переданная в этот хук должна возвращать или функцию, или ничего.

Запросы на сервер

В классовом компоненте запросы на сервер должны были делаться в


методе componentDidMount.
Мы уже научились писать аналог этого метода в функциональном компоненте с
использование хука useEffect.

Давай теперь сделаем запрос:


import React , { useState, useEffect} from 'react';

function CryptoCoins() {
const [data, setData] = useState([]);

useEffect(() => {
fetch('/get-coins-data')
.then(data => setData(data));
}, [])

return null;
}
Полученные с сервера данные нам надо хранить в состоянии, чтобы компонент
перерендерился после их получения. Для этого мы создали состояние data с
помощью хука useState.
Сам запрос мы выполняем внутри useEffect, которому передали пустой массив
зависимостей. Таким образом запрос на сервер мы отправляем только один
раз.
Полученные данные мы обрабатываем в then. Делаем мы это неспроста.
Функция, которая передается в useEffect, должна быть синхронной. По-
этому async/await нельзя использовать для первого метода useEffect:
import React , { useState, useEffect} from 'react';

function CryptoCoins() {
const [data, setData] = useState([]);

// так делать нельзя


useEffect(async () => {
const data = await fetch('/get-coins-data')
setData(data);
}, [])

return null;
}
Почему же так делать нельзя? Асинхронная функция всегда
возвращает Promise, а как мы уже знаем - мы должны возвращать или
функцию, или ничего.
Если же тебе больше нравится синтаксис async/await, то запрос можно
реализовать следующим образом:
import React , { useState, useEffect} from 'react';

function CryptoCoins() {
const [data, setData] = useState([]);

useEffect(() => {
const getData = async () => {
const data = await fetch('/get-coins-data')
setData(data);
};

getData();
}, [])

return null;
}
Внутри useEffect мы объявили новую функцию getData, которую и сделали
асинхронной. Внутри getData мы получаем данные и сохраняем их в
состояния. Внутри useEffect мы просто вызываем getData и на этот раз
ничего не возвращаем.

350 RN: React Native и Expo


Различный сайты, веб-приложения пользователи открывают на самых разных
браузерах - Chrome, IE, Safari, Mozilla. И для любого браузера пишутся
сайты с одним и тем же набором технологий - HTML, CSS, JS. И везде они
открываются и работают одинаково (в большинстве случаев).
С мобильными приложениями ситуация обстоит несколько иначе.
Разработка Android приложений ведется на Java/Kotlin, а iOS - на
Objective-C/Swift. Из-за этого разработка приложения для обеих платформ
занимает очень много времени и средств: две команды разработчиков должны
разрабатывать по сути два одинаковых приложения.
Для решения этой проблемы разработчики Facebook и придумали
фреймворк React Native. По сути, приложения пишется на JS, а в его основе
лежит библиотека React. Это позволяет веб программистам быстро перейти в
разработку мобильных приложений - в работе ты столкнешься с теми же
метода жизненного цикла, хуками, свойствами и состояниями.

Потоки и React Native Bridge

Итоговое приложение запускается на телефоне с использованием нативного


кода. Для этого в каждом приложении предусмотрено два потока: основной и
JS. Основной потом отвечает за отображение элементом в жесты
пользователя. JS поток выполняет JS код и отвечает за логику работы
приложения. Для того, чтобы JS код понял, что сделал пользователь и какое
действие необходимо сделать, нужен обмен данными между двумя потоками.

Этот обмен происходит с помощью моста React Native (с англ. - bridge,


React Native Bridge). Интерфейс описывается на JS, а мост рассчитывает
макеты, которые нужно показать пользователю. Он передает их в основной
поток, который и показывает экраны пользователю. Пользователь делает
какое-то действие, например нажимает на кнопку. Основной поток отправляет
нативное (native) действие через мост на JS. Мост преобразовывает это
действие в понятный для JS формат, а уже в потоке JS это действие
обрабатывается.

После того как действие обработано и JS выполнил все необходимые функции


— он отправляет в основной поток ответ для обновления интерфейса. Также
из потока JS можно вызвать, если необходимо, нативные действия — например
вызов окна оформления подписки. И ответ из потока JS, и обращение из
основного потока проходят через мост в виде сериализованного объекта
JSON:

При написании веб приложения с использованием React, для создания


интерфейса используются обычные теги и стили CSS. В каждой мобильной же
системе есть свои нативные элементы для отрисовки UI и теги здесь не
помогут. В React Native предусмотренны готовые компоненты для построения
интерфейса, каждому из которых соответствует нативный Android и iOS
компонент. Каждый из них мы будем рассматривать в соответствующей теме,
во всем остальном фундаментального отличия между React и React
Native нет.

Первое приложение

Есть несколько вариантов для запуска и сборки React Native приложения.


Первый из них это ReactNative CLI, второй - Expo CLI. Первый
рекомендуется для программистов с опытом в мобильной разработке, так как
требует дополнительного использования Xcode и Android Studio.

Expo - это фреймворк, созданный вокруг React Native, и имеет очень


широкий функционал для разработки, сборки и развертывания мобильного
приложения. Еще одним важным плюсом будет то, что для начала разработки
нам нужна будет только командная строка и телефон.
Давай создадим наше первое приложение на React Native. Но перед этим нам
надо провести подготовительные работы. И первое, что нужно сделать — это
установить Expo. Открой терминал и введи следующую команду:
npm install -g expo-cli

Эта команда запустит процесс установки клиента expo на твой компьютер.


Дальше надо создать свой первый проект. В терминале открой папку, в
которой ты хочешь создать проект и запусти следующую команду:
expo init my-first-rn-project

После вызова команды, клиент expo даст тебе выбор, какой проект тебе
нужен.
Смело выбирай пункт blank и нажимай Enter. Клиент создаст обычный пустой
проект ReactNative со всеми необходимыми для запуска и разработки
файлами. Дальше нам надо запустить проект. Для этого перейди в папку
проект и вызови команду для запуска:
cd my-first-rn-project # переходим в папку с проектом
npm start # запускаем проект

В результате будет запущен проект и в твоем браузере откроется следующая


страница:

Теперь посмотрим, как выглядит наше приложение. Самый просто способ это
установить мобильное приложение Expo для Android или iOS. После этого
достаточно отсканировать QR код из страницы в браузере и приложение
запуститься на смартфоне:
Вот и все действия, которые нужно проделать, чтобы сделать свой первое
приложение. Дальше мы разберем элементы для построения экранов
приложения, а пока переходи к задачам и попробуй сам создать и отправить
на проверку свое первое приложение.

351 RN: Flex View и стили


В мобильной разработке самым базовым элементом интерфейса является
отображение (view). View является прямоугольной областью, которая
используется для отображения текста, картинок, полей ввода или даже
область для нажатия. У каждой системы для каждого случая есть свой
нативный элемент.
Например, для контейнера в Android используется элемент <ViewGroup>, а в
iOS - <UIView>. В React Native используется один единственный
компонент <View>, который с помощью моста React Native отрисовывается
в <ViewGroup> или <UIView> в основном потоке.
В вебе аналогом этого компонента может быть тег <div>, но особенностью
контейнера в React Native является то, что он по умолчанию является flex
контейнером. Следовательно, ему можно применять все те же стили CSS, что
и flex-контейнеру в web-е. Вообще практически со всеми элементами можно
использовать обычные стили CSS, но об этом немного позже, пока посмотрим
как использовать элемент <View>.

Компонент View

По умолчанию <View> - это flex контейнер с вертикальным расположением


элементов. Начальное значение свойства flex-direction у него column.
Такое начальное значение необходимо, потому что у мобильных приложений
зачастую вертикальные макеты и элементы расположены в колонку.
Значение column позволяет не менять лишний раз свойство flex-direction.
View - это стандартный элемент из библиотеки React Native. Для его
использования, как и любого другого стандартного элемента, его нужно
импортировать:
import { View } from 'react-native';
В элемент View можно вкладывать дочерние элементы, при чем View может
быть дочерним элементом другого View.

Используется он так же, как и обычный React компонент:


import React from "react";
import { View } from "react-native";

const ViewBox = () => <View />;

Текстовый элемент

Для добавления текста необходимо использовать текстовый элемент Text,


который тоже является стандартным:
import React from "react";
import { View, Text } from "react-native";

const ViewBox = () => (


<View>
<Text>React Native is cool</Text>
</View>
);

Текст поддерживает вложение: можно вкладывать один текстовый элемент


внутрь другого.
import React from "react";
import { Text } from "react-native";

const TextBox = () => (


<Text>
React Native is:
<Text>cool,</Text>
<Text>easy.</Text>
</Text>
);

Свойство style

Стилизуются элементы в React Native, как я уже говорил выше, с помощью


CSS. Первый способ добавления стили очень похож на строчный в HTML. Для
этого элементу надо добавить свойство style.
Значение свойства style - объект. Ключами такого объекта будут свойство
стилей, значениями — само значение стилей.
Свойства стилей и их значения обычно совпадают со своими аналогами в web-
е. Единственное отличие — свойства пишутся в стиле camel case. Вот
несколько примеров:
o в web background-color, в React Native backgroundColor;
o в web font-weight, в React Native fontWeight;
o в web flex-direction, в React Native flexDirection;
o в web color, в React Native color;

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


со стороной 200px:
import React from 'react';
import { View } from 'react-native';

const ViewBox = () => <View style={{ backgroundColor: 'red', width:


200, height: 200 }} />;
Обрати внимание: свойствам width и height мы указали значение 200 без
размерности. В React Native все величины безразмерны. Подробнее об этом
поговорим в следующее лекции.

StyleSheet

Второй способ добавление стилей компонентам это StyleSheet API. Первый


шаг — импорт StyleSheet из стандартной библиотеки React Native.
import { StyleSheet } from "react-native";
Следующим шагом будет создания таблицы стилей. Для этого нужно вызвать
метод StyleSheet.create(). Аргументом этого метода будет объект.

Ключами этого объекта будет имя стиля. Об этом имени можно думать как о
классе CSS, потому что его можно использовать много раз для разных
элементов.

Значения этого объекта — стили, написанные точно так же, как и для
свойства style.

Попробуем переписать пример с красным квадратом:


import React from "react";
import { View, StyleSheet } from "react-native";

const ViewBox = () => <View style={styles.redBox} />;

const styles = StyleSheet.create({


redBox: {
backgroundColor: "red",
width: 200,
height: 200,
},
});

Объединения стилей

В React Native предусмотрен механизм добавление нескольких стилей одну и


тому же элементу. Это очень полезно, чтобы не дублировать некоторые
стили, или если стили должны зависеть от каких-то свойств компонента. Для
этого свойству style надо указать массив объектов стилей. Давай перепишем
пример выше для наглядности:
import React from "react";
import { View, StyleSheet } from "react-native";

const ViewBox = ({ opacity = 0.7 }) => {


return (
<View style={[ styles.redBox, styles.fixedSizes, { opacity } ]} />
);
}

const styles = StyleSheet.create({


redBox: {
backgroundColor: "red",
},
fixedSizes: {
width: 200,
height: 200,
}
});

Styled components

Стили можно добавить с помощью библиотеки styled-components. Плюсом


использования этой библиотеки будет возможность переиспользовать
написанные стили и привычный синтаксис их написания.

Для использования библиотеки, сначала ее надо установить. Для этого


запусти в терминале в корне проекта команду.
npm i styled-components
Дальше давай перепишем пример выше с использованием библиотеки styled-
components:
import React from "react";
import styled from "styled-components/native";

const StyledView = styled.View`


background-color: red;
width: 200px;
height: 200px;
`;

const ViewBox = () => <StyledView />;


Поскольку эту библиотеку можно использовать как для написания веб-
приложений, так и мобильных, нужно импортировать ее нативную часть
из styled-components/native. styled-components использует теговые шаблоны
(tagged template literals) для добавления стилей. По этому сначала нужно
выбрать элемент, который мы хотим стилизовать (styled.View) и просто
присоединяем к нему стили.

Особенные свойства

Но все CSS свойства доступны в там таком же виде, как и в web-е. Например
обычное свойство border в React Native надо разбивать на
два: borderColor для указания цвета и borderWidth для указания толщины
линии. Этих двух свойств будет достаточно, но если надо изменить стиль
линии, используй свойство borderStyle.
Свойство box-shadow вовсе отсутствует. Для iOS можно добавить
свойства shadowOffset, shadowColor, shadowOpacity, shadowRadius, которые
на самом деле и являются частями свойства box-shadow:
const styles = StyleSheet.create({
iosShadow: {
shadowOffset: { height: 5, width: 5 },
shadowColor: '#000',
shadowOpacity: 0.7,
shadowRadius: 10,
},
});
Для Android приложения можно использовать свойство elevation для
добавления тени:
const styles = StyleSheet.create({
androidShadow: {
elevation: 10,
},
});
Из приятных новостей — упрощенное добавление свойство margin и padding. А
именно можно одним свойство добавить или верхнее и нижнее значения
(вертикальные), или левое и правое (горизонтальные). Для этого надо
нужному свойству добавить слова Vertical или Horizontal соответственно:
const styles = StyleSheet.create({
withVerticalMargin: {
marginVertical: 20,
},
withHorizontalPadding: {
paddingHorizontal: 20,
},
});
При этом нельзя использовать записи следующего вида: padding: 10
20, margin: 10 20 10 20, padding: 5 10 7. Для задания разных значений
разным сторонам используй свойства для конкретной стороны,
напр. paddingLeft.

352 RN: Изображения и Размеры


Размеры

Размеры элементов в React Native можно несколькими способами.


Первый — фиксированные размеры с помощью свойств стилей width и height.
Эти размеры являются не зависящими от плотности пикселями (DIP - density-
independent pixels).

Это связано с тем, что есть много разных размеров экрана и разных
разрешений на одном и том же размере экрана. Для того, чтобы один и тот
же элемент смотрелся на экране с высоким разрешением и высокой плотностью
пикселей примерно так же, как и на экране с низким разрешением и
используется эта размерность. Поэтому при использовании фиксированных
значения, размерности указывать не нужно:
<View style={{ width: 200, height: 200, padding: 30,
backgroundColor: 'red' }}>
<View style={{ width: 100, height: 100, borderRadius: 10,
backgroundColor: 'blue'}}>
<View style={{ width: 50, height: 50, margin: 10, backgroundColor:
'green'}} />
</View>
</View>
Второй способ — динамическое изменение размера в зависимости от
свободного места. В React Native все построено с помощью flexbox. Как и в
web-разработке, свободное пространство распределяется между элементами
свойством flex.
<View style={{ flex: 1 }}>
<View style={{flex: 1, backgroundColor: 'red'}} />
<View style={{flex: 2, backgroundColor: 'blue'}} />
<View style={{flex: 3, backgroundColor: 'green'}} />
</View>
Для изменения размеров дочернего элемента с помощью значения
свойство flex - обязательно должны быть установлены размеры родительского
элемента. Если у родительского элемента не будет установлены значения
свойств для width и height или flex, он будет "свернут", и дочерний
элемент будет скрыт.
<View style={{}}>
// каждый из элементов ниже будет скрыт
<View style={{flex: 1, backgroundColor: 'red'}} />
<View style={{flex: 2, backgroundColor: 'blue'}} />
<View style={{flex: 3, backgroundColor: 'green'}} />
</View>

Еще один способ указания размеров — проценты. В этом случае также важно
указать размеры родительского элемента, иначе размеры дочерних элементов
не будут иметь значения — элементы будут просто скрыты.
<View style={{height: '100%'}}>
<View style={{height: '10%', backgroundColor: 'red'}} />
<View style={{flex: 2, backgroundColor: 'blue'}} />
<View style={{height: 30, backgroundColor: 'green'}} />
</View>
В этом случае родительский View займет весь экран по высоте. Первый
дочерний элемент - 10% высоты родительского. Последний 30 пикселей. Все
оставшееся пространство займет средний дочерний элемент.

Ширина родительского элемента, как и дочерних, будет занимать 100% ширины


контейнера. Для родительского элемента контейнером является экран.

Размеры экрана

Различные устройства отличаются размером экрана. При этом хорошее


кроссплатформенное приложение должно выглядеть отлично на каждом их них.
Часто приходится привязывать размеры различных элементов в размерах
экрана.
В React Native для получения ширины и высоты экрана устройство
предусмотренно API Dimensions, которое входит в стандартную
библиотеку react-native.
import { Dimensions } from 'react-native';
Для получения размеров экрана надо вызвать метод Dimensions.get().
Единственным аргументом этого метода будет строка: window или screen.
Если передать аргумент window - результатом будет размер видимого окна
приложения. С аргументом screen ты получишь размеры экрана устройства.
Результатом вызова будет объект с двумя полями: width и height.
import { Dimensions } from 'react-native';

const { width, height } = Dimensions.get('window');


Размеры экрана и окна будут отличаться только на android. Под размером
окна (window) понимается часть экрана без панели программного меню.
Исходя из этого значения высоты или ширины для window будут меньше, чем
для screen: при вертикальной ориентации устройства меньшей будет высота,
при горизонтальной — ширина.

Полученные размеры экрана можно использовать при указании высоты и ширины


элементов:
import { React } from 'react';
import { View } from 'react-native;
import { Dimensions } from 'react-native';

const { width } = Dimensions.get('window');

const App = () => <View style={{ height: 40, width }} />;

Изображения

Для добавления изображений в мобильное проложение используется


элемент Image. Для использования его надо импортировать из
библиотеки react-native.
Источник изображения указывается свойством source. Источником может быть
и локальный файл, и удаленный источник. Поддерживаются все доступные
форматы: png, jpg, jpeg, bmp, gif, webp, psd (этот формат можно
использовать только для iOS).
Источник зачастую указывается в виде объекта. В поле uri объекта
указывается строка, которая может быть http адресом, путем к локальному
файлу или названием статического ресурса. Прочими полями могут
быть width и height, с помощью которых можно указать значения изображения
по умолчанию, которые будут использовать во время сборки
приложения; scale - коэффициент масштабирования изображения, по умолчанию
равен 1.
Свойство style используется для стилизации изображения.
import React from 'react';
import { Image, View } from 'react-native';

const App = () => (


<View style={{ flex: 1 }}>
<Image
style={{ flex: 1 }}
source={{ uri: 'https://learn.coderslang.com/lecture-background-
image.jpg'}}
/>
</View>
);

Методы изменения размера изображения

Размеры контейнера для изображения могут отличаться от самого


изображения. Для того, чтобы изображение помещалось в указанный
контейнер, надо или изменить его размеры, или масштабировать.
Для определения подхода используется свойство resizeMethod.
Значение resize этого свойства означает, что будет изменяться размер
изображения. Этот подход лучше использовать, когда размер изображения
значительно превышает размер контейнера. Таким образом не будет заметно
ухудшение качества изображения.
Второй метод, который соответствует значению scale, работает быстрее.
Масштабирование может происходить как в сторону увеличения, так и в
сторону уменьшение. Этот подход лучше использовать, когда изображение
меньше контейнера.
По умолчанию значением свойства resizeMethod будет auto - комбинация
методов scale и resize для получения наилучшего эффекта.
import React from 'react';
import { Image, View } from 'react-native';

const App = () => (


<View style={{ flex: 1 }}>
<Image
style={{ flex: 1, resizeMethod: "scale" }}
source={{ uri: 'https://learn.coderslang.com/lecture-background-
image.jpg' }}
/>
</View>
);

Режим изменения размера изображения

Поведение изображения изменяется с помощью свойства resizeMode:


o cover - изображение будет масштабировано с сохранением пропорций ширины и
высоты. Масштабирование будет происходить, пока изображение полностью не
займет область отображения и по высоте, и по ширине. В этом случае
изображение может быть обрезанным.
o contain - изображение будет масштабировано с сохранением пропорций ширины
и высоты. Масштабирование будет происходить, пока изображение полностью
не займет область отображения или по высоте, или по ширине. В этом случае
изображение не будет обрезанным.
o stretch - изображения будет растягиваться, чтобы полностью заполнить
область отображения. Пропорции при этом не будут соблюдаться.
o repeat - изображение сохранит исходные размеры и пропорции. Для
заполнения всей области будет повторяться по вертикали и горизонтали
нудное количество раз. При этом изображения может быть обрезанным в
последней колонке или строке, если полностью не будет помещаться.
o center - изображение будет отображаться посередине области. Его размеры
могут быть уменьшены, чтобы полностью поместиться по высоты или ширине.
Пропорции при этом сохранятся.
import React from 'react';
import { Image, View } from 'react-native';

const App = () => (


<View style={{ flex: 1 }}>
<Image
style={{ flex: 1, resizeMode: "contain" }}
source={{ uri: 'https://learn.coderslang.com/lecture-background-
image.jpg' }}
/>
</View>
);
PixelRatio

Приложения открываются на самых разных устройствах с дисплеями как


высокого, так и низкого расширения. Представим, что изображению указано и
ширину, и высоту равную 100. Таким образом размеры изображения
зафиксированы. И чем выше будет плотность пикселя, тем меньше изображение
будет меньшим при отображении. Это может быть нежелательным эффектом.
Чтобы избавиться от него, нужно привязать размер изображения к плотности
пикселей. Для этого в React Native предусмотренно API PixelRatio:
import { PixelRatio } from 'react-native';
Для преобразования DIP в px с учетом плотности пикселей на устройстве
нужно использовать метод getPixelSizeForLayoutSize():
import React from 'react';
import { View, Image, PixelRatio } from 'react-native';

export default function App() {


return (
<View>
<Image
source={{ uri: 'https://learn.coderslang.com/lecture-background-
image.jpg' }}
style={{
width: PixelRatio.getPixelSizeForLayoutSize(200),
height: PixelRatio.getPixelSizeForLayoutSize(200)
}}
/>
</View>
);
}

Единственным аргументом метода будет желаемое значение изображения. В


результате будет возвращен размер с учетом плотности пикселей.

353 RN: Поля ввода и кнопка


Поле ввода

В мобильном приложении постоянно надо вводить информацию: данные для


регистрации в сервисе такси, имя в мобильной игре, поисковую информацию в
браузере, сообщение в мессенджере. Во всех этих случаях надо использовать
базовый компонент TextInput.
Элемент TextInput является стандартным элементом из библиотеки react-
native. Его значения указывается при помощи свойства value, а
метод onChangeText позволяет считывать введенные пользователем данные.
Давай напишем простой компонент, в котором пользователь будет вводить
имя, а введенное значение будет сохраняться переменную состояния name:
import React, { useState } from 'react';
import { View, Text, TextInput } from 'react-native';

const NameInput = () => {


const [name, setName] = useState('');
return (
<View style={{ flex: 1, padding: 30 }}>
<Text>Введите имя:</Text>
<TextInput value={name} onChangeText={text => setName(text)} />
</View>
);
};
Как видишь, использование хуков ничем не отличается от их использования в
обычном React - здесь мы использовали useState для хранения введенного
имени. Для стилизации этого компонента используй свойство style:
import React, { useState } from 'react';
import { View, Text, TextInput } from 'react-native';

const NameInput = () => {


const [name, setName] = useState('');

return (
<View style={{ flex: 1, padding: 30 }}>
<Text>Введите имя:</Text>
<TextInput
value={name}
style={{ color: 'red', border: '2px solid black', marginTop: 10,
height: 40 }}
onChangeText={text => setName(text)}
/>
</View>
);
};
По умолчанию, поле ввода будет однострочным. Если надо сделать поле
многострочным, как textarea в браузере, передай значение true
свойству multiline:
import React, { useState } from 'react';
import { View, Text, TextInput } from 'react-native';
const NameInput = () => {
const [address, setAddress] = useState('');

return (
<View style={{ flex: 1, padding: 30 }}>
<Text>Введите адрес:</Text>
<TextInput
Multiline
value={address}
style={{ color: 'red', border: '2px solid black', marginTop: 10,
height: 40 }}
onChangeText={text => setAddress(text)}
/>
</View>
);
};

Свойства текстового поля

У текстового поля есть свойства, с помощью которых изменяется внешний вид


и логика работы компонента. Рассмотрим их подробнее:
o editable - значение этого свойства по умолчанию true. Если указать false,
поле ввода не будет работать и нельзя будет изменять его текст.
o keyboardType - это свойство определяет, какую клавиатура открыть.
Например, если в поле для ввода телефонного номера нужны только цифры,
свойство keyboard стоит указать со значением numeric.
o placeholder - с помощью этого свойства указывается текст, который
отображается в поле, пока оно пустое.
o placeholderTextColor - указывает цвет для текста placeholder.
o textAlign - определяет выравнивание текста в поле ввода. Значение по
умолчанию left - текст выравнен по левому краю. Еще возможны
значения right для выравнивания текста по правому краю и center для
выравнивания посередине.
o secureTextEntry - это свойство скрывает введенную информацию, подходит
для полей для ввода пароля. Для включения этого свойство укажи
значение true, по умолчанию свойство отключено.
o maxLength - с помощью этого свойство можно указать максимальное
количество символов для ввода в поле.
o autoFocus - если это свойство установленно в true, этому полю ввода будет
предоставлен автоматический фокус для ввода.
o onChange - callback, который срабатывает, когда изменяется введенный
текст. В отличие от метода onChangeText, этот метод получает не новое
значение поля, а объект события. Новое значение можно получить из
свойства target.value:
import React, { useState } from 'react';
import { View, Text, TextInput } from 'react-native';

const NameInput = () => {


const [name, setName] = useState('');
const onChange = e => setName(e.target.value);

return (
<View style={{ flex: 1, padding: 30 }}>
<Text>Введите имя:</Text>
<TextInput
value={name}
style={{ color: 'red', border: '2px solid black', marginTop: 10,
height: 40 }}
onChangeText={onChange}
/>
</View>
);
};
Это не все свойства, которые есть в TextInput, но это основные, с
которыми тебе предстоит работать больше всего.
Кнопка

Базовый компонент для отображения кнопки Button входит в стандартную


библиотеку react-native. Текст кнопки определяется свойством label,
callback для отработки события передается через свойство onPress.

Немного изменим наш компонент для ввода имени. Добавим кнопку, при
нажатии на которую поле ввода будет заменяться просто введенным именем:
import React, { useState } from 'react';
import { View, Text, TextInput, Button } from 'react-native';

const NameInput = () => {


const [name, setName] = useState('');
const [showName, setShowName] = useState(false);

return (
<View style={{ flex: 1, padding: 30 }}>
{showName ? (
<Text>{name}</Text>
) : (
<>
<Text>Введите имя:</Text>
<TextInput
value={name}
style={{ color: 'red', border: '2px solid black', marginTop: 10,
height: 40 }}
onChangeText={text => setName(text)}
/>
</>
)}
<Button style={{ marginTop: 10 }} title="Показать имя" onPress={()
=> setShowName(true)} />
</View>
);
};
Кнопка всегда будет правильно отображена на любой платформе, но список
его настроек очень короткий. Например, кнопке нельзя передать свойство
стилей. Внешне кнопка может изменяться только с помощью двух
свойств: color и disabled.
Свойство color изменяет цвет текст кнопки, если запускать приложение на
iOS, на Android-устройствах это свойство изменяет цвет заднего фона.
Свойство disabled отключает любое взаимодействие с элементом.

TouchableOpacity

Из-за ограниченных настроек, такая кнопка скорее всего не подойдет твоему


приложению и тебе прийдется самому создавать свою кнопку. Собственную
кнопку можно создать из любого другого компонента, например из Text. Для
этого достаточно обернуть его в компонент стандартной
библиотеки TouchableOpacity, который реагирует на нажатия. При этом
прозрачность обернутой области увеличивается, давая пользователю понять,
что нажатие сработало.
В отличие от компонента Button, TouchableOpacity можно указать точно
такие же стили, как и любому компоненту View. Как и кнопки, для передачи
функции обратного вызова, у TouchableOpacity служит свойство onPress.
Давай перепишем NameInput, использую собственную кнопку:
import React, { useState } from 'react';
import { View, Text, TextInput, TouchableOpacity } from 'react-
native';

const NameInput = () => {


const [name, setName] = useState('');
const [showName, setShowName] = useState(false);

return (
<View style={{ flex: 1, padding: 30 }}>
{showName ? (
<Text>{name}</Text>
) : (
<>
<Text>Введите адрес:</Text>
<TextInput
Multiline
value={name}
style={{ color: 'red', border: '2px solid black', marginTop: 10,
height: 40 }}
onChangeText={text => setName(text)}
/>
</>
)}
<TouchableOpacity
style={{ backgroundColor: 'red', height: 40, alignItems: center,
justifyContent: 'center', marginTop: 10, borderRadius: 5 }}
onPress={() => setShowName(true)}>
<Text>Показать имя</Text>
</TouchableOpacity>
</View>
);
};
Работа такой кнопки ничем не будет отличаться от компонента Button, но мы
можем стилизовать ее, как угодно и отображаться на всех устройствах она
будет одинаково.
Прозрачность кнопки при нажатии можно изменить с помощью
свойства activeOpacity, начальное значение которого 0.2.

Pressable

Pressable - еще одни компонент для обработки нажатий в React Native. Его
особенность заключается в том, что он может отслеживать разные стадии
нажатия на свои дочерние элементы.
Первое, что делает пользователь, это нажимает на такое элемент. В этот
момент срабатывает метод onPressIn. Когда пользователь убирает свой
палец, нажатие заканчивается и срабатывает метод onPressOut. В самом
конце срабатывает метод onPress.
import React, { useState } from 'react';
import { Pressable, Text, View, StyleSheet } from 'react-native';

export default function App() {


const [pressIn, setPressIn] = useState(false);
const [pressOut, setPressOut] = useState(false);
const [press, setPress] = useState(false);
return (
<View style={styles.container}>
<Pressable
onPressIn={() => setPressIn(true)}
onPressOut={() => setPressOut(true)}
onPress={() => setPress(true)}
>
<Text style={styles.press}>Press me</Text>
</Pressable>
{pressIn && <Text>Press in</Text>}
{pressOut && <Text>Press out</Text>}
{press && <Text>Press</Text>}
</View>

);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 40,
},
press: {
fontWeight: 'bold',
color: 'white',
backgroundColor: 'red',
paddingVertical: 20,
paddingHorizontal: 40,
}
});
Если нажать на кнопку Press me и держать, то сначала у нас под кнопкой
появится сообщение Press in. Как только мы отпустим кнопку, под ней
появится еще два сообщения Press out и Press.
Есть еще один интересный момент. Если продержать кнопку нажатой более
500ms, то будет вызван еще один метод onLongPress. Давай добавим его в
наш компонент.
import React, { useState } from 'react';
import { Pressable, Text, View, StyleSheet } from 'react-native';

export default function App() {


const [pressIn, setPressIn] = useState(false);
const [longPress, setLongPress] = useState(false);
const [pressOut, setPressOut] = useState(false);
const [press, setPress] = useState(false);
return (
<View style={styles.container}>
<Pressable
onPressIn={() => setPressIn(true)}
onLongPress={() => setLongPress(true)}
onPressOut={() => setPressOut(true)}
onPress={() => setPress(true)}
>
<Text style={styles.press}>Press me</Text>
</Pressable>
{pressIn && <Text>Press in</Text>}
{longPress && <Text>Long press</Text>}
{pressOut && <Text>Press out</Text>}
{press && <Text>Press</Text>}
</View>

);
}

const styles = StyleSheet.create({


container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 40,
},
press: {
fontWeight: 'bold',
color: 'white',
backgroundColor: 'red',
paddingVertical: 20,
paddingHorizontal: 40,
}
});
Но как только мы добавили метод onLongPress - метод onPress уже не
срабатывает. После onLongPress выполнится только onPressOut. Это сделано
для того, чтобы разработчики могли добавлять два обработчика для разных
нажатий пользователя.
Время, которое нужно держать кнопку нажатой для срабатывания
метода onLongPress, можно изменить. Для этого передай числовое значение в
миллисекундах в свойстве delayLongPress.
Чтобы просто отключить кнопку, передай свойство disabled со
значением false.
При нажатии на элемент Pressable никак не видно визуально, что нажатия
сработало (в отличие от Button или TouchableOpacity). Исправить это можно
следующим образом:
/* ... */
<Pressable
onPressIn={() => setPressIn(true)}
onLongPress={() => setLongPress(true)}
onPressOut={() => setPressOut(true)}
onPress={() => setPress(true)}
>
{({ pressed }) => (
<Text
style={[styles.press, { opacity: pressed ? 0.8 : 1 }]}
>
Press me
</Text>
)}
</Pressable>
/* ... */

В качестве дочернего элемента можно указать функцию, которая в аргументах


получает состояния нажатия на кнопку. Это булевое значение можно
использовать для изменения стилей.
Сам элемент Pressable можно стилизовать тоже с помощью свойства style.
Ему можно передать или сами стили, или метод, который получает в
аргументах состояния нажатия на кнопку и должен вернуть стили.
/* ... */
<Pressable
style={({ pressed }) => ({ backgroundColor: 'red', opacity: pressed
? 0.8 : 1 })}
>
{/* ... */}
</Pressable>

ActivityIndicator

Очень часто после нажатия на кнопку происходят какие-то длительные


действия. Например, сохранение формы в базу данных. И когда это
происходит, надо показать пользователю какой-нибудь лоадер. Для этого в
React Native предусмотрен стандартный компонент ActivityIndicator,
который отображается как крутящийся лоадер.
import React, { useState } from 'react';
import { View, ActivityIndicator, TouchableOpacity } from 'react-
native';

const NameInput = () => {


const [name, setName] = useState('');
const [showName, setShowName] = useState(false);

return (
<View style={{ flex: 1, alignItems: 'center', justifyContent:
'center' }}>
<TouchableOpacity>
<ActivityIndicator />
</TouchableOpacity>
</View>
);
};
У ActivityIndicator есть два размера: small и large. По умолчанию small,
а изменить это значение можно с помощью свойства size. С помощью
свойства color можно изменить цвет лоадера:
/* ... */
<ActivityIndicator size="large" color="red" />
/* ... */
};
В системе iOS цвет по умолчанию - #999999, а в системе Android - null. По
этому, чтобы везде лоадер выглядел одного цвета, рекомендую его
указывать.

354 RN: Списки и SafeArea


В React Native все или является View или его надо оборачивать в него.
Например TouchableOpacity, который мы рассмотрели в предыдущей лекции —
тоже является View. Просто он реагирует на касания.
Но у View, с которым мы уже работали, есть одна особенность — все что
выходит за его рамки, будет скрыто и никакой прокрутки не будет. Это
значит, что из такого элемента нельзя формировать списки. Ведь любой
список, который превышает по размерам дисплей устройства, будет обрезан.

ScrollView

Тут нам помощь придет еще один компонент из стандартной библиотеки react-
native - ScrollView. По сути, этот компонент тоже создает
отображение View, но у него появляются границы прокрутки. Обычно этими
границами будут высота и ширина самого экрана.
import React from 'react';
import { StyleSheet, Text, View, ScrollView, Dimensions } from
'react-native';

export default function App() {


return (
<ScrollView style={styles.container}>
<View style={{ ...styles.item, backgroundColor: '#56c7c0' }}>
<Text style={styles.text}>#56c7c0</Text>
</View>
<View style={{ ...styles.item, backgroundColor: '#38277f' }}>
<Text style={styles.text}>#38277f</Text>
</View>
<View style={{ ...styles.item, backgroundColor: '#ffb900' }}>
<Text style={styles.text}>#ffb900</Text>
</View>
</ScrollView>
);
}

const styles = StyleSheet.create({


container: {
flex: 1,
backgroundColor: '#fff',
},
item: {
height: Dimensions.get('window').height * 0.4,
marginBottom: 20,
alignItems: 'center',
justifyContent: 'center',
},
text: {
color: '#fff',
fontSize: 20,
}
});

В результате мы получим область на весь экран, которую можно


прокручивать:
Область прокрутки можно ограничить, добавив обертку вокруг ScrollView.
Давай в стилях добавим новые стили для этой обертки:
const styles = StyleSheet.create({
wrapper: {
height: Dimensions.get('window').height * 0.9,
width: Dimensions.get('window').width * 0.6,
},
container: {
// ...

А вокруг ScrollView добавим саму обертку:


export default function App() {
return (
<View style={styles.wrapper}>
<ScrollView style={styles.container}>
// ...
</ScrollView>
</View>
);
}

Получим следующий результат:

По умолчанию элементы внутри ScrollView выстраиваются вертикально. Часто


дизайнеры на макеты добавляют строку с горизонтальной прокруткой. Для
того, чтобы изменить способ выстраивания элементов,
добавь ScrollView свойство horizontal со значением true:
<ScrollView horizontal>
{ /* ... */ }
</ScrollView>
При горизонтальном отображении очень удобно может быть постраничная
прокрутка. Например, есть на экране отображается галерея фотографий —
удобно один раз провести пальцем, чтобы полностью смахнуть к следующему
фото. Для это используй свойство pagingEnabled. По умолчанию его
значение false, переключи в true чтобы все заработало.
— А можно это свойство использовать для вертикальной прокрутки?

— Не рекомендую. Вертикальная постраничная прокрутка не работает на


Android устройствах.
ScrollView рендерит все свои дочерние элементы за раз. Если таких
элементов будет всего несколько, то никаких проблем с этой особенностью
не будет. Если же таких элементов будет слишком много, но возникнут
проблемы с производительностью. Ведь ресурсы и память и телефона будут
тратиться на рендрер элементов, которые возможно даже не будут отображены
на экране.
Например, лента новостей в Facebook. Нет смысла отрисовывать новость
прошлого месяца — пользователь попросту может даже не дойти до этого
элемента. И зачем его тогда рендерить. В таких случаях лучше будет
использовать другой элемент для списков - FlatList.

FlatList

Особенностью FlatList является ленивый рендер. Это означает, что элементы


будут рендерится в момент, когда они вот-вот должны появится. Элементы,
которые были уже давно прокручены, удаляются — это позволяет экономить
память. Когда удаленный элемент снова должен появится на экране — он
снова будет отрендерен.
Использование этого метода отличается от
использования ScrollView. FlatList нуждается в трех вещах для своей
работы:
o массив данных, которые должны быть отрисованы, передается в
свойстве data;
o метод для рендера элемента — метод, которые передается в
свойстве renderItem. В этот метод передается элемент из массива данных, а
возвращается элемент списка.
o свойство keyExtractor - функция, которая в качестве аргумента принимает
элемент из массива данных. Возвращает она уникальный идентификатор,
который будет служить элементом списка.
Перепишем первый пример со ScrollView на FlatList:
import { StyleSheet, Text, View, FlatList, Dimensions } from 'react-
native';

const DATA_SET = [
{
id: 1,
color: '#56c7c0',
},
{
id: 2,
color: '#38277f',
},
{
id: 3,
color: '#ffb900',
},
];

export default function App() {


const renderItem = ({ item }) => (
<View style={{ ...styles.item, backgroundColor: item.color }}>
<Text style={styles.text}>{item.color}</Text>
</View>
);

return (
<FlatList
data={DATA_SET}
keyExtractor={item => item.id}
renderItem={renderItem}
style={styles.container}>
</FlatList>
);
}
Результат будет точно такой же, как и в первом примере со ScrollView.
Одно замечания еще по поводу keyExtractor. Его использование в принципе
не всегда обязательно. Функция по умолчанию ищет поле item.key. Если оно
найдено, его значение используется в качестве ключа. Если нет — функция
ищет item.id и использует его, если нашла. А если и этого поля не
окажется — тогда в качестве ключа будет использовать индекс элемента в
массиве.
Элементы во FlatList тоже можно выстроить горизонтально с помощью
свойства horizontal true.
initialNumToRender - довольно полезное свойство FlatList. С его помощью
можно указать, сколько элементов списка должно быть изначально
отрисовано. Таким образом ты сам можешь проконтролировать, что начальные
элементы точно отрисуются для пользователя.

Эти элементы никогда не будут удалены при прокрутке для улучшения


производительности при прокрутке вверх — иначе каждый раз при прокрутке
вверх все эти элементы будут рендерится. По-этому при выборе их
количества лучше не указывать намного больше, чем размер экрана.

SectionList

Третий вариант списка в React Native - это список секций SectionList.


Массив элементов этого списка — это секция, у которой может быть
заголовок, а уже внутри секции будут элементы списка. В отличие
от FlatList, массив секций передается с помощью свойства sections. Еще
одно отличие от FlatList, это свойство renderSectionHeader. Переданная в
это свойство функция должна рендерить заголовок секции.

Давай посмотрим на примере, как это работает:


import React from 'react';
import { StyleSheet, Text, View, SectionList, Dimensions, StatusBar,
SafeAreaView } from 'react-native';
const DATA_SET = [
{
title: "Trucks and Vans",
data: ["Ford ranger", "Ford F-150", "Toyota Hilux"],
},
{
title: "Suvs and Crossovers",
data: ["Ford Escape", "Hyundai Santa Fe", "Kia Sorento"],
},
{
title: "Sedans",
data: ["Hyundai Sonata", "Kia k5", "VW Passat"],
},
]

const Item = ({ text }) => (


<View style={styles.item}>
<Text style={styles.text}>{text}</Text>
</View>
);

export default function App() {


return (
<SafeAreaView style={styles.container}>
<SectionList
sections={DATA_SET}
keyExtractor={(item, index) => `${item}${index}`}
renderSectionHeader={
({ section: { title } }) => (
<Text style={styles.sectionHeader}>{title}</Text>
)}
renderItem={({ item }) => <Item text={item} />}
/>
</SafeAreaView>
);
}

const styles = StyleSheet.create({


container: {
flex: 1,
marginHorizontal: 30,
},
sectionHeader: {
fontSize: 24,
marginBottom: 8,
backgroundColor: '#ffb900',
padding: 8,
},
item: {
alignItems: 'center',
justifyContent: 'center',
marginVertical: 8,
backgroundColor: '#56c7c0',
padding: 40,
},
text: {
color: '#000000',
fontSize: 20,
}
});

Вот что получается в итоге:


Обрати внимание на заголовок первой секции 'Trucks and Vans'. Мы
прокрутили список до второго элемента первой секции - а заголовок
остается на месте. Он там будет до тех пор, пока прокрутка не дойдет до
второй секции и ее заголовок не займет это место. За это отвечает
свойство stickySectionHeadersEnabled. По молчанию это свойство включено
только для iOs. Чтобы включить его и на Android - укажи значение true для
этого свойства, чтобы отключить везде — передай false:
export default function App() {
return (
<SectionList
sections={DATA_SET}
stickySectionHeadersEnabled={false}
// ...
/>
);
}
SafeAreaView

Еще ты увидел, что на предпоследнем примере мы использовали новый


компонент SafeAreaView. Это компонент используется для безопасного
отображения контента для устройств с iOS версий 11 и выше. Чтобы понять,
зачем нам надо использовать этот компонент, давай посмотрим на пример
без SafeAreaView, на котором явно видно проблемы:

Обрати внимание на верхнюю часть экрана. Теперь наш контент налазит на


строку состояния (status bar). Чтобы избежать этого, как раз и
используется SafeAreaView, которая смещает наш контент и не дает ему
залазить в строку состояния. По факту, это View, у которого в нужный
момент добавляется верхний и нижний padding.

StatusBar

Кроме того, чтобы контент не налазил на строку состояния (status bar),


иногда надо изменить ее стиль. Приложение будет выглядеть не полноценным,
если у него будет синий фон и белый текст, а строка состояния будет
светлая с темным текстом.
Для управления строкой состояния в React Native предусмотрен
компонент StatusBar:
import React from 'react';
import { SafeAreaView, StatusBar } from 'react-native';

export default function App() {


return (
<SafeAreaView style={{ flex: 1 }}>
<StatusBar barStyle="light-content" backgroundColor="red" />
</SafeAreaView>
)
}
В примере выше мы установили строке состояния стиль light-content с
помощью свойства barStyle. В этом случае в строке состояния будет
отображаться темный фон и светлые иконки. Чтобы иконки были темные, а фон
светлым, это свойство должно равняться dark-content. Начальное значение
- default, для iOS - это dark-content, для android - light-content.
Кроме стандартных цветов для фона (светлый и темный), можно указать свой
с помощью свойства backgroundColor. В примере выше мы сделали задний фон
красным.
Чтобы полностью скрыть строку состояния — добавь свойство hidden со
значением true.
Иногда приходится менять стиль строки состояния в зависимости от логики
работы приложения. Например, на одном экране она должна быть с черным
задним фоном и светлыми иконками, а на другом — бежевая с темными
иконками. Понятное дело, что ты будешь просто менять значение нужных
свойств. Но чтобы изменения не были резкими, можно использовать
свойство animated:
import React, { useState } from 'react';
import { SafeAreaView, StatusBar, Button } from 'react-native';

export default function App() {


const [theme, setTheme] = useState('light');
return (
<SafeAreaView style={{ flex: 1 }}>
<StatusBar
Animated
barStyle={`${theme}-content`}
backgroundColor={theme === 'dark' ? 'beige' : 'black'} />
<Button
title="Toggle Status Bar"
onPress={() => setTheme(theme === 'dark' ? 'light': 'dark')}
/>
</SafeAreaView>
)
}
Свойство animated анимирует изменения
свойств hidden, barStyle и backgroundColor.

355 RN: Введение и Стек


Навигатор

Всего, что мы уже выучили, достаточно для создания разных экранов. Мы


можем создавать списки, можем добавлять элементы для нажатия,
изображения. Но размещать все это на одном экране, или замещать одни
элементы другими очень сложно. Это сложно как для разработки, так и для
поддержки.

Для решения этой проблемы надо разбивать приложение на экраны. Самый


просто пример такого разбиения это экран списка и экран деталей. С экрана
списка, кликнув по его элементу, можно попасть на экран деталей нажатого
элемента. С экрана деталей можно вернуться на экран списка, нажав кнопку
назад.
Каждый экран — это обычный компонент React. Обычно эти компоненты
размещают в папке screens:
|-- App.js
|-- screens
| |-- Details
| | |-- index.js
| |-- List
| | |-- index.js

Подключение навигации

В браузере переключаться между страницами сайта можно с помощью тега <a>.


При нажатии на ссылку, адрес страницы попадает в стек браузера. Из этого
же стека берется адрес страницы, когда мы нажимаем на кнопку назад.
В React Native нет встроенного механизма для обработки таких переходов.
За переход между этими экранами отвечает так называемый навигатор.
Навигатор создается с помощью сторонних библиотек, которые являются
частью react-navigation. Добавить их в проект можно с помощью следующей
команды:
npm install @react-navigation/native @react-navigation/stack
@react-navigation/native - отвечает на интеграцию навигаторов в
приложение React Native. @react-navigation/stack отвечает на переход
между экранами и историю посещенных экранов. Теперь добавим прочие
необходимые зависимости:
npm install react-native-reanimated react-native-gesture-handler
react-native-screens react-native-safe-area-context @react-native-
community/masked-view
react-native-gesture-handler - эта библиотека отвечает на обработку
жестов, связанных с навигацией. С помощью этой библиотеки обработка
нажатий происходит не в потоке JS, а в основном потоке. Это увеличивает
плавность обработки нажатий и их надежность.
Подключать ее один раз на весь проект будет достаточно. App.js файл будет
отличным место для этого.
// App.js
import 'react-native-gesture-handler';
import * as React from 'react';

export default function App() {


return null;
}
Следующим шагом мы обернем все наше приложение в контейнер
навигации NavigationContainer. Этот контейнер связывает навигатор
верхнего уровня со средой приложения. Один из примеров, за что отвечает
этот контейнер — кнопка назад на Android. Для
добавления NavigationContainer нам пригодится ранее установленная
библиотека @react-navigation/native, которая и отвечает за интеграцию
навигации в приложения, как уже говорилось ранее. Чтобы обернуть все
приложение этим контейнером, добавим его в файле App.js:
import 'react-native-gesture-handler';
import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';

export default function App() {


return <NavigationContainer /> ;
}
Следующим шагом будет добавления стека. Чтобы его добавить, нужно его
сначала создать с помощью метода createStackNavigator из
библиотеки @react-navigation/stack. Сразу импортируем в App.js экраны
нашего приложения:
import 'react-native-gesture-handler';
import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

// Screens
import ListScreen from './screens/List';
import DetailsScreen from './screens/Details';

// создаем стек
const Stack = createStackNavigator();

export default function App() {


return (
<NavigationContainer>
<Stack.Navigator />
</NavigationContainer>
);
}
На примере выше мы используем компонент Stack.Navigator для создания
навигатора нашего приложения. Для того, чтобы добавить экран приложения
используй компонент Stack.Screen. Этому компоненту нужно добавить два
свойства: name и component. Свойство name мы дальше будет использовать
для перехода между экранами. В свойстве component мы передаем
непосредственно экран, который нужно отобразить. Добавлять экраны нужно
внутри стека, который отвечает ха переходы между ними:
import 'react-native-gesture-handler';
import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

// Screens
import ListScreen from './screens/List';
import DetailsScreen from './screens/Details';

const Stack = createStackNavigator();

export default function App() {


return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="List" component={ListScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
— Мы добавили два экрана, какой же из них будет отображен при загрузке
приложения?
— Хороший вопрос. Экран, который указан первым и станет начальным
экраном. Это можно изменить. Чтобы указать, какой из экранов будет
отображаться при начальной загрузке приложения. За это отвечает
свойство initialRouteName компонента Stack.Navigator. Добавим его со
значением List, чтобы первым загрузился экран со списком:
import 'react-native-gesture-handler';
import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

// Screens
import ListScreen from './screens/List';
import DetailsScreen from './screens/Details';

const Stack = createStackNavigator();

export default function App() {


return (
<NavigationContainer>
<Stack.Navigator initialRouteName="List">
<Stack.Screen name="List" component={ListScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
Переход между экранами

Теперь, когда мы первым загружаем экран List, нам надо сделать переход на
экран Details. Для начала посмотрим на код экрана List:
import React from 'react';
import { View, Text, Button } from 'react-native';

export default function List() {


return (
<View>
<Text>List screen</Text>
<View>
<Text>Item 1</Text>
<Button
title="Item 1 details"
onPress={() => { /*переход на экран Details для первого элемента*/
}}
/>
</View>
<View>
<Text>Item 2</Text>
<Button
title="Item 2 details"
onPress={() => { /*переход на экран Details для второго элемента*/
}}
/>
</View>
</View>
)
}
У нас есть две кнопки, и при нажатии на каждую из них должен произойти
переход на экран Details. Тут нам поможет свойство navigation. Это
свойство автоматически передается каждому компоненту, который указывается
в свойстве component компонента Stack.Screen.
Свойство navigation - это объект. У него есть разные свойства и методы,
но из них нам сейчас понадобится только метод navigate. Этому методу
нужно передать имя экрана, на который мы хотим попасть. В нашем случае
это Details:
import React from 'react';
import { View, Text, Button } from 'react-native';

// получаем свойство navigation из props


export default function List({ navigation }) {
return (
<View>
<Text>List screen</Text>
<View>
<Text>Item 1</Text>
<Button
title="Item 1 details"
onPress={() => navigation.navigate('Details')}
/>
</View>
<View>
<Text>Item 2</Text>
<Button
title="Item 2 details"
onPress={() => navigation.navigate('Details')}
/>
</View>
</View>
)
}
Супер, теперь мы сможем попасть на экран Details. Но на экране деталей
элементы хотелось бы понимать, детали какого именно элемента списка нужно
показать. Для этого нам надо передать параметры при переходе

Передача параметров

Для передачи параметров при переходе с одного экрана на другой нужно


добавить методу navigate второй аргумент. Это должен быть объект, поля и
значения которого и станут нужными нам параметрами. Давай передадим на
экран Details параметр itemId. Чтобы полностью не повторять весь код,
покажу только на примере кнопки первого элемента списка:
/*...*/
<Button
title="Item 1 details"
onPress={() => navigation.navigate('Details', { itemId: 1 })}
/>
/*...*/
Мы передали нужный параметр. Теперь на экране Details нам нужно его
получить. Здесь нам пригодится свойство route. Как и navigation, это
свойство автоматически передается на каждый экран. В нем вы можете найти
всю информацию о текущем пути (от английского «route» - путь), который
есть на каждом экране. Таким образом, route - это свойство пути,
а navigation - свойство навигатора.
Переданные параметры берутся из свойства route.params. Рассмотрим код для
экрана Details:
import React from 'react';
import { View, Text, Button } from 'react-native';

// получаем свойство route из props


export default function Details({ route }) {
return (
<View>
<Text>Details: item id - {route.params.itemId}</Text>
<Button
title="Go back to List"
onPress={() => {/* переход обратно на предыдущий экран */}}
/>
</View>
)
}

Возврат на предыдущий экран

Теперь нам надо вернуться на экран List. Сделать это можно уже известным
нам способом navigation.navigate('List'). В нашем случае мы действительно
вернемся на один экран назад. А что если между
экраном List и Details было бы еще несколько экранов. Тогда эим способом
мы просто перескочим все эти экраны и попадем на экран List.
Чтобы вернуться именно на один экран назад, как это происходит в
браузере, используй метод .goBack() свойства navigation:
import React from 'react';
import { View, Text, Button } from 'react-native';

// получаем свойства route и navigation из props


export default function Details({ route }) {
return (
<View>
<Text>Details: item id - {route.params.itemId}</Text>
<Button
title="Go back to List"
onPress={() => navigation.goBack()}
/>
</View>
)
}
Кроме метода .goBack() есть еще одна стандартная опция для возврата на
предыдущий экран. Навигатор автоматически добавить хедер экрану с кнопкой
для возврата на предыдущий экран, когда есть возможность сделать это с
активного экрана. А на устройствах Android кнопка назад будет работать
как ожидается. Ведь не зря же мы используем библиотеку @react-
navigation/native.
Еще один из способов возврата назад — возврат на начальный экран стека.
Снова таки, подойдет вариант с navigation.navigate. Но тут нам надо
вспоминать или проверять название первого экрана. Альтернатива этому
- navigation.popToTop(). Вызов этого метода вернет нас к первому экрану в
стеке.

356 RN: Хедер и навигатор вкладок


Хедер

Ранее мы уже вспоминали про хедер экрана. На нем отображается по


возможности кнопка возврата на предыдущий экран. Кроме этой кнопки хедер
отображает название активного экрана. Не всегда, имя которое мы
присваиваем экрану можно показывать пользователю. А если приложение
поддерживает несколько языков, тогда, в зависимости от локали, надо это
название переводить.
Поскольку заголовок является настройкой экрана, то и изменять его мы
будем с помощью свойств компонента Stack.Screen. Этому компоненту можно
передать свойство options, которое может быть или объектом, или функцией,
возвращающей объект. Заголовок экрана меняется с помощью поля title этого
свойства:
import 'react-native-gesture-handler';
import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
// Screens
import ListScreen from './screens/List';
import DetailsScreen from './screens/Details';

const Stack = createStackNavigator();

export default function App() {


return (
<NavigationContainer>
<Stack.Navigator initialRouteName="List">
<Stack.Screen
name="List"
component={ListScreen}
options={{
title: 'Items list here'
}}
/>
<Stack.Screen
name="Details"
component={DetailsScreen}
options={() => ({
title: 'Items details here'
})}
/>
</Stack.Navigator>
</NavigationContainer>
);
}
Если для указания options ты используешь функцию, она будет вызываться с
объектом, свойствами, который нам уже знакомые: navigation (свойства
навигатора) и route (свойство пути):
/* ... */
<Stack.Screen
name="Details"
component={DetailsScreen}
options={({ navigation, route }) => ({
title: route.params.name // например на экране деталей покажем имя
элемента в качестве заголовка
})}
/>
/* ... */
Чтобы полностью спрятать хедер, если он не нужен, просто добавь
поле headerShown со значением false в объект options.
Для стилизации хедера надо добавь поле headerStyle. Дальше добавляй
нужные стили для View, который оборачивает хедер — ведь все в React
Native это View. Но это свойство изменит только контейнер. Для изменения
шрифта, цвета и интенсивности текста добавь поле headerTitleStyle. С
помощью поля headerTintColor можно изменить цвет кнопки Назад и ее текст.
Вот как это все используется на примере:
<Stack.Screen
name="Details"
component={DetailsScreen}
options={() => ({
title: 'Items details here',
headerStyle: {
backgroundColor: 'red',
},
headerTintColor: 'yellow',
headerTitleStyle: {
fontWeight: 'bold',
fontSize: 20,
},
})}
/>
Но объект options можно изменить и внутри компонента, который
соответствует экрану. Делается это с помощью
метода setOptions свойства navigation. Давай на экране Details добавим
кнопку, при нажатии которой заголовок экрана будет меняться на I've
updated you:
import React from 'react';
import { View, Text, Button } from 'react-native';

export default function Details({ route }) {


return (
<View>
<Text>Details: item id - {route.params.itemId}</Text>
<Button
title="Go back to List"
onPress={() => navigation.goBack()}
/>
<Button
title="Go back to List"
onPress={() => navigation.setOptions({ title: "I've updated you" })}
/>
</View>
);
}

Кнопки хедера

У хедера может быть две кнопки — левая и правая. За них отвечают


поля headerLeft и headerRight соответственно. Для изменения каждой надо
передать функцию, которая будет возвращать кнопку для отрисовки. Для
левой кнопки по умолчанию используется компонент HeaderBackButton из
библиотеки @react-navigation/stack.
Добавим для экрана Details правую кнопку, которая будет просто считать
нажатия:
import 'react-native-gesture-handler';
import React, { useState } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { Button } from 'react-native';

// Screens
import ListScreen from './screens/List';
import DetailsScreen from './screens/Details';

const Stack = createStackNavigator();

export default function App() {


const [count, setCount] = useState(0);

return (
<NavigationContainer>
<Stack.Navigator initialRouteName="List">
<Stack.Screen
name="List"
component={ListScreen}
/>
<Stack.Screen
name="Details"
component={DetailsScreen}
options={{
headerRight: () => (
<Button
onPress={() => setCount(count + 1)}
title="Counter"
/>
),
}}
/>
</Stack.Navigator>
</NavigationContainer>
);
}

Точно таким же образом можно переопределить и левую кнопку.

Кастомный заголовок

Можно переопределять не только левую кнопку. В качестве хедера


использовать кастомный компонент. В этом поможет поле headerTitle,
которому нужно передать функцию:
import 'react-native-gesture-handler';
import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { Text } from './react-native';

// Screens
import ListScreen from './screens/List';
import DetailsScreen from './screens/Details';

const Stack = createStackNavigator();

const ScreenHeader = ({ title }) => <Text>{title || "Default screen


title"}</Text>

export default function App() {


return (
<NavigationContainer>
<Stack.Navigator initialRouteName="List">
<Stack.Screen
name="List"
component={ListScreen}
options={{ headerTitle: props => <ScreenHeader {...props} /> }}
/>
<Stack.Screen
name="Details"
component={DetailsScreen}
options={{ headerTitle: props => <ScreenHeader {...props}
title="Details Header" /> }}
/>
</Stack.Navigator>
</NavigationContainer>
);
}
При этом, если поле title ты оставишь, это не будет ошибкой. Его значение
будет использовано в качестве запасного варианта для headerTitle.

Навигатор вкладок

Мы обсудили хедер мобильного приложения. Но самым частым способом


построения мобильных приложений есть наличие нижней навигации. Конечно,
можно самому сделать компонент для этого, однако в React Navigation
предусмотрен специальный навигатор для этого - TabNavigator. Навигатор
вкладок является самый распространенным способом навигации в мобильных
приложения.

Каждый экран в таком навигаторе — это вкладка, которой соответствует


кнопка в навигации. Чаще всего навигацию показывают внизу экрана, так как
в этом месте пользователю удобнее всего ею пользоваться. Тем не менее эту
навигацию можно размещать и под хедером, или даже вместо него.

Для добавления такого навигатора надо сначала установить нужную


библиотеку:
npm install @react-navigation/bottom-tabs
Теперь, как и в случае со стек навигатор, создадим навигатор вкладок с
помощью метода createBottomTabNavigator():
import * as React from 'react';
import { Text, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-
tabs';

const Tab = createBottomTabNavigator();

export default function App() {


return (
<NavigationContainer>
<Tab.Navigator />
</NavigationContainer>
);
}
Теперь добавим три экрана Home, Settings, Profile. Представим, что
компоненты соответствующих экранов созданы и находятся в папке screens:
import * as React from 'react';
import { Text, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-
tabs';

// Screens
import HomeScreen from './screens/Home'
import SettingsScreen from './screens/Settings'
import ProfileScreen from './screens/Profile'

const Tab = createBottomTabNavigator();

export default function App() {


return (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
<Tab.Screen name="Profile" component={ProfileScreen} />
</Tab.Navigator>
</NavigationContainer>
);
}
Как видишь, кроме метода создания навигатора, особых отличий больше нет.
Автоматически снизу появится навигационное меню, на котором сразу
появится три кнопки. По умолчанию на кнопках будет отображаться значение
поля name каждого экрана. Лейбл активного экрана будет подсвечиваться
другого цвета. Обычно это синий цвет, у не активной кнопки цвет лейбла
серый.
Для изменения цвет текста для активного и неактивного состояний кнопки
можно использовать свойство tabBarOptions компонента Tab.Navigator. Этому
свойству можно передать объект с двумя полями - activeTintColor для цвета
активного состояния и inactiveTintColor для неактивного:
/* ... */
<Tab.Navigator
tabBarOptions={{
activeTintColor: 'red',
inactiveTintColor: 'yellow',
}}
>
{/* ... */}
</Tab.Navigator>
/* ... */
Если ты хочешь изменить отображение кнопки по умолчанию, надо
использовать свойство screenOptions компонента Tab.Navigator. Этому
свойству можно передать или объект, или функцию, которая возвращает
объект. Функция в качестве аргумента получает объект
с route и navigation свойствами.
Для стилизации контейнера навигации добавь свойство style.
Для изменения иконок кнопок отвечает свойство tabBarIcon. Для добавления
иконок в примере будем использовать библиотеку react-native-vector-
icons/MaterialIcons. Разберем следующий пример:
import Icons from 'react-native-vector-icons/MaterialIcons';

/* ... */
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
let iconName = 'Home';

if (route.name === 'Settings') iconName = 'settings';


if (route.name === 'Profile') iconName = focused ? 'person-outline'
: 'person';

return <Icons name={iconName} size={size} color={color} />;


},
})}
tabBarOptions={{
activeTintColor: 'red',
inactiveTintColor: 'yellow',
}}
>
{/* ... */}
</Tab.Navigator>
/* ... */
tabBarIcon - это метод, который в качестве аргумента получает объект с
тремя свойствами: focused, color, size. focused показывает, активен ли
сейчас экран, соответствующий иконке. color - цвет из
объекта tabBarOptions. size - размер кнопки навигатора. Вообще
метод tabBarIcon необязательно должен возвращать иконку, с тем же успехом
можно вернуть текстовый элемент. Можно также возвращать свой кастомный
компонент.
Свойства отдельного экрана изменяется с помощью
свойства options компонента Tab.Screen:
<Tab.Screen
name="Home"
component={HomeScreen}
options={{
tabBarLabel: 'Home Tab'
tabBarIcon: ({ color, size }) => (
<Icons name="home" />
),
}}
/>
tabBarLabel изменит дефолтный текст кнопки. Если надо полностью скрыть
текст с кнопки, добавь свойство showLabel со значением false. Это же
свойство можно использовать в контейнере в свойстве tabBarOptions, чтобы
применить эти изменения для всех кнопок.

Вложенные навигаторы

У каждой вкладки может быть несколько экранов. Например, рассмотренную


выше структуры можно немного изменить. Давай объединим
экраны Settings и Profile. Для этого создадим стек экранов настроек и уже
этот стек укажем как компонент для вкладки Settings:
import * as React from 'react';
import { Text, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-
tabs';
import { createStackNavigator } from '@react-navigation/stack';

// Screens
import HomeScreen from './screens/Home'
import SettingsScreen from './screens/Settings'
import ProfileScreen from './screens/Profile'

const Tab = createBottomTabNavigator();


const Stack = createStackNavigator();

function Settings() {
return (
<Stack.Navigator>
<Stack.Screen name="Settings" component={SettingsScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
);
}

export default function App() {


return (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Settings" component={Settings} />
</Tab.Navigator>
</NavigationContainer>
);
}
Компонент Settings содержит в себе стек навигатор и для обоих
экранов Settings и Profile кнопка вкладки Settings будет активна. Для
перехожа между экранами, к стати, можно также пользоваться
методом navigation.navigate. Чтобы попасть на экран Settings с
экрана Home надо сделать следующий вызов:
navigation.navigate('Settings');
Переход на экран Settings сработал, потому что этот экран первый в стеке
экранов настроек и имя соответствующего экрана в навигаторе вкладок и в
стек навигаторе совпадают. С переходом на экран Profile все немного
сложнее. Чтобы этот переход сработал, нам передать второй аргумент в
метод navigation.navigate:
navigation.navigate(Setting, { screen: 'Profile' });
Первым аргументом мы указываем, что в навигаторе вкладок переходим на
экран настроек. Поскольку этот экран сам по себе является навигатором,
вторым аргументом мы указываем, какой именно нам экран нужен - в нашем
случае это Profile.

Мы рассмотрели вариант, где стек навигатор вложен в навигаторе вкладок.


Обратное вложение тоже возможно:
import * as React from 'react';
import { Text, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-
tabs';
import { createStackNavigator } from '@react-navigation/stack';

// Screens
import HomeScreen from './screens/Home'
import SettingsScreen from './screens/Settings'
import ProfileScreen from './screens/Profile'

const Tab = createBottomTabNavigator();


const Stack = createStackNavigator();

function SettingsTabs() {
return (
<Tab.Navigator>
<Tab.Screen name="Settings" component={SettingsScreen} />
<Tab.Screen name="Profile" component={ProfileScreen} />
</Tab.Navigator>
);
}

export default function App() {


return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Settings" component={SettingsTabs} />
</Stack.Navigator>
</NavigationContainer>
);
}
Теперь изначально у нас будет грузиться стек с начальным экраном Home.
При переходе на экран Settings или Profile, снизу появится для
переключения вкладок.

357 RN: Модальное окно и Alert,


Drawer навигатор
Компонент Modal

Как и веб сайтах, в мобильных приложениях часто возникает необходимость


показать пользователю модальное окно. Для подтверждения каких либо
действий, или может даже формы логина.
В React Native для этого предусмотрен целый компонент стандартной
библиотеки Modal. Давай его просто добавим в приложение:
import React from 'react';
import { View, Modal, Text } from 'react-native';

export default function App() {


return (
<View>
<Modal>
<Text>Это модальное окно</Text>
</Modal>
</View>
);
}
По умолчанию модальное окно, при его добавлении, будет отображаться. Для
того, чтобы его скрыть, надо изменить значение свойства visible на false.
import React, { useState } from 'react';
import { View, Modal, Text, Button } from 'react-native';

export default function App() {


const [isVisible, setIsVisible] = useState(false);

return (
<View>
<Modal visible={isVisible}>
<Text>Это модальное окно</Text>
<Button
title="Скрыть модалку"
onPress={() => setIsVisible(false)}
/>
</Modal>
<Button
title="Показать модалку"
onPress={() => setIsVisible(true)}
/>
</View>
);
}
По умолчанию, когда модальное окно появится поверх своего отображения,
все отображение будет видно. Для того, чтобы его скрыть, измени значение
свойства transparent на false.
Для того, чтобы стилизовать модальное окно, нужно внутри добавить
компонент View и уже от него отталкиваться, стилизуя его и его дочерние
элементы:
import React, { useState } from 'react';
import { View, Modal, Text, Button, StyleSheet } from 'react-
native';

export default function App() {


const [isVisible, setIsVisible] = useState(false);

return (
<View>
<Modal visible={isVisible}>
<View style={styles.modalView}>
<Text>Это модальное окно</Text>
<Button
title="Скрыть модалку"
onPress={() => setIsVisible(false)}
/>
</View>
</Modal>
<Button
title="Показать модалку"
onPress={() => setIsVisible(true)}
/>
</View>
);
}

const styles = StyleSheet.create({


modalView: {
justifyContent: "center",
alignItems: "center",
},
});
По умолчанию никакой анимации при появлении модального окна нет. За эту
особенность отвечает свойство animationType. Значение по умолчанию
- none. Это начальное поведение можно заменить один из двух видом
анимации со значениями свойства slide и fade.
При анимации slide модальное окно появляется снизу и выезжает наверх.
Когда модальное окно закрывается — оно "уезжает" вниз. При
анимации fade - появляется и исчезает с эффектом изменения прозрачности.

Полноэкранное модальное окно

Изначально модальное окно занимает столько места, на сколько его


растягивает контент внутри него. Чтобы сделать модальное окно на весь
экран телефона, можно изменять размеры контейнера внутри него. Второй
вариант достижения такого эффекта — использования отдельного экрана
навигатора для модального окна.

Самый лучший способ сделать это — вложенные навигаторы. Таким образом


можно отделить экран модального окна от всех остальных экранов.
import 'react-native-gesture-handler';
import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

// Screens
import ListScreen from './screens/List';
import DetailsScreen from './screens/Details';

// главный навигатор приложения


const AppStack = createStackNavigator();
// навигатор экранов приложения
const Stack = createStackNavigator();

function Modal({ navigation }) {


return (
<View>
<Text>Это модальное окно</Text>
<Button onPress={() => navigation.goBack()} title="Скрыть модалку"
/>
</View>
);
}

function Screens() {
return (
<Stack.Navigator>
<Stack.Screen name="List" component={ListScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
)
}

export default function App() {


return (
<NavigationContainer>
<AppStack.Navigator>
<AppStack.Screen name="Screens" component={Screens} />
<AppStack.Screen name="Modal" component={Modal} />
</AppStack.Navigator>
</NavigationContainer>
);
}
Для того, чтобы открыть модальное окно с любого экрана, достаточно
вызвать метод navigation.navigate:
navigation.navigate('Modal')
Для выхода из модального окна в примере мы добавили кнопку, при нажатии
на которую вызывается метод navigation.goBack(). Также можно попасть на
любой другой экран, если в этом заключается логика приложения.

Alert API

Еще одним вариантом взаимодействия с пользователем в браузере является


диалоговое окно, вызываемое методом alert. В React Native для этих целей
используется API Alert. Импортировать его нужно из стандартной
библиотеки react-native.
Для вызова диалогового окна используй метод Alert.alert. У этого метода
есть один обязательный аргумент title, который является заголовком
диалогового окна. Если передать первым аргументом null или пустую строку
— заголовок будет скрыт.
Вторым, уже не обязательным аргументом метода будет message. Это
сообщение, которое ты хочешь показать пользователю. Если ничего не
передать — будет отображен только заголовок.
Третий, тоже не обязательный аргумент — массив кнопок, которые будут
отображаться по д сообщением (или заголовком, если сообщения не будет).
Если массив кнопок не передать, будет отображена кнопка Ok, которая при
нажатии на нее, просто закроет диалоговое окно. Каждая кнопка описывается
объектом с полями onPress (действие при нажатии на кнопке) и text (лейбл
кнопки).

Давай напишем диалоговое окно подтверждение выхода пользователя:


import React from 'react';
import { Alert, Button, View } from 'react-native';

export default function App() {


const handleLogout = () => {
// логика выхода пользователя
}

const confirmLogout = () => Alert.alert(


"Выход",
"Вы действительно хотите выйти",
[
{
title: 'Да',
onPress: handleLogout,
},
{
title: 'Нет',
}
]
);

return (
<View>
<Button
title="Logout"
onPress={confirmLogout}
/>
</View>
)
}
При нажатии на любую кнопку диалоговое окно будет закрыто. Однако при
нажатии на кнопку Да будет вызван метод handleLogout с логикой выхода
пользователя из приложения.

Drawer навигатор

Навигационное меню тоже можно скрывать от пользователя и показывать


только когда оно ему нужно или этого требует логика приложения. Для этого
надо использовать еще один навигатор Drawer. Для начала его надо
установить:
npm install @react-navigation/drawer
Дальнейшее подключение особо ничем не отличается от других навигаторов,
кроме метода создания навигатора:
import * as React from 'react';
import { Text, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createDrawerNavigator } from '@react-navigation/drawer';

// Screens
import HomeScreen from './screens/Home'
import SettingsScreen from './screens/Settings'
import ProfileScreen from './screens/Profile'

const Drawer = createDrawerNavigator();

export default function App() {


return (
<NavigationContainer>
<Drawer.Navigator>
<Drawer.Screen name="Home" component={HomeScreen} />
<Drawer.Screen name="Settings" component={SettingsScreen} />
<Drawer.Screen name="Profile" component={ProfileScreen} />
</Drawer.Navigator>
</NavigationContainer>
);
}

Теперь когда пользователь проведет с левого края экрана вправо, то увидит


боковое навигационное меню. При нажатии на пункт меню, пользователь
перейдет на выбранный экран, а меню скроется.
Если надо боковое меню открыть программно с какого-то экрана, то в
свойстве navigation появляется новый метод openDrawer, а для закрытия
- closeDrawer.
navigation.openDrawer();
navigation.closeDrawer();
А если надо просто переключить состояние меню, а не именно закрыть или
открыть (напр. ты не знаешь - в этот момент меню открыто или все таки
закрыто) используй метод toggleDrawer:
navigation.toggleDrawer();
Логично будет предположить, что было бы круто знать, открыто меню или
нет, прежде чем что-то с ним делать. Для этого в библиотеке @react-
navigation/drawer предусмотрен метод useIsDrawerOpen:
import { useIsDrawerOpen } from '@react-navigation/drawer';

const isDrawerOpen = useIsDrawerOpen();


isDrawerOpen будет равняться true, если меню открыто, и false - если
закрыто.

Сделай сам
Помнишь, в одной из первых лекций, мы рассказывали тебе об алгоритме
двоичного поиска?
Это - один из классических алгоритмов. Найти его реализацию
на JavaScript можно в интернете за 5 секунд. Еще за 10 секунд проверить
работоспособность и отправить задачу на проверку. Но это - путь в никуда.

Решая задачи самостоятельно, ты прокачиваешь свои навыки. Может решение


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

Копируя чужой код, ты, возможно, получишь секундное удовольствие от


"зеленых галочек", но пользы они тебе не принесут. Ты можешь говорить
себе сколько угодно, что ты "и сам бы додумался, просто лень было", или
"разобрал детально это решение и хорошо его понял", но это все самообман.
Можешь воспринимать свой мозг как мышцы, а CoderslangJS, как тренажерный
зал.

Ты можешь спросить совет, если чувствуешь, что не понимаешь как делать


какое-то упражнение. В зале всегда найдутся люди готовые помочь тебе. В
конце-концов можешь просто поболтать с кем-то о жизни, если устал тягать
железо.

Но просить кого-то поднять за тебя штангу? Это нонсенс. Точно такая же


ерунда, как копировать чужой код и радоваться сданной задаче.

По одному биту за раз

Побитовые операторы не очень распространены в JS, но иногда без них не


обойтись.
Чтобы понять, как работать с побитовыми операторами, тебе нужно сначала
разобраться как работает двоичная система счисления. Если ты хочешь
освежить память, то мы проходили этот материал в этой лекции.

Обзор побитовых операций в JS

Оператор Название Описание

Если оба бита равны 1 - получим 1. Если


& И (AND)
хотя бы один равен 0 - то получим 0.

Если оба бита равны 0 - получим 0. Если


| ИЛИ (OR)
хотя бы один равен 1 - то получим 1.

Исключающее ИЛИ Если оба бита одинаковые - получим 0.


^
(XOR) Если разные - 1.

~ НЕ (NOT) Инвертирует все биты в числе.

Сдвигает все биты числа влево и


<< Сдвиг влево
заполняет пустое место нулями.

Сдвигает все биты числа вправо и


>> Сдвиг вправо
заполняет пустое место нулями для
Оператор Название Описание

положительных чисел и единицами для


отрицательных.

Беззнаковый сдвиг Сдвигает все биты числа вправо и


>>>
вправо заполняет пустое место нулями.

Примеры и использование

Побитовые операции довольно сложно понять, если применять их к десятичным


числам.

Я советую тебе взять бумагу и карандаш и записывать пошагово все


действия.

"И", "ИЛИ", "Исключающее ИЛИ"


Операторам &, | и ^ нужно два числа, чтобы работать правильно. Это
- бинарные операторы. Они сравнивают значения битов на одинаковых
позициях в этих числах и получают результат.

Давай попробуем на примерах. Двоичная запись числах в комментарии:


const x = 5; // 0101
const y = 6; // 0110

Будет проще, если ты будешь записывать двоичные числа одно под другим,
вот так:
AND 0101 OR 0101 XOR 0101
0110 0110 0110
---- ---- ----
0100 0111 0011
Теперь мы можем добавить console.log и проверить правильно ли мы
посчитали:
console.log(x & y); // 4
console.log(x || y); // 7
console.log(x ^ y); // 3

Важный момент с XOR состоит в том, что если передать ему два одинаковых
числа, то в результате всегда получится ноль.
console.log(x ^ x); // 0
console.log(y ^ y); // 0
С другой стороны, если мы заменим ^ на & или | , число не изменится.

Оператор "НЕ"
Оператор ~ работает иначе. Он называется унарным оператором и ему нужно
только одно число. Работает он так:
NOT 0101 => 1010
NOT 0110 => 1001

Или в JS:
console.log(~x); // -6
console.log(~y); // -7

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


который инвертируется вместе с остальными. Если он становиться нулем, то
число положительное. Единицей - отрицательное.
Если ты применишь оператор ~ к одному и тому же числу дважды, ты получишь
то с чего начал:
console.log(~~x); // 5

Бинарные операторы сдвига

Операторам сдвига нужно два операнда, чтобы работать правильно. Первый -


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

Сдвиг числа влево на 1 бит - это то же самое, что умножение этого числа
на 2.
console.log(x << 1); // 10
console.log(y << 2); // 24

Исключение - ситуация, когда не хватает битов для того, чтобы сохранить


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

Сдвиг вправо

Правый сдвиг, в отличие от левого, уменьшает число. В случае, если число


достаточно большое и при сдвиге не возникает "лишних" битов, правый сдвиг
будет делить число на два. Мы в примерах используем небольшие числа,
поэтому все не так просто:
console.log(x >> 1); // 2
console.log(y >> 2); // 1

Не ленись, возьми карандаш и проверь расчеты самостоятельно!

Беззнаковый сдвиг влево

Беззнаковый сдвиг отличается от обычного сдвига вправо тем, что он не


сохраняет знак числа. Он делает все отрицательные числа положительными
потому, что заполняет освободившиеся биты нулями.
const n = 100;
const m = -100;

console.log(n >>> 2); // 25


console.log(m >>> 2); // 1073741799
В первом примере нет сюрпризов и 100 ожидаемо стало в четыре раза меньше.
Но ты понимаешь почему -100 превратилось в 1073741799?
Выводы

Если ты будешь использовать JavaScript исключительно для веб разработки,


то ты вряд ли будешь часто встречаться с побитовыми операциями.

Но на собеседованиях часто просят решить какую-то задачу только


анализируя нули и единицы. Например, тебя могут попросить написать
программу, которая определяет является ли число четным.
Для подготовки к собеседованиям мы сделали приложение Coderslang. В нем
более 1300 вопросов по JavaScript, HTML/CSS, Java, C#, Node.js, React.js,
React Native и QA. Скачать его можно на iOS и Android.

Сортировка
Сортировка - это процесс упорядочивания последовательности объектов.
Объектами могут быть любые сущности которые можно сравнить.

Представь интернет магазин. Ты вводишь запрос в строку поиска и получаешь


список результатов. Чтобы найти самый дешевый товар, тебе нужно выполнить
сортировку по возрастанию цены.

Если ты просматриваешь свои траты с кредитной карты и хочешь начать с


самых крупных, тебе тоже нужна сортировка.

Под капотом многих компьютерных программ сортировка используется для


того, чтобы увеличить эффективность других операций, например поиска.

Сортировка пузырьком

Одним из простейших алгоритмов сортировки считается сортировка пузырьком.


В ней все объекты становятся похожими на пузырьки воздуха, которые
всплывают вверх к поверхности воды.
Мы сравниваем попарно соседние элементы массива и меняем их местами, если
текущий элемент больше следующего. Когда дойдем до конца массива, то
самый последний элемент будет гарантированно на своем месте. Мы повторим
процедуру еще раз, но уже до предпоследнего элемента.

После второй итерации, на своих позициях будет уже 2 последних элемента.


Будем повторять алгоритм до тех пор, пока у нас не останется только
первый элемент.

Оптимизация пузырька

Алгоритм пузырьковой сортировки никак не следит за текущим состоянием


массива.

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

Быстродействие можно улучшить, если добавить флаг (логическую переменную)


который будет следить за тем был ли хотя бы один обмен на текущей
итерации.

Если нет, то массив отсортирован и задача выполнена.


Шейкерная сортировка

Coctail sort или Шейкерная сортировка - это еще одно улучшение


"пузырька". Альтернативные названия этой сортировки - сортировка
перемешиванием или bidirectional sort.

Начинаем мы точно так же, как и в пузырьковой сортировке, и "выдавливаем


наверх" максимальный элемент. После этого, разворачиваемся и "толкаем
вниз" минимальный из оставшийся элементов.

Оказавшись в начале массива, на своих местах будет уже 2 элемента -


первый и последний. Так как на каждом проходе мы устанавливаем два
элемента на свои места, то мы сделаем в 2 раза меньше итераций внешнего
цикла. За счет этого скорость шейкерной сортировки будет немного выше,
чем у пузырька.

Сортировка выбором

Если предыдущие алгоритмы показались тебе не слишком очевидными, то тебе


точно понравится сортировка выбором.
Логика такая:
o находим минимальный элемент в массиве

o меняем его с элементом на первой позиции

o переходим на шаг 1 и сортируем оставшуюся часть массива, исключив


отсортированные элементы

В сортировке выбором мы делаем всего один обмен за каждый проход. Находим


минимальный элемент во всем массиве и ставим его на первую позицию. С
каждой итерацией анализируем на 1 элемент меньше. Останавливаемся, когда
дойдем до конца массива и все элементы будут стоять на своих местах.

Чтобы изменить порядок сортировки, тебе нужно будет искать максимальный


элемент вместо минимального. Обычно для этого достаточно просто изменить
знак "больше" на "меньше".
Задачи

Сортировка

Улучшенные алгоритмы сортировки

Оценка эффективности алгоритмов


У всех алгоритмов есть 2 главные характеристики:
1. Количество необходимой памяти.

2. Время выполнения.

Их используют для того, чтобы сравнивать алгоритмы между собой. Одни


алгоритмы работают быстрее, но требуют больше памяти, а другие наоборот.

Для большинства алгоритмов время выполнения и количество памяти нельзя


выразить одним числом. Чем больше элементов будет во входящем массиве,
тем дольше будут выполняться поиск и сортировка.
Для описания эффективности алгоритмов придумали нотацию Big-O.

Постоянная сложность - О(1)

Самый эффективный алгоритм в теории работает за постоянное время и


расходует постоянное количество памяти. Как ни увеличивай объем входящих
данных, сложность алгоритма не повысится. Эффективность (или сложность)
такого алгоритма называют константным и записывают как O(1).

Пример алгоритма с постоянной сложностью:


const getArrayElement = (arr, i) => arr[i];
На вход получаем массив arr и индекс i. Возвращаем элемент массива на
позиции i.

Скорость выполнения и объем требуемой памяти этого алгоритма не зависит


от длины входящего массива, поэтому сложность считается постоянной.

Линейная сложность - O(n)

Сложность многих алгоритмов растет прямо пропорционально изменению


количества входящих данных.

Хороший пример - линейный поиск:


const findArrayElement = (arr, x) => {
for (let i = 0; i < arr.length; i++) {
if (arr[i] === x) {
return i;
}
}

return -1;
}

Если поиск в массиве из 10 элементов занимает 5 секунд, то в массиве из


100 элементов мы будем искать 100 секунд. В 10 раз дольше.
Такую сложность (эффективность) называют линейной и записывают как О(n).
Но, заметь, что алгоритму линейного поиска не нужна дополнительная
память. Поэтому, по этой характеристике он получает высший балл - O(1).

Квадратичная сложность - O(n^2^)

Простые алгоритмы сортировки, такие как сортировка пузырьком или


сортировка выбором имеют сложность О(n^2^).

Это не очень эффективно. При росте длины массива в 10 раз, время


выполнения алгоритма увеличится в 10^2^ (в 100) раз!
Так происходит из-за того, что в алгоритме используется вложенный цикл.
Сложность одного прохода мы оцениваем как O(n). Но проходим мы его не 1
раз, а n. Поэтому и получается O(n * n) = O(n^2^).

Логарифмическая сложность - O(log n)

Алгоритмы, которые на каждой итерации могут сократить количество


анализируемых данных имеют логарифмическую сложность - O(log n).

В качестве примера можно представить двоичный поиск. На каждой итерации


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

Вам также может понравиться