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
Первый контакт
«Добро пожаловать в прекрасный мир будущего!»
Вместо слов я издал мычание. Язык, напоминавший лежалую мокрую вату, едва
не провалился в горло. Наверное, так мычали первобытные дикари, приглашая
товарок полакомиться мамонтятиной.
Говорил я вполне сносно. Облизал зубы: все целы. Поморгал. Подышал носом.
Приложил ладонь к сердцу. Тук-тук. Организм работает.
Техник скрылся. Клянусь, красота Сигмы согревала меня. Мои щёки пылали,
точно две звезды. При ней я уже не чувствовал себя обитателем гроба
хладного.
— Держи.
Тут в мозгу, как по команде, проявилось имя. Моё собственное. Я его сразу
узнал. Вовсе не Джон. И не Смит.
Глобальные перспективы
Твоя уникальность, в том, что ты родился еще до того, когда был создан
полноценный искусственный интеллект. Симуляция 2022 для тебя ничем не
будет отличаться от жизни до погружения в камеру анабиоза.
— Если эта лекция покажется тебе слишком сложной или скучной, ты можешь
ее пропустить и вернуться позже. Проблем с этим нет. Ты же хочешь стать
программистом?
Пропустить лекцию
Для начала, представь 10 лампочек. Каждая из них может быть включена или
выключена.
— Ну что ж, попробую все таки показать тебе, что для машин нет ничего
невозможного.
Тогда, мы бы могли получить все комбинации от 0 000 000 000 до 9 999 999
999. Или 10 миллиардов разных чисел, включая 0, конечно.
— Все равно не понятно, как посчитать до 1000 с помощью 10 обычных
лампочек.
У каждой цифры или, другими словами, у каждого разряда, есть свой вес. Он
считается по формуле:
БАЗА в степени ПОЗИЦИЯ.
БАЗА — это основа системы счисления. В привычных людям числах — это 10. В
более естественной для машин, двоичной системе - 2.
Вес позиции 0 = 2 ^ 0 = 1
Вес позиции 1 = 2 ^ 1= 2
Вес позиции 2 = 2 ^ 2 = 4
...
Теперь, чтобы посчитать итоговое число, нам осталось только умножить вес
каждой позиции на ее значение (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
Абстракция
Если стульев было бы 3, а не 12, то проще всего было бы показать их, как
3 пальца на одной руке или 3 включенных лампочки. Это самый простой и
очевидный способ что-то посчитать. Но что делать если их 12, 40, 125?
Пальцев уже не хватит, а где хранить лампочки тоже не очень понятно.
Алгоритмы и псевдокод
В итоге все меньше и меньше людей стало понимать, что происходит внутри
машин. Сначала, компьютер помогал человеку найти нужный номер в списке
контактов, чтобы заказать пиццу по телефону. Потом, начал воспринимать
голосовые команды и самостоятельно начал делать заказы. Дальше — решать в
какой именно пиццерии оформить заказ. И в конце концов — по какой дороге
ехать курьеру.
— Ты прав. При наличии оглавления туда точно стоит взглянуть. Этот метод
называется индексация и он очень часто используется в базах данных.
Но, при всей неоптимальности линейного поиска, он точно поможет нам найти
телефон Петра Васильева или убедиться в том, что в справочнике его нет.
Но, так как этого не было в условии, и мы можем оказаться в мире где 90%
людей с фамилией на букву А, то я бы предложил открыть справочник ровно
посередине. Оценить точность попадания и пойти направо или налево,
отбросив половину записей.
Так как я робот, попробую формализовать этот алгоритм для тебя "почти"
человеческим языком.
— Это мы исправим. Давай пройдем тест, чтобы оценить, как ты усвоил новую
информацию.
Если у тебя возникнут проблемы с тестом, ты можешь перейти к следующей
лекции из меню "". Открываются лекции кликом на иконку с "замком".
Перейти к тесту
Первая программа
— Привет, Герой! Меня зовут Фей, - сказала девушка с темными волосами.
Перед твоим погружением в Симуляцию, я вкратце расскажу тебе о том как
работали программисты в 2022.
— Вы совсем не похожи на программиста, пробормотал я.
— Ты прав. Два года назад меня достали из такой же камеры анабиоза, как и
тебя. В 2022 я была лучшим HR специалистом в отделе кадров "компании,
которую нельзя называть". На основной работе я обеспечивала комфортные
условия работы команды, занималась поиском и наймом новых технических
специалистов.
— Интересно.
Впрочем, хватит о грустном. Давай лучше я расскажу о том, что ждет тебя в
2022.
В те времена, люди искренне верили, что у них в запасе есть еще десятки
лет до того как будет создан первый AGI - искусственный интеллект общего
назначения. Большинство компьютерных программ представляли собой обычный
текст.
— Текст?
— Да, именно текст. Компьютеры еще не умели программировать сами себя, а
для людей ничего привычнее текста не было. К тому же, текст намного проще
структурировать чем речь. Для написания программ нужно было следовать
определенным правилам, иначе возникали синтаксические ошибки т.к.
компьютер делал вид не понимал что от него хочет человек.
Если ты еще не настроил VSCode для решения практических задач, сделай это
сейчас.
А теперь перейдем к следующему заданию. Чтобы получить его, нажми еще раз
на кнопку Получить задачи. Задача появится в папке task70.
8. Переменные и константы
2. Строка (String)
Чтобы данными можно было пользоваться, их нужно где-то хранить. Для этого
придумали понятие переменная.
Создать переменную можно с помощью ключевого слова let, а сохранить в нее
значение можно используя оператор =.
Например:
let greeting = 'Hello, world!';
let salary = 100000;
let isWinning = true;
Сразу после ключевого слова let нужно указать имя переменной.
Как следует из названия — переменная может менять свое значение. Чтобы
сменить значение переменной, нам нужен все тот же оператор
присваивания =.
— Hi?
— Точно! Начальное значение переменной greeting мы заменили на Hi и
именно его вывели на экран.
— Чем-то похоже на подписывание контейнера или коробки на складе.
salary = 200000;
Тут, мы:
1. Создали переменную salary и связали ее (сохранили адрес) со значением
100.
2. Создали переменную oldSalary и связали ее с тем же значением 100.
3. Вывели на экран значения переменных salary и oldSalary, чтобы убедиться,
что они равны.
4. Связали переменную salary с новым значением - 200000.
5. Снова вывели на экран значения переменных salary и oldSalary, чтобы
убедиться, что изменилась только salary!
— Действительно, если бы хранила значение, а не адрес значения, то так бы
не получилось сделать. Иначе oldSalary изменилась бы вместе с salary.
— Мне начинает казаться, что еще немного и ты сможешь занять мое место,
Герой.
— А может в переменной ничего не храниться? Коробка ведь вполне может
быть пустой.
— Конечно! В JavaScript "пустые коробки" содержат
значение null или undefined. Между ними есть некоторые отличия, но тебе о
них пока не стоит переживать.
Правила простые:
1. Пишем все слова слитно.
//solution.js
import { NET_PROFIT_MARGIN, REGULAR_SALES_TAX } from
'./constants.js';
10. Функции
Чтобы сохранить этот код в функцию, нам нужно создать переменную (или
константу) :
const sayHello;
Теперь осталось "положить" или "связать" функцию, приветствующую
пользователя, с константой sayHello. Для этого, мы обернем все команды
внутри функции в конструкцию () => {…}. Вместо трех точек мы напишем наши
команды, поставим знак "равно" и функция будет готова!
const sayHello = () => { console.log('Hello, Peter Peterson!'); };
— Так, но мы же планировали приветствовать пользователя с любым именем.
Будет странно, если к нам пришел John Johnson, а мы говорим ему Hello,
Peter Peterson!.
Параметры и аргументы
— Конечно. Смотри:
const addAndPrint = (x, y) => {
console.log(x + y);
}
Мы создали функцию addAndPrint, которая складывает два числа и выводит
результат на экран.
Переменные x и y - это параметры. У них нет значения в момент объявления
функции, но оно появится, когда функция будет вызвана.
Обрати внимание, что тут мы не используем ключевые слова let и const, а
просто пишем параметры в круглых скобках через запятую. Их количество
неограниченно.
//solution.js
import { sayHello } from './functions.js'
user.name = 'John';
Тут мы переименовали пользователя и теперь его зовут не Jack, а John.
— Мне кажется, или мы только что изменили объект, который был объявлен
как константа?
— Верно, но мы не изменили значение user - он по-прежнему ссылается на
тот же объект. Изменилось только его внутреннее состояние, а это
разрешено.
console.log(animals.length);
console.log(animals.length);
— Дай угадаю, мы выводим на экран длину массива перед добавлением нового
элемента и после, чтобы было очевидно ее изменение?
— Именно так. Свойство length есть в любом массиве и оно всегда равно
количеству элементов массива.
12.Принимаем решение
— В программировании, как и в жизни, очень часто возникает необходимость
изменить поведение программы в зависимости от какого-то условия.
Несколько примеров:
Если пойдет дождь, то используй зонтик. Если будет солнечно и ты пойдешь
на пляж, то используй солнцезащитный крем. Если на часах больше 22
часов, то активируй темную тему приложения.
Тут мы видим ключевые слова выделенные полужирным шрифтом. Условие,
выделенное курсивом. И, полезное действие, выделенное подчеркиванием.
— Тогда у тебя получится бесконечный цикл, так как условие выхода никогда
не будет достигнуто. Вывод на экран не прекратится до тех пор, пока не
закончится память выделенная твоей программе.
14. Строки
— Привет, Герой. Я заменю Сигму на несколько ближайших лекций, - сказал
Техник.
— Что-то случилось?
— Пока я не могу тебе об этом рассказать. Постарайся сфокусироваться на
новом материале. Все вопросы сможешь задать в конце лекции.
— Как скажешь, — постарался я ответить как можно спокойнее.
— Сегодня мы будем подробно разбираться со строками.
Шаблонные строки
По своей сути, строка — это массив символов. У нее также, как и у массива
есть свойство 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 s = '1234';
s[2] = '0';
console.log(s) // 1234
Неизменяемость строк — очень важное их качество. Дальше мы рассмотрим
некоторые функции, которые "кажется" изменяют строку,
например replace или slice, но на самом деле, они просто создают новую
строку, а старая остается без изменений.
Функция Описание
Специальные символы
Экранирование
Вывести на экран некоторые символы не так просто, как кажется. Например,
представь, что тебе нужно напечатать строку 'Ваши данные находятся в
каталоге \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 и вывести результат на экран для проверки.
Представь, что у тебя есть массив каких-то данных. И тебе нужно его
отфильтровать по какому-то правилу. Правило ты еще не знаешь, но было бы
очень удобно, если бы ты не мог менять правила фильтрации по ходу дела.
Область видимости
В процессе написания программы ты будешь создавать множество функций и
переменных. Не все они будут друг-другу доступны. Чтобы хорошо понять
ошибки, которые будут возникать в процессе разработки, тебе нужно
запомнить несколько правил.
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'];
Замыкания
Вернемся к нашему примеру с созданием функций.
const longerThan = (n) => {
return (s) => {
return s.length > n;
}
}
При каждом вызове функции longerThan(n) будет создана новая функция,
которая "запомнит" параметр n, который использовался при ее создании.
Можно создать сколько угодно функций с разными значениями n и для каждой
из них n будет своим. Это — замыкание.
Замыкание - это функция со всеми доступными ей внешними переменными.
Может показаться, что это просто или очевидно, но если бы мы сохранили n,
как обычную переменную, то у нас бы ничего не получилось.
Значение n каждый раз бы менялось и это изменение ломало бы логику
программы.
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 внутрь функции, то были бы проблемы.
Превращается в
const sum = (x, y) => x + y;
После стрелки => ты вместо фигурных скобок можешь сразу написать
возвращаемое значение. Ключевое слово return в таком случае не
используется.
Это — неправильно:
const getObjectIncorrect = (name) => { name: 'Jack', email:
'welcome@coderslang.com' };
Тернарный оператор
Вот как будет выглядеть функция, которая печатает на экран значения всех
элементов массива.
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.
Квадратные скобки для доступа к полям объекта могут быть полезны тогда,
когда мы не знаем какие именно поля есть в объекте.
Но, время идет, а торта все нет. Родители пытаются дозвониться, чтобы
уточнить срок доставки, а ответа нет.
Тут раздается стук в дверь. Маша бежит открывать. Она видит чудесную фею
с розовыми крылышками. В руках у нее огромная коробка.
Что произошло
return userFriends;
}
Ты не хочешь, чтобы программа сломалась, поэтому проверяшь userId в самом
начале функции. Если его нет — возвращаешь пустой массив.
Возможно, для поиска нужно будет сделать запросы в базу данных, а может
нужно будет авторизоваться на каком-то сервисе и получить прогноз погоды
там.
Сервер
Сервер — это компьютер. Практически такой же, как и твой телефон, планшет
или ноутбук. Большинство отличий, видимых тебе, лежат в плоскости
операционной системы (ОС).
Телефоны работают на 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.
Проверить адрес своего устройства ты можешь прямо сейчас тут.
Запросы
Одна программа запущенная на сервере может занять один или более портов.
Занять порт — значит быть ответственным за все запросы, которые будут на
него отправлены. Как правило, нам достаточно одного порта.
Итак, у нас есть IP адрес, порт и тип запроса GET. Попробуем отправить
его на сервер и получить ответ.
Введи в браузер строку 51.15.200.89:8090 и посмотри что получится.
Доменные имена
server.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
Если ты выполнишь все по инструкции, сохранишь javascript код в
файл index.js и напишешь node index.js, то ты запустишь свой первый
сервер написанный с использованием фреймворка Express.js.
Пока он не будет доступен никому извне, но если ты попробуешь перейти в
браузере по адресу localhost:8080, ты увидишь, что сервер отвечает Hello,
Express.js!.
Middleware
А после клика на Click to Continue то же, что было доступно раньше только
на localhost:8080.
105-107. JSON
Один из самых популярных форматов обмена данными между бекэндом и
фронтэндом - JSON, или JavaScript Object Notation. Он очень похож на то,
как выглядят обычные JavaScript объекты, но также имеет свои особенности.
Читается - "джейсон", хотя часть твоих будущих коллег будут говорить и
"джсон", и "жсон", и даже "жисон".
Начнем с того, что JSON - это строка. Это позволяет при необходимости
очень эффективно сжимать данные. Недостаток — мы не можем хранить
циклические структуры данных, например объект, который ссылается на
самого себя.
Все точно как в JS, оборачиваем название массива в двойные кавычки, а сам
массив указываем в квадратных скобках.
{
"pets": ["Rex", "Sandy"]
}
Еще раз обращаем внимание, что в конце строки нет ни запятой, ни точки с
запятой.
Express.js и JSON
Так как мы знаем, что JSON объект — это строка, нам будет просто
модифицировать сервер и вместо Hello, Express.js отправлять какой-нибудь
объект.
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. Промисы
До сих пор ты работал только с обычными значениями. Создавал переменную
или константу, сохранял туда значение и мог сразу его использовать,
например вывести на экран.
Нельзя сказать, что один способ однозначно лучше, а другой хуже. В разных
ситуациях нам нужно разное поведение.
Это произойдет потому, что данных еще нет и нам нужно их ждать.
Состояния Promise
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}`);
}
userCount.then(handleSuccess).catch(handleReject);
Обработка ошибок
Разберемся пошагово:
1. В блоке try мы вызываем функцию fetchUserData, которая
возвращает Promise в состоянии pending
2. Блок catch игнорируется, потому что в блоке try ошибок не было.
Асинхронное выполнение пока никого не интересует!
3. На экран выводится строка finally
4. Возникает ошибка в асинхронном коде и мы видим в
консоли UnhandledPromiseRejectionWarning
Чтобы не возникало необработанных ошибок в промисах, к ним нужно всегда
добавлять обработчики в .catch().
fetchUserData(userId).then(console.log).catch(handleError);
Создание промисов
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);
};
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
Promise.race
Promise.race(arr).then(console.log); // fast
На экран будет выведено сообщение fast через 100 миллисекунд и мы не
будем ждать выполнения второго промиса.
110. Async/await
Промисы — очень удобный инструмент организации асинхронного кода. Но
мозгу человека намного привычнее синхронные операции. Сделали что-то,
подождали, потом продолжили.
Await
console.log(userCount); // 12345
На экране появится сразу значение с которым завершится промис, который
возвращает getUserCount(). А если не написать await, то мы увидим в
консоли строку Promise { <pending> }.
Async
console.log(getHelloWorld); // Promise { }
А чтобы получить результат промиса, нужно добавить все тот же await. Или
убрать async, если ты точно уверен, что функция getHelloWorld будет
выполнять только синхронные операции.
const getHelloWorld = async () => {
return 'Hello, world!';
}
Обработка ошибок
Минутка истории
HTML
HyperText Markup Language или HTML - это язык на котором говорит браузер.
В русском языке есть термин "язык гипертекстовой разметки", но им никто
не пользуется.
HTML - это текст, написанный по определенным правилам. Задача браузера —
понять этот текст, преобразовать и выдать тебе на экран красивую веб
страницу.
Например, вот так выглядит фрагмент html файла нашей базы знаний:
Текстовый редактор
Так как HTML - это текст, то писать его можно где угодно. Точно как и
программы на JavaScript!
Я рекомендую тебе использовать VSCode, как один из лучших бесплатных
инструментов для написания кода в 2022.
Практические задачи ты будешь получать прямо в VSCode и там же сможешь
отправить их на проверку. Если задание будет выполнено неверно, то ты
сразу увидишь изменения, которые необходимо сделать.
HTML теги
Одинарные теги
Кроме парных тегов, существуют одинарные. Они отличаются тем, что состоят
только из одной части.
Например, есть такой тег <hr>, который рисует горизонтальную линию-
разделитель. Давай посмотрим как будут выглядеть два параграфа с
заголовком второго уровня каждый, разделенные горизонтальной линией:
<h2>Это заголовок первого параграфа</h2>
<p>Первый параграф</p>
<hr>
<h2>Это заголовок второго параграфа</h2>
<p>А это второй параграф</p>
Блочные элементы
Использование тегов
Но, твои коллеги наверняка будут писать все маленькими буквами, поэтому
советую следовать такому стилю и тебе.
Строчные элементы
Все дело в том, что строчные теги можно использовать внутри блочных не
нарушая структуру. На деле это выглядит следующим образом:
<p>Это параграф, где одно слово будет выделено <b>жирным</b>
шрифтом</p>
<p>А в этом параграфе одно слово будет написано <i>курсивом</i></p>
В данном примере структура построения HTML страницы никак не будет
отличаться с использованием или без тегов <b> и <i>. При этом
теги <b> и <i> называются вложенными по отношению к тегу <p>, внутри
которого каждый используется, а теги <p> называются родительскими.
Вложенные теги
Комментарии в HTML
Или ты просто хочешь оставить себе напоминание, о том, что должно быть
сделано.
Атрибуты
HTML атрибуты указывают браузеру каким образом отобразить тот или иной
элемент. Например, с их помощью мы можем добавить выравнивание к
текстовым абзацам.
Все атрибуты состоят из двух частей: имени и значения. Как и в случае с
именем тега, имя атрибута не чувствительно к регистру, но все пишут их
строчными (маленькими) буквами.
Чтобы добавить атрибут, нужно начать с его имени, продолжить знаком = и
после него указать значение атрибута в кавычках.
Относительные ссылки
Любому HTML элементу можно добавить атрибут id. Значение этого атрибута
может быть любым, но обязательно должно быть уникальным в рамках одной
веб страницы.
Атрибут <id> нужен для того, чтобы элемент можно было однозначно
идентифицировать.
<p id="p1">Первый абзац</p>
<p id="p2">Второй абзац</p>
Внутренние ссылки
205. Списки
— Продвигаемся и изучим новые теги.
— Я думал мы уже закончили с тегами, ведь уже даже атрибуты рассмотрели.
Маркированный список
Нумерованный список
<!-- Результат:
X. Пункт 1
IX. Пункт 2
VIII. Пункт 3
-->
Список определений
206. Таблицы
— Теперь разберемся с еще одним способом структурировать и группировать
информацию - с таблицами. Раньше их активно использовали для верстки веб
страниц. Но такая структура была не очень гибкой. После того, как
появилась необходимость делать адаптивный дизайн для разных размеров
экранов, от них постепенно отказались.
— Так если от них отказались, зачем тогда мне их изучать?
Базовая структура
— Совершенно верно.
— Но таких же таблиц в реальной практике не будет. Как мне добавить
несколько ячеек и строк?
— Для добавления несколько строк тебе нужно повторить несколько раз
тег <tr> внутри тега <table>. А для добавления нескольких ячеек — повтори
тег <td> внутри строки таблицы. Но помни - в каждой строке должно быть
одинаковое количество ячеек, для сохранения структуры таблицы.
<table>
<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>
Объединение ячеек
Группы строк
Хотя все эти три тега использовать необязательно, лучше их все таки
добавлять.
Тип документа
Заголовок документа
Мета теги
Тело документа
<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 и в переводе с
английского означает каскадные таблицы стилей.
Строчные стили
Представим, что у нас есть параграф и нам необходимо сделать цвет текста
красным. Вот как это можно сделать:
<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>.
— Неплохо, но что если таких параграфов будет сто? А если больше? А если
кроме цвета надо будет изменить размер шрифта?
Было бы намного удобнее один раз указать стиль, который применился бы ко
всем тегам <p>, чем дублировать атрибут style в каждом из них.
Начнем с простого HTML документа без стилей.
<!DOCTYPE html>
<html>
<head>
<title>Внутренние стили</title>
</head>
<body>
<h1>Заголовок</h1>
<p>Первый параграф</p>
<p>Второй параграф</p>
</body>
</html>
Первая задача - установить размер шрифта внутри всех тегов <p> - 16px.
Чтобы избежать дублирования стилей, мы добавим парный тег <style>. Внутри
него — имя тега, и список свойств стилей которые мы хотим применить.
o начинаем с имени тега
— Иногда, нам нужно сделать разные стили для одного и того же тега.
Например, часть параграфов нужно красить в красный цвет, а другую часть -
в черный. Для этого селектор по имени тега нам не подойдет и нужно будет
использовать другой подход.
Идентификаторы
Классы
Например, для двух средних параграфов нам надо сделать шрифт 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>
После этого, в стилях можно будет изменить цвет всех HTML элементов этого
класса.
Цвет
В стандартном наборе HTML 140 цветов и если тебе нужен будет какой-то
цвет, который не представлен там, то нужно будет использовать
шестнадцатеричный формат (HEX код) или RGB.
HEX код всегда начинается с символа # за которым следует трехбайтовое
шестнадцатеричное число (всего 6 цифр). Каждый байт отвечает за
интенсивность одного из трех основных цветов: первый отвечает за
интенсивность красного цвета, второй — зеленого, третий — синего:
Выравнивание
Украшение (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>
Трансформация текста
Точно так же, как задается расстояние между символами, можно задать и
расстояние между словами. Отличие будет только в имени свойства - 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>
Выбор шрифта
Размер шрифта
На самом деле есть очень много вещей, который стоит знать про размер
шрифта в CSS, но их рассмотрим в следующих лекциях.
Стиль шрифта
Насыщенность шрифта
Подключение шрифтов
Расширение
Формат Браузеры
файла
embedded-
.eot IE6-IE8
opentype
215. Границы
Граница (англ. border) - это линия, которая рисуется по краю элемента.
Для ее добавления используется свойство border.
o На втором — стиль
o На третьем — цвет
Также, каждое из трех значений свойства border можно задать и с помощью
трех разных отдельных свойств: border-width, border-style и border-color.
Давай про каждое значение и поговорим.
Стиль линии
Поэтому, если ты хочешь чтобы граница была, тебе нужно явно указать
стиль. Возможные варианты:
o solid - сплошная линия
o double - двойная сплошная линия
o dotted - пунктирная линия в точку
o dashed - штрих-пунктирная линия
o groove - трехмерная рифленая граница (эффект зависит от выбранного цвета
границы)
o ridge - трехмерная гребенчатая граница (эффект зависит от выбранного
цвета границы)
o inset и outset - выгнутая внутрь или наружу 3d границы (эффект зависит от
выбранного цвета границы)
<style>
div {
border: 1px red;
}
</style>
Цвет границы
Цвет границы можно указывать всеми теми же способами, что и цвет заднего
фона, включая значение transparent.
Если для указания цвета ты выберешь свойство border-color, то тогда
можешь выбрать цвет отдельно для каждой из сторон элемента.
Цвет границ должен быть обязательно перечислен в правильно порядке. По
часовой стрелке, начиная с верхней: border-color: верхняя_граница правая
нижняя левая .
<style>
div {
border: 1px solid;
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-сторона.
Скругление углов
Поля
o право (right)
o низ (bottom)
o лево (left)
Отрицательные поля
Для первого параграфа ничего не поменялось, потому что перед ним нет
параграфа с отрицательным полем.
С тем же успехом мы можем каждому параграфу указать margin-top: -15px. В
таком случае все три параграфа поднимутся вверх на 15px.
— Очень полезная особенность, попробую поэкспериментировать с ней.
— Да, но будь осторожен. Если ты укажешь слишком большое значение для
отрицательного поля, тогда элементы будут накладываться друг на друга и
перекрывать контент.
Отступы
Уточним еще один момент. Представим, нам нужно задать одинаковые отступы
по вертикали (сверху и снизу) и по горизонтали (слева и справа). Сделать
это можно легко уже знакомой нам записью:
<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.
Значения по умолчанию
Чтобы изменить это поведение, нужно самому указать, каким будет размер
полей.
Для того, чтобы убрать поля, нужно просто указать margin: 0px. Также
можно самому "привязывать" поля и отступы к размеру шрифта, указав
значение в em.
Например, чтобы сделать все поля у элементов <p> в половину размера
шрифта нужно просто указать следующее:
<style>
p {
margin: 0.5em;
}
</style>
— А есть ли еще какие-то элементы со значениями по умолчанию, не равными
0?
— Конечно, например у списков <ul> и <ol> значение по умолчанию для
вертикальных полей равно 1em, а левый отступ - 40px.
Абсолютные размеры
Относительные размеры
Позиционирование
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 элемент можно подвинуть относительно
верхнего левого угла экрана.
Этот вариант отлично подходит для верхнего меню навигации сайта, если
нужно его постоянно отображать.
При этом стоит помнить, что элемент будет выбран только в том случае,
если он будет первым (или последним) в списке всех дочерних элементов.
Это значит, что будут учитываться элементы всех типов, а не только те,
которые указаны в селекторе.
Например, здесь текст первого параграфа не станет красным, потому что
первый дочерний элемент внутри <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 и так .
Каждый HTML элемент можно рассматривать как блок. Из этих блоков строятся
веб-страницы, так же как из кирпичей строятся дома.
Отображение элементов
Все 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 контейнеру для того, чтобы понимать вдоль какой линии
будут располагаться элементы, и как они будут размещены вдоль этой линии.
С помощью свойства justify-content изменяется расположение элементов
вдоль главной оси.
Изначально элементы пытаются находиться как можно левее к началу главной
оси, что соответствует значению flex-start.
Flex элементы
Свойство flex-grow
<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
<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
<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
.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>
Это свойство может быть очень полезным при разработке адаптивного сайта,
где понадобится изменить положение элемента в зависимости от размера
экрана. Но об адаптивной верстке будем говорить позже, сейчас время
решать практические задачи.
Grid-контейнер
Grid tracks
Grid области
Если вместо названия блока поставить точку, данная ячейка будет считаться
"пустой" и в ней ничего не будет отображаться:
.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-элемента
.header {
grid-area: header;
}
Выравнивание элемента
Простые селекторы
Кроме селекторов по типу 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 -->
Составные селекторы
Список селекторов
Комбинаторы
Родственные комбинаторы
<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 стилей. Это
можно сделать различными способами: внешние, внутренние стили, строчные
стили. Их можно указать с помощью классов или идентификаторов, с помощью
селектора по типу или составного селектора.
Подсчет веса
!important
Чтобы закрыть все вопросы семантики одним махом, тебе надо использовать
семантически верные теги, которые мы дальше и рассмотрим.
Структурные теги
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>
Теги контента
Виды верстки
Фиксированная верстка
Самый простой способ верстки. При таком подходе размеры элементов
фиксируются с помощью свойств width и height, размеры при этом
указываются в px. Но нам такая верстка не подходит - как только элемент
не будет помещаться по ширине в границы экрана, снизу появится
горизонтальная прокрутка, а это очень не удобно для пользователя.
Резиновая верстка
При таком подходе элементы сайта могут менять свои размеры в зависимости
от размера окна браузера. Добиться этого можно с помощью свойств max-
width и min-width, а также указывая размеры элементов в процентах.
Media запросы
Носители
Кроме значения all для определения носителей, которое применяет стили для
всех устройств, возможны следующие значения:
o screen – экран монитора;
o print – принтеры и другие печатающие устройства;
o projection – проекторы;
o handheld – смартфоны и аналогичные им аппараты;
o braille – устройства, основанные на системе Брайля, которые предназначены
для чтения слепыми людьми;
o embossed – принтеры, использующие для печати систему Брайля;
o speech – речевые синтезаторы, а также программы для воспроизведения
текста вслух;
o tty – устройства с фиксированным размером символов;
o tv – телевизоры.
Логические операторы
Кроме логического оператора and можно еще использовать not:
@media not print {
div {
width: 200px;
}
}
media-функции
Как мы уже говорили ранее, media-функции указывают технические
характеристики устройств, для которых нужно изменить стили. Большинство
из них используют приставки min- и max-. Ранее мы уже использовали max-
width и min-width, но также можно зафиксировать какой-то конкретные
размер с помощью значения width (хотя применения такому особо нету).
Относительные размеры
p {
font-size: 1.4rem;
}
@media screen and (max-width: 1400px) {
html: {
font-size: 19px;
}
}
Mobile first
Библиотеки и фреймворки
Вокруг было темно. Я едва ли мог открыть глаза, как в голове зазвучал
знакомый голос.
Библиотеки и фреймворки
function subtract(a, b) {
return a - b;
}
Фреймворк ограничивает тебя в том, как должен быть расположен твой код и
он сам решает, когда и в каком порядке, этот код будет вызван.
Библиотека React
return <h2>{title}</h2>
};
o Виртуальный DOM (Virtual Dom). Самые затратные операции в веб приложении
связаны с изменениями в DOM дереве - добавление какого-либо класса
элемента, или замена одних элементов другими. Для оптимизации этих
операций в React используется виртуальный DOM. Это объект, в котором
хранится информация и состоянии UI. Так React всегда знает какие элементы
как отображаются. Виртуальный DOM - это копия реального DOM. Все
изменения сначала происходят в этой копии (например удаление элемента
списка после нажатия кнопки), потом React рассчитывает разницу между
двумя объектами, обновляет реальный DOM. После обновления реального DOM
пользователь видит изменения.
и дождись ее завершения.
План такой:
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.
JSX
// compiles into
let createdElement = React.createElement(
"h1",
null,
"Hello, ",
React.createElement("b", null, "world!")
);
Теперь у заголовка h1 два дочерних элемента: текст "Hello, " и тег b со
своим внутренним текстом.
При этом тег b тоже был заменен на вызов функции React.createElement с
нужными аргументами.
— А я могу самостоятельно узнать во что трансформируются другие примеры
JSX кода?
— Можешь. И это будет очень полезно. Во что компилируются все JSX
сниппеты, ты можешь легко посмотреть в онлайн компиляторе Babel.
Отображение элемента
ReactDOM.render(
element,
document.getElementById('root')
);
Встраиваемые выражения
ReactDOM.render(
element,
document.getElementById('root')
);
Теперь во время отображения этого элемента на экране
вместо {name} появится Hero! - все между фигурными скобками
интерпретируется как JS-выражение.
— Практически как шаблонные строки в обычном JavaScript. Только там мы
добавляли знак доллара перед открывающей фигурной скобкой.
ReactDOM.render(
element,
document.getElementById('root')
);
Атрибуты в JSX
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.
ReactDOM.render(
someBlock,
document.getElementById('root')
);
А в HTML пришлось бы использовать два тега <div>. Открывающий и
закрывающий.
Сложные элементы
— Понятно.
function HelloWorld() {
return <div>Hello world!</div>
}
ReactDOM.render(
element,
document.getElementById('root')
);
Свойства элементов
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>
}
// 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')
);
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import User from './User.js';
ReactDOM.render(
<User {...data} />,
document.getElementById('root')
);
Здесь мы применили спред (spread) оператор (...) для того, чтобы передать
свойства firstName и lastName компоненту User.
function StringComponent() {
return 'some string';
};
Что бы ничего не отображать в браузере, можно вернуть из
компонента null, true или false. Булевые значения зачастую используются
для условного рендеринга.
function NothingComponent() {
return null;
}
function TimeCounter() {
let count = 0;
setInterval(() => {
count += 1;
}, 1000);
return (
<div>{count}</div>
);
}
this.state = {
count: 0
}
}
render() {
return <div>{this.state.count}</div>
}
}
Состояние мы объявили в конструкторе (this.state).
Если у компонента объявлен конструктор, мы обязаны передать
свойства props родительскому классу (super(props)).
Можно и не использовать constructor для объявления состояния, если ты не
работаешь в нем с props:
import React from react;
render() {
return <div>{this.state.count}</div>
}
}
Изменение состояние
handleCountIncrement = () => {
this.setState({ count: this.state.count + 1 });
}
render() {
return <div>{this.state.count}</div>
}
}
Метод handleCountIncrement мы объявили с помощью стрелочной функции. Это
позволяет использовать this внутри этого метода.
Для изменения полей состояния необходимо использовать метод setState.
Этот метод нам доступен благодаря тому, что наш компонент был создан из
класса React.Component. В таком случае после изменения состояние
компонент будет перерисован.
handleFieldTwoUpdate = () => {
this.setState({ fieldTwo: 5 })
}
В результате вызова метода handleFieldTwoUpdate состояние будет
следующим:
state = {
fieldOne: 1,
fieldTwo: 5,
fieldThree: 3
}
Теперь нам надо добавить логику вызова метода handleCountIncrement один
раз в секунду. И здесь нам помогут методы жизненного цикла.
Методы жизненного цикла
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;
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>
}
}
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;
componentDidMount() {
// выводим свойства в консоль
console.log(this.props);
this.intervalID = setInterval(
this.handleCountIncrement,
1000
);
}
// ...
}
componentDidMount() {
const { interval = 1000 } = this.props;
this.intervalID = setInterval(
this.handleCountIncrement,
Interval
);
}
// ...
}
Здесь мы деструктуризируем свойство interval и используем его в качестве
второго аргумента метода setInterval.
Обрати внимание, что мы обезопасили наш код, присвоив
переменной interval начальное значение в 1000 мс. Вдруг кто-то забудет
передать значение для этого важного свойства.
Этап 1: монтирование
this.state = {
name: 'John Doe',
}
};
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: обновление
Этот метод может быть очень полезен для избежания ненужных рендеров.
Давай рассмотрим пример:
// 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
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 не изменилось, компонент не будет обновлен и этап
будет завершен.
return null;
}
render() {
return (
<div>Здесь мы булем рендерить огромный список из
{this.props.list.length} элементов</div>
)
}
}
Если тебе пришлось использовать этот метод, не забывай возвращать из него
или null, или значение снэпшота, которое ты сможешь потом использовать.
Все, что возвращается из метода getSnapshotBeforeUpdate, попадает в
метод componentDidUpdate.
componentDidUpdate()
Метод componentDidUpdate вызывается сразу после обновления компонента. Он
получает ряд аргументов, которые могут помочь тебе в реализации какой-то
логики (например, запроса для получения каких-то дополнительных
данных): prevProps - свойства предыдущего рендера, prevState - состояние
предыдущего рендера, snapshot - значение возвращаемое
из getSnapshotBeforeUpdate().
async componentDidMount() {
const { page } = this.state;
const fetchedList = await fetch(`/getStarsList/${page}`);
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: размонтирование
componentWillUnmount() {
window.removeEventListener('scroll', console.log);
}
render() {
return <div>Какой-то очень высокий блок</div>
}
}
Открой консоль и обрати внимание когда мы убираем убираем слушатель с
события scroll с помощью метода removeEventListener
Прежде чем перейти к событиям в React, давай вспомним как мы это делали в
HTML.
Представим, что у нас есть кнопка с текстом Click me. И в консоль будет
выводится фраза Button clicked:
<button onclick="handleClick()">Click me</button>
<script>
function handleClick() {
console.log('Button clicked');
}
</script>
Должно быть все ясно, как белый день. Пишем функцию handleClick где
выводим текст в консоль. Потом устанавливаем обработчик клика для кнопки
с помощью onclick="handleClick()"
function Button() {
const handleClick = () => {
console.log('Button clicked');
}
return (
<button onClick={handleClick}>Click me</button>
)
}
render() {
return (
<button onClick={this.handleClick}>Click me</button>
);
}
}
Здесь обработчик нажатия вынесен в метод класса, поэтому для его
использования мы должны взять его из объекта this.
А теперь допустим нам надо выводить в консоль значение
свойства clickMessage. Перепишем наш компонент:
import React from 'react';
render() {
return (
<button onClick={this.handleClick}>Click me</button>
);
}
}
На первый взгляд все правильно. Но при нажатии на кнопку это код вызовет
ошибку, которая звучит следующим образом: Cannot read property 'props' of
undefined.
handleClick() {
const { clickMessage = "Button clicked" } = this.props;
console.log(clickMessage);
}
render() {
return (
<button onClick={this.handleClick}>Click me</button>
);
}
}
render() {
return (
<button onClick={this.handleClick}>Click me</button>
);
}
}
function Button(props) {
console.log('I was rerendered, but nothing changed');
return (
<button onClick={props.onClick}>{props.label}</button>
);
}
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 каждый
раз новое, ведь создается новая анонимная функция. А при изменение
свойств компонент перерисовывается.
Поля ввода
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 для каждого поля
ввода. Сами значение берутся из состояния с начальными значениями в виде
пустых строк.
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 и сохраняем в состояние компонента.
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: '',
};
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 мы просто вывели наше состояние в консоль,
однако именно здесь можно организовать отправку данных на сервер для
проверки.
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>
);
}
}
render() {
return (
<form>
<input
placeholder="Enter email"
onChange={this.handleEmailChange}
/>
<input
placeholder="Enter password"
type="password"
onChange={this.handlePasswordChange}
/>
<button>Login</button>
</form>
);
}
}
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';
render() {
const { feedback } = this.state;
return (
<textarea
value={feedback}
placeholder="Leave your feedback"
onChange={this.handleFeedbackChange}
/>
);
};
}
Как видно на примере выше - использование textarea ничем не отличается от
использование input.
Использование select , кстати, тоже мало чем отличается. Давай
рассмотрим.
select
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';
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
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.
Таким образом нам надо будет отображать либо страницу логина, либо
страницу профиля.
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>
);
}
}
render() {
const { userData: { firstName, lastName } } = this.state;
return (
<div>
<h2>Welcome</h2>
<div>First name: {firstName}</div>
<div>Last name: {lastName}</div>
</div>
);
}
}
handleLogin = () => {
this.setState({ userLoggedIn: true });
}
render() {
return <LoginPage handleLogin={this.handleLogin} />;
}
}
Изначально наше приложение должно отображать форму логина. Поэтому мы в
методе render() возвращаем компонент LoginPage.
Чтобы форма логина работала, в свойствах мы передадим
функцию handleLogin для формы логина.
В методе handleLogin мы изменяем значение поля
состояния userLoggedIn на true. Это поле и говорит нам, что пользователь
залогинился в приложении.
А когда пользователь залогинен, то мы должны показать ему
компонент ProfilePage. Давай внесем нужные нам изменения в
метод render():
// src/App.js
render() {
const { userLoggedIn } = this.state;
if (userLoggedIn) {
return <ProfilePage />;
}
Встроенные условия
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>
);
}
}
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 будет не определено.
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>
);
}
}
Переменные-элементы
render() {
const { userData: { firstName, lastName, avatarUrl } } = this.state;
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';
getAvatar = () => {
const { userData: { firstName, lastName, avatarUrl } } = this.state;
if (avatarUrl) {
return <Image url={avatarUrl} />;
}
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дотвращение рендера
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. Но не одно их этих значений
не имеет графического отображения.
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.
function CitiesList() {
return (
<ul>
<li>{cities[0]}</li>
<li>{cities[1]}</li>
<li>{cities[2]}</li>
<li>{cities[3]}</li>
</ul>
);
}
function CitiesList() {
const listItems = cities.map(city => <li>{city}</li>);
return (
<ul>
{listItems}
</ul>
);
}
Здесь с помощью метода map мы проходим по списку городов и формируем
новый массив listItems, который состоит из элементов li.
Каждый элемент содержит название города в качестве внутреннего текста.
Сам listItems мы уже используем в фигурных скобках как и любую другую
переменную для рендера.
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 из списка городов будет удален
последний.
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.
function CitiesList() {
return (
<ul>
{cities.map((city, index) => <li key={index}>{city}</li>)}
</ul>
);
}
Здесь мы использовали индексы, которые передаются как второй аргумент
колбэка map , в качестве ключей.
Но это плохой подход, поскольку порядок элементов в массиве может
поменяться, и тогда будут тратится лишние ресурсы на манипуляции с DOM.
— А как сделать все хорошо и правильно?
async componentDidMount() {
const coins = await cryptoService();
this.setState({ coins });
}
render() {
return null;
}
}
Как только компонент CryptoCoins будет монтирован, мы отправим запрос на
сервер для получения списка монет. Для этого мы используем написанный
ранее cryptoService.
Полученный результат сохраняем в состояние в поле 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>
);
}
}
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 из объекта, описывающего
монету.
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.
function CitiesList() {
return (
<ul>
{cities.map(city => <li>{city}</li>)}
</ul>
<ol>
{cities.map(city => <li>{city}</li>)}
</ol>
);
};
function CitiesList() {
return (
<>
<ul>
{cities.map(city => <li>{city}</li>)}
</ul>
<ol>
{cities.map(city => <li>{city}</li>)}
</ol>
</>
);
};
Здесь мы заменили компонент <Fragment></Fragment> на <></>. Эти две
записи эквивалентны, ты можешь выбрать ту которая тебе больше нравится.
— Похоже на то как в однострочной стрелочной функции можно писать return,
а можно и не писать.
Обрати внимание еще на один момент мы два раза повторяем один и тот же
код.
function CitiesList() {
return (
<Fragment>
<ul>
<Items listType="ul" />
</ul>
<ol>
<Items listType="ol" />
</ol>
</Fragment>
);
};
И если нам надо будет внести изменения во все карточки, мы сделаем это
просто в одном месте.
PropTypes
Чем больше будет твое приложения, тем у тебя больше будет компонентов с
самым различным набором свойств. И когда их станет слишком много, тебе и
твоим коллегам будет все тяжелее понимать какие свойства нужно передать
тому или иному компоненту, какого типа должны они быть.
В React для этого предусмотрен специальных механизм. Давай рассмотрим
просто пример - компонент UserProfile.
Сейчас нам совсем не важно, что рендерит этот компонент, нам нужны только
его свойства:
function UserProfile({
firstName,
lastName,
age,
...rest
}) {
return null;
}
Здесь мы деструктурировали некоторые свойство, остальные собрали в
объект rest c помощью оператора ...
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.
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
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 и в случае необходимости
выбрасывать предупреждения.
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 должны быть строками.
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';
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 должен быть элементом.
WithChildren.propTypes = {
children: PropTypes.element.isRequired
};
function App() {
return (
<WithChildren>
<div>
<div>Первый дочерний элемент</div>
<div>Второй дочерний элемент</div>
</div>
</WithChildren>
);
}
defaultProps
function UserProfile({
role = 'user',
}) {
return null;
}
UserProfile.propTypes = {
role: PropTypes.oneOf(['admin', 'user']).isRequired,
};
Здесь мы свойству role указываем значение user, если такое свойство не
передали.
UserProfile.propTypes = {
role: PropTypes.oneOf(['admin', 'user']).isRequired,
};
UserProfile.defaultProps = {
role: 'user',
};
Если тебе нужно указать значение свойства по умолчанию, нужно добавить
свойство defaultProps компонента.
Как и propTypes - это объект. Ключи объекта - это свойства (их названия).
Значения ключей - соответствующие значения которые будут использованы по
умолчанию, то есть если какие-то свойства не будут переданы.
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 мы берем из состояния, потому что этот стиль мы
можем изменять с помощью нажатий кнопки.
Внешние стили
div {
background-color: #e3e3e3;
}
.flex-wrapper {
display: flex;
justify-content: center;
}
import './FlexWrapper.css';
Учитывай, что при добавлении стилей таким образом, они будут применять ко
всем компонентам, а не конкретно к тому, в файл которого мы импортировали
стили.
CSS модули
.flex-wrapper {
background-color: #e3e3e3;
display: flex;
justify-content: center;
}
.flex-wrapper {
background-color: #e3e3e3;
display: flex;
justify-content: center;
}
.title {
font-size: 34px;
}
Чтобы добавить несколько CSS классов используя CSS модули придется
объединить их названия в одну строку, которую мы передадим в className
import styles from './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';
useState
function EmailInput() {
const handleInputChange = () => {};
return (
<input
placeholder="Enter email"
value={undefined}
onChange={handleInputChange}
/>
);
}
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;
render() {
return null;
}
}
setEmailValue - это метод, который переданный аргумент сохраняет в
качестве нового значения состояния emailState. emailValue - это и есть
значение состояния. В классовом компоненте ему будет
эквивалентно this.state.emailValue.
Давай закончим наш функциональный компонент с использованием
хука useState:
import React , { useState } from 'react';
function EmailInput() {
const [emailValue, setEmailValue] = useState('');
return (
<input
placeholder="Enter email"
value={emailValue}
onChange={handleInputChange}
/>
);
}
Здесь мы не создавали отдельную переменную emailState , а сразу
деструктурировали массив, который вернул useState и создали
переменные emailValue и setEmailValue.
Переменную emailValue мы сразу передаем в поле ввода в качестве значения.
Функцию setEmailValue мы используем внутри метода handleInputChange —
сначала мы получаем из объекта события введенное значение, потом с
помощью setEmailValue сохраняем его в emailValue.
function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
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 может быть любого типа: строка,
число, объект, массив и т.д
return (
<div>
<div>Full name: {userData.firstName} {userData.lastName}</div>
<button onClick={changeName}>Change Name</button>
</div>
);
}
useEffect
function EmailInput() {
const [emailValue, setEmailValue] = useState('');
return (
<input
placeholder="Enter email"
value={emailValue}
onChange={handleInputChange}
/>
);
}
Сразу после рендера и при каждом изменении значения поля мы будем видеть
в консоли строку Current email: ... .
Это происходит, потому что после каждого рендера, включая первый
срабатывает наш useEffect.
А срабатывает он потому, что мы не указали никаких условий для его
выполнения - второй аргумент просто отсутствует. Это
означает, useEffect здесь выполняет одновременно и
роль componentDidMount , и роль componentDidUpdate.
Попробуем использовать хук useEffect только в режиме componentDidMount.
Для этого нужно передать вторым аргументом в функцию useEffect массив.
function EmailInput() {
const [emailValue, setEmailValue] = useState('');
useEffect(
() => console.log(`Current email: ${emailValue}`,
[]
);
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]
);
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);
}
};
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);
}
};
componentWillUnmount
componentWillUnmount() {
window.removeEventListenet(console.log);
}
render() {
return null;
}
}
function WithScrollListent() {
useEffect(() => {
window.addEventListenet(console.log);
return () => window.removeEventListenet(console.log);
}, []);
render() {
return null;
}
}
Запросы на сервер
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([]);
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 и на этот раз
ничего не возвращаем.
Первое приложение
После вызова команды, клиент expo даст тебе выбор, какой проект тебе
нужен.
Смело выбирай пункт blank и нажимай Enter. Клиент создаст обычный пустой
проект ReactNative со всеми необходимыми для запуска и разработки
файлами. Дальше нам надо запустить проект. Для этого перейди в папку
проект и вызови команду для запуска:
cd my-first-rn-project # переходим в папку с проектом
npm start # запускаем проект
Теперь посмотрим, как выглядит наше приложение. Самый просто способ это
установить мобильное приложение Expo для Android или iOS. После этого
достаточно отсканировать QR код из страницы в браузере и приложение
запуститься на смартфоне:
Вот и все действия, которые нужно проделать, чтобы сделать свой первое
приложение. Дальше мы разберем элементы для построения экранов
приложения, а пока переходи к задачам и попробуй сам создать и отправить
на проверку свое первое приложение.
Компонент View
Текстовый элемент
Свойство style
StyleSheet
Ключами этого объекта будет имя стиля. Об этом имени можно думать как о
классе CSS, потому что его можно использовать много раз для разных
элементов.
Значения этого объекта — стили, написанные точно так же, как и для
свойства style.
Объединения стилей
Styled components
Особенные свойства
Но все 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.
Это связано с тем, что есть много разных размеров экрана и разных
разрешений на одном и том же размере экрана. Для того, чтобы один и тот
же элемент смотрелся на экране с высоким разрешением и высокой плотностью
пикселей примерно так же, как и на экране с низким разрешением и
используется эта размерность. Поэтому при использовании фиксированных
значения, размерности указывать не нужно:
<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 пикселей. Все
оставшееся пространство займет средний дочерний элемент.
Размеры экрана
Изображения
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>
);
};
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, но это основные, с
которыми тебе предстоит работать больше всего.
Кнопка
Немного изменим наш компонент для ввода имени. Добавим кнопку, при
нажатии на которую поле ввода будет заменяться просто введенным именем:
import React, { useState } from 'react';
import { View, Text, TextInput, Button } from 'react-native';
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
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';
);
}
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';
);
}
ActivityIndicator
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. По
этому, чтобы везде лоадер выглядел одного цвета, рекомендую его
указывать.
ScrollView
Тут нам помощь придет еще один компонент из стандартной библиотеки react-
native - ScrollView. По сути, этот компонент тоже создает
отображение View, но у него появляются границы прокрутки. Обычно этими
границами будут высота и ширина самого экрана.
import React from 'react';
import { StyleSheet, Text, View, ScrollView, Dimensions } from
'react-native';
FlatList
const DATA_SET = [
{
id: 1,
color: '#56c7c0',
},
{
id: 2,
color: '#38277f',
},
{
id: 3,
color: '#ffb900',
},
];
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
StatusBar
Подключение навигации
// Screens
import ListScreen from './screens/List';
import DetailsScreen from './screens/Details';
// создаем стек
const Stack = createStackNavigator();
// Screens
import ListScreen from './screens/List';
import DetailsScreen from './screens/Details';
// Screens
import ListScreen from './screens/List';
import DetailsScreen from './screens/Details';
Теперь, когда мы первым загружаем экран List, нам надо сделать переход на
экран Details. Для начала посмотрим на код экрана List:
import React from 'react';
import { View, Text, Button } from 'react-native';
Передача параметров
Теперь нам надо вернуться на экран List. Сделать это можно уже известным
нам способом navigation.navigate('List'). В нашем случае мы действительно
вернемся на один экран назад. А что если между
экраном List и Details было бы еще несколько экранов. Тогда эим способом
мы просто перескочим все эти экраны и попадем на экран List.
Чтобы вернуться именно на один экран назад, как это происходит в
браузере, используй метод .goBack() свойства navigation:
import React from 'react';
import { View, Text, Button } from 'react-native';
Кнопки хедера
// Screens
import ListScreen from './screens/List';
import DetailsScreen from './screens/Details';
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>
);
}
Кастомный заголовок
// Screens
import ListScreen from './screens/List';
import DetailsScreen from './screens/Details';
Навигатор вкладок
// Screens
import HomeScreen from './screens/Home'
import SettingsScreen from './screens/Settings'
import ProfileScreen from './screens/Profile'
/* ... */
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
let iconName = 'Home';
Вложенные навигаторы
// Screens
import HomeScreen from './screens/Home'
import SettingsScreen from './screens/Settings'
import ProfileScreen from './screens/Profile'
function Settings() {
return (
<Stack.Navigator>
<Stack.Screen name="Settings" component={SettingsScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
);
}
// Screens
import HomeScreen from './screens/Home'
import SettingsScreen from './screens/Settings'
import ProfileScreen from './screens/Profile'
function SettingsTabs() {
return (
<Tab.Navigator>
<Tab.Screen name="Settings" component={SettingsScreen} />
<Tab.Screen name="Profile" component={ProfileScreen} />
</Tab.Navigator>
);
}
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';
return (
<View>
<Modal visible={isVisible}>
<View style={styles.modalView}>
<Text>Это модальное окно</Text>
<Button
title="Скрыть модалку"
onPress={() => setIsVisible(false)}
/>
</View>
</Modal>
<Button
title="Показать модалку"
onPress={() => setIsVisible(true)}
/>
</View>
);
}
// Screens
import ListScreen from './screens/List';
import DetailsScreen from './screens/Details';
function Screens() {
return (
<Stack.Navigator>
<Stack.Screen name="List" component={ListScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
)
}
Alert API
return (
<View>
<Button
title="Logout"
onPress={confirmLogout}
/>
</View>
)
}
При нажатии на любую кнопку диалоговое окно будет закрыто. Однако при
нажатии на кнопку Да будет вызван метод handleLogout с логикой выхода
пользователя из приложения.
Drawer навигатор
// Screens
import HomeScreen from './screens/Home'
import SettingsScreen from './screens/Settings'
import ProfileScreen from './screens/Profile'
Сделай сам
Помнишь, в одной из первых лекций, мы рассказывали тебе об алгоритме
двоичного поиска?
Это - один из классических алгоритмов. Найти его реализацию
на JavaScript можно в интернете за 5 секунд. Еще за 10 секунд проверить
работоспособность и отправить задачу на проверку. Но это - путь в никуда.
Примеры и использование
Будет проще, если ты будешь записывать двоичные числа одно под другим,
вот так:
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
Сдвиг числа влево на 1 бит - это то же самое, что умножение этого числа
на 2.
console.log(x << 1); // 10
console.log(y << 2); // 24
Сдвиг вправо
Сортировка
Сортировка - это процесс упорядочивания последовательности объектов.
Объектами могут быть любые сущности которые можно сравнить.
Сортировка пузырьком
Оптимизация пузырька
Даже если на вход мы отправим уже отсортированный массив, нам нужно будет
столько же итераций цикла, как и для неотсортированного массива, чтобы
получить резульат.
Сортировка выбором
Сортировка
2. Время выполнения.
return -1;
}