React course
  • Компоненты и коллекции
  • TypeScript
  • Стилизация
  • События и состояния
  • Формы
  • Жизненный цикл
  • Функциональные vs классовые компоненты
  • Основы Redux
  • Redux Toolkit
  • Асинхронный Redux
  • Селекторы
  • React Router
  • Code splitting
  • Паттерны и контекст
  • Анимация
Powered by GitBook
On this page
  • 1. Веб-приложения
  • 1.1. Multiple-page Application
  • 1.2. Single-page Application
  • 2. React
  • 3. Browser DOM и Virtual DOM
  • 4. Инструменты
  • 4.1. Create React App
  • 4.2. React DevTools
  • 5.React-элементы
  • 5.1. Рендер элемента в DOM-дерево
  • 6. JavaScript Syntax Extension (JSX)
  • 6.1. Правило общего родителя
  • 6.2 Дополнительные материалы
  • 7. Компоненты
  • 7.1. Компоненты-функции
  • 8. Свойства компонента (props)
  • 8.1. Свойство props.children
  • 8.2. Свойство defaultProps
  • 8.3. Свойство propTypes
  • 9. Рендер по условию
  • 9.1. if с помощью логического оператора &&
  • 9.2. if...else с помощью тернарного оператора
  • 9.3. Дополинтельные материалы
  • 10. Коллекции
  • 10.1. Ключи
  • 10.2. Дополнительные материалы

Was this helpful?

Компоненты и коллекции

1. Веб-приложения

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

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

1.1. Multiple-page Application

Несколько лет назад мы бы использовали подход включающий несколько отдельных HTML-страниц.

  • Архитектура клиент-сервер

  • Вся логика живет на сервере

  • На каждый запрос сервер отсылает готовый HTML-документ

  • Перезагрузка страницы при каждом запросе

  • Плохая интерактивность

  • Отличное SEO

1.2. Single-page Application

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

  • Архитектура клиент-сервер

  • При загрузке сайта сервер всегда отдает стартовую HTML-страницу index.html

  • Каждый последующий запрос на сервер получает только данные в JSON-формате

  • Обновление интерфейса происходит динамически на клиенте

  • Загрузка первой страницы может быть довольно медленной (лечится)

  • Логика не связанная с безопасностью живет на клиенте

  • Слабое SEO (лечится)

  • Сложность кода и его поддержки масштабируется с кол-вом функционала приложения

2. React

Библиотека для создания элементов интефрейса. В React нет встроенной маршрутизации, HTTP-модуля и т. п. Тем не менее есть богатая экосистема, которая позволит решить любую задачу.

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

3. Browser DOM и Virtual DOM

Browser DOM - древовидное представление HTML-документа, где каждый элемент документа представлен в виде DOM-узла. Хранится в браузере и напрямую связан с тем что мы видим на странице.

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

Virtual DOM - абстракция, легковесная копия реального DOM-дерева в виде JSON-документа.

  • Существует только в памяти и не рендерится в браузере

  • Не зависит от внутренней имплементации браузера

  • Использует лучшие практики обновления реального DOM

  • Собирает обновления в группы для оптимизации рендера (batching)

4. Инструменты

Для создания React-приложения необходимы Node.js, Webpack, Babel, React и DevTools. Можно написать свою Webpack-сборку или взять любую хорошую с GitHub.

4.1. Create React App

Для обучения и маленьких/средних проектов рекомендуется использовать утилиту от авторов React.

  • Абстрагирует всю конфигурацию, позволяя сосредоточиться на написании кода

  • Включает необходимые инструменты: Webpack, Babel, ESLint и т. п.

  • Расширяется дополнительными пакетами из экосистемы React

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

npx create-react-app имя_папки_проекта

Для того чтобы создать приложение в текущей папке, вместо имени проекта ставится точка. Например npx create-react-app .. Это можно использовать когда был склонирован репозиторий и в его локальной версии инициализируется приложение.

npx — инструмент, предназначенный для того, чтобы помочь стандартизировать использование npm-пакетов. Поставляется с npm версии 5.2.0 и выше. npm упрощает установку и управление зависимостями, размещенными в реестре, a npx упрощает использование CLI-утилит и других исполняемых файлов без необходимости их установки в систему или проект.

4.2. React DevTools

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

5.React-элементы

React-элементы - это самые маленькие строительные блоки React, элементы Virtual DOM. Элементы это обычные JS-объекты, поэтому создавать их очень быстро.

Функция React.createElement() это самый главный метод предоставляемый React API. Подобно document.createElement() для DOM, React.createElement() это функция для создания React-элементов. Возвращает объект, элемент Virtual DOM.

React.createElement(type, [props], [...children])
  • type - имя встроенного React-элемента который в Virtual DOM соответсвует будущему HTML-тегу.

  • props - объект содержащий HTML-атрибуты и кастомные свойства. Может быть null или пустой объект, если передавать ничего не нужно.

  • children - произвольное количество аргументов после второго это дети создаваемого элемента. Так создается дерево элементов.

import React from 'react';

const link = React.createElement(
  'a',
  {
    href: 'https://reactjs.org/',
    target: '_blank',
    rel: 'noreferrer noopener',
  },
  'Ссылка на reactjs.org',
);

Создадим элемент с детьми, карточку продукта.

import React from 'react';

const image = React.createElement('img', {
  src:
    'https://images.pexels.com/photos/461198/pexels-photo-461198.jpeg?dpr=2&h=480&w=640',
  alt: 'Tacos With Lime',
  width: 640,
});
const title = React.createElement('h2', null, 'Tacos With Lime');
const price = React.createElement('p', null, 'Price: 10.99$');
const button = React.createElement('button', { type: 'button' }, 'Add to cart');

const product = React.createElement('div', null, image, title, price, button);

/*
 * Для передачи детей также используется свойство children параметра props.
 * Обратите внимание на то, что свойство children это массив.
 */
const productWithChildrenInProps = React.createElement('div', {
  children: [image, title, price, button],
});

5.1. Рендер элемента в DOM-дерево

Для того чтобы отрендерить элемент, в пакете react-dom есть метод ReactDOM.render().

  • Первым аргументом принимает ссылку на React-элемент или компонент (что рендерить)

  • Вторым, ссылку на уже существующий DOM-элемент (куда рендерить)

import ReactDOM from 'react-dom';

ReactDOM.render(product, document.getElementById('root'));

React использует модель отношений предок - потомок, поэтому достаточно использовать только один вызов ReactDOM.render() в приложении. Рендер самого верхнего элемента в иерархии повлечет за собой рендер всего поддерева.

6. JavaScript Syntax Extension (JSX)

Код в предыдущем разделе понятен браузеру. Но так описывать разметку интерфейса неудобно, нам привычен HTML. Для этого был создан JSX.

  • Позволяет использовать XML-образный синтаксис прямо в JavaScript

  • Упрощает код, делает его декларативным и читабельным

  • Описывает объекты - элементы Virtual DOM

  • Это не HTML, Babel трансформирует JSX в вызовы React.createElement()

  • В JSX можно использовать весь потенциал JavaScript

JSX не обязателен, но давайте сравним следующий код.

// Plain JavaScript
const link = React.createElement(
  'a',
  {
    href: 'https://reactjs.org/',
    target: '_blank',
    rel: 'noreferrer noopener',
  },
  'Ссылка на reactjs.org',
);

// JSX
const linkWithJSX = (
  <a href="https://reactjs.org/" target="_blank">
    Ссылка на reactjs.org
  </a>
);

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

import React from 'react';
import ReactDOM from 'react-dom';

const imageUrl =
  'https://images.pexels.com/photos/461198/pexels-photo-461198.jpeg?dpr=2&h=480&w=640';
const price = 10.99;

const product = (
  <div>
    <img src={imageUrl} alt="Tacos With Lime" width="640" />
    <h2>Tacos With Lime</h2>
    <p>Price: {price}$</p>
    <button type="button">Add to cart</button>
  </div>
);

ReactDOM.render(product, document.getElementById('root'));
  • JSX преобразовывается в вызовы React.createElement(), поэтому пакет React должен быть в области видимости модуля.

  • Можно использовать практически любое валидное JavaScript-выражение, оборачивая его в фигурные скобки.

  • Значения атрибутов указываются через двойные кавычки, если это обычная строка, и через фигурные скобки, если значение вычисляется, либо тип отличается от строки.

  • Все атрибуты React-элементов именуются в camelCase нотации.

  • JSX-теги могут быть родителями других JSX-тегов. Если тег пустой или самозакрывающийся, его обязательно необходимо закрыть используя />.

6.1. Правило общего родителя

Разберем следующий код с не валидной JSX-разметкой.

const post = (
  <h2>Post Header</h2>
  <p>Post text</p>
);

Перепишем код используя React.createElement().

const post = (
  React.createElement('h2', null, 'Post Header')
  React.createElement('p', null, 'Post text')
);

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

const post = React.createElement(
  'div',
  null,
  React.createElement('h2', null, 'Post Header'),
  React.createElement('p', null, 'Post text'),
);

В JSX это выглядит так.

const post = (
  <div>
    <h2>Post Header</h2>
    <p>Post text</p>
  </div>
);

Если в разметке лишний тег-обертка не нужен, используются Фрагменты, похожие на DocumentFragment. Этот встроенный компонент при рендере растворяется, подставляя свое содержимое.

import React, { Fragment } from 'react';

const post = (
  <Fragment>
    <h2>Post Header</h2>
    <p>Post text</p>
  </Fragment>
);

Синтаксис фрагментов можно сократить и не добавлять импорт Fragment. Babel сделает все необходимые трансформации, заменив пустые JSX-теги на React.Fragment.

import React from 'react';

const post = (
  <>
    <h2>Post Header</h2>
    <p>Post text</p>
  </>
);

6.2 Дополнительные материалы

7. Компоненты

Компоненты - основные строительные блоки React-приложений, при помощи которых интерфейс делится разделить на независимые части.

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

React-приложение можно представить как дерево компонентов. На верхнем уровне стоит корневой компонент, в котором вложено произвольное количество других компонентов. Каждый компонент должен вернуть JSX-разметку, тем самым указывая какой HTML мы хотим отрендерить в DOM.

7.1. Компоненты-функции

В простейшей форме компонент это JavaScript-функция с очень простым контрактом: функция получает объект свойств который называется props и возвращает дерево React-элементов.

Имя компонента обязательно должно начинаться с заглавной буквы. Названия компонентов с маленькой буквы зарезервированы для HTML-элементов. Если вы попробуете назвать компонент card, а не Card, при рендере, React проигнорирует его и отрендерит тег <card></card>.

const MyFunctionalComponent = props => <div>Functional Component</div>;

Компоненты-функции составляют большую часть React-приложения.

  • Меньше boilerplate-кода

  • Легче воспринимать

  • Легче тестировать

  • Нет контекста (this)

Сделаем карточку продукта компонентом-функцией.

const Product = props => (
  <div>
    <img
      src="https://images.pexels.com/photos/461198/pexels-photo-461198.jpeg?dpr=2&h=480&w=640"
      alt="Tacos With Lime"
      width="640"
    />
    <h2>Tacos With Lime</h2>
    <p>Price: 10.99$</p>
    <button type="button">Add to cart</button>
  </div>
);

// В разметке компонент записывается как JSX-тег
ReactDOM.render(<Product />, document.getElementById('root'));

// Это аналогично
ReactDOM.render(React.createElement(Product), document.getElementById('root'));

8. Свойства компонента (props)

Свойства (пропсы) это одна из основных концепций React. Компоненты принимают произвольные свойства и возвращают React-элементы, описывающие что должно отрендерится в DOM.

  • Пропсы используются для передачи данных от родителя к ребенку.

  • Пропсы передаются только вниз по дереву от родительского компонента.

  • При изменении пропсов React ре-рендерит компонент и, возможно, обновляет DOM.

  • Пропсы доступны только для чтения, изменить их в ребенке нельзя.

Пропсом может быть текст кнопки, картинка, url, любые данные для компонента. Пропсы могут быть строками или результатом JS-выражения. Если передано только имя пропса - это буль, по умолчанию true.

const App = () => (
  <>
    <h1>Best selling products</h1>
    <Product name="Tacos With Lime" />
  </>
);

Компонент <Product> объявляет параметр props, это всегда будет объект содержащий все переданные пропсы.

const Product = props => (
  <div>
    <h2>{props.name}</h2>
  </div>
);

Добавим компоненту <Products> несколько других свойств.

const Product = props => (
  <div>
    <img src={props.imgUrl} alt={props.name} width="640" />
    <h2>{props.name}</h2>
    <p>Price: {props.price}$</p>
    <button type="button">Add to cart</button>
  </div>
);

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

const Product = ({ imgUrl, name, price }) => (
  <div>
    <img src={imgUrl} alt={name} width="640" />
    <h2>{name}</h2>
    <p>Price: {price}$</p>
    <button type="button">Add to cart</button>
  </div>
);

const App = () => (
  <div>
    <h1>Best selling products</h1>
    <Product
      imgUrl="https://images.pexels.com/photos/461198/pexels-photo-461198.jpeg?dpr=2&h=480&w=640"
      name="Tacos With Lime"
      price={10.99}
    />
    <Product
      imgUrl="https://images.pexels.com/photos/70497/pexels-photo-70497.jpeg?dpr=2&h=480&w=640"
      name="Fries and Burger"
      price={14.29}
    />
  </div>
);

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

8.1. Свойство props.children

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

  • Свойство children автоматически доступно в каждом компоненте, его содержимым является то, что стоит между открывающим и закрывающим JSX-тегом.

  • В функциональных компонентах обращаемся как props.children.

  • Значением props.children может быть практически что угодно.

К примеру у нас есть компонент профиля <Profile> и вспомогательный ui компонент <Panel>, в который мы можем помещать произвольный контент.

const Profile = ({ name, email }) => (
  <div>
    <p>Name: {name}</p>
    <p>Email: {email}</p>
  </div>
);

const Panel = ({ title, children }) => (
  <section>
    <h2>{title}</h2>
    {children}
  </section>
);

const App = () => (
  <div>
    <Panel title="User profile">
      <Profile name="Mango" email="mango@mail.com" />
    </Panel>
  </div>
);

В противном случае нам бы пришлось пробросить пропы для <Profile> сквозь <Panel>, что более тесно связывает компоненты и усложняет повторное использование.

8.2. Свойство defaultProps

Что если компонент ожидает какое-то значение, а его не передали? - при обращении к свойству объекта props, получим undefined.

Для того чтобы указать значения свойств по умолчанию, у компонентов есть статическое свойство defaultProps, в котором можно указать объект с дефолтными значениями пропов (не обязательно всех). Этот объект будет слит с пришедшим объектом props.

const Product = ({ imgUrl, name, price }) => (
  <div>
    <img src={imgUrl} alt={name} width="640" />
    <h2>{name}</h2>
    <p>Price: {price}$</p>
    <button type="button">Add to cart</button>
  </div>
);

Product.defaultProps = {
  imgUrl:
    'https://dummyimage.com/640x480/2a2a2a/ffffff&text=Product+image+placeholder',
};

/*
 * Определение defaultProps гарантирует, что `props.imgUrl` будет иметь значение,
 * даже если оно не было указано при вызове компонента в родителе.
 */
ReactDOM.render(
  <Product name="Tacos With Lime" price={10.99} />,
  document.getElementById('root'),
);

8.3. Свойство propTypes

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

npm install --save-dev prop-types

Используем prop-types и опишем пропсы компонента Product.

import PropTypes from 'prop-types';

const Product = ({ imgUrl, name, price }) => (
  <div>
    <img src={imgUrl} alt={name} width="640" />
    <h2>{name}</h2>
    <p>Price: {price}$</p>
    <button type="button">Add to cart</button>
  </div>
);

Product.defaultProps = {
  imgUrl:
    'https://dummyimage.com/640x480/2a2a2a/ffffff&text=Product+image+placeholder',
};

Product.propTypes = {
  imgUrl: PropTypes.string,
  name: PropTypes.string.isRequired,
  price: PropTypes.number.isRequired,
};

Сначала применяются значения по умолчанию, заданные в defaultProps. После запускается проверка типов с помощью propTypes. Так что проверка типов распространяется и на значения по умолчанию.

9. Рендер по условию

Для рендера разметки по условию используются операторы ветвлений и условий. Условия можно проверять перед возвратом разметки, или прямо в JSX.

Если по условию ничего не должно быть отрендерено, можно вернуть null, undefined или false, они не рендерятся.

9.1. if с помощью логического оператора &&

Читается как: если условие приводится к true, то рендерим разметку.

const Mailbox = ({ unreadMessages }) => (
  <div>
    <h1>Hello!</h1>
    {unreadMessages.length > 0 && (
      <p>You have {unreadMessages.length} unread messages.</p>
    )}
  </div>
);

9.2. if...else с помощью тернарного оператора

Читается как: если условие приводится к true, рендерим разметку после ?, в противном случае рендерим разметку после :.

const Mailbox = ({ name, unreadMessages }) => (
  <div>
    <h1>Hello {name}.</h1>
    {unreadMessages.length > 0 ? (
      <p>You have {unreadMessages.length} unread messages.</p>
    ) : (
      <p>No unread messages.</p>
    )}
  </div>
);

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

const Mailbox = ({ name, unreadMessages }) => (
  <div>
    <h1>Hello {name}.</h1>
    <p>
      {unreadMessages.length > 0
        ? `You have ${unreadMessages.length} unread messages.`
        : 'No unread messages.'}
    </p>
  </div>
);

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

const Product = ({ imgUrl, name, price, quantity }) => (
  <div>
    <img src={imgUrl} alt={name} width="640" />
    <h2>{name}</h2>
    <p>Price: {price}$</p>
    <h1>Quantity: {quantity < 20 ? 'Few left' : 'In stock'}</h1>
    <button type="button">Add to cart</button>
  </div>
);

9.3. Дополинтельные материалы

10. Коллекции

Для того чтобы отрендерить коллекцию однотипных элементов, используется метод Array.prototype.map(), callback-функция которого, для каждого элемента коллекции, возвращает JSX-разметку. Таким образом получаем массив React-элементов который можно рендерить.

const favouriteBooks = [
  { id: 'id-1', name: 'JS for beginners' },
  { id: 'id-2', name: 'React basics' },
  { id: 'id-3', name: 'React Router overview' },
  { id: 'id-4', name: 'Redux in depth' },
];

const BookList = ({ books }) => (
  <ul>
    {books.map(book => (
      <li>{book.name}</li>
    ))}
  </ul>
);

ReactDOM.render(
  <BookList books={favouriteBooks} />,
  document.getElementById('root'),
);

10.1. Ключи

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

Ключ (key) — это специальный строковый проп, который нужно задать при создании элементов коллекции.

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

Ключи должны быть:

  • Уникальные - ключ элемента должен быть уникальным только среди его соседей. Нет смысла в глобально уникальных ключах.

  • Стабильные - ключ элемента не должен меняться со временем, изменением порядка элементов или после обновления страницы.

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

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

const favouriteBooks = [
  { id: 'id-1', name: 'JS for beginners' },
  { id: 'id-2', name: 'React basics' },
  { id: 'id-3', name: 'React Router overview' },
  { id: 'id-4', name: 'Redux in depth' },
];

const BookList = ({ books }) => (
  <ul>
    {books.map(book => (
      <li key={book.id}>{book.name}</li>
    ))}
  </ul>
);

ReactDOM.render(
  <BookList books={favouriteBooks} />,
  document.getElementById('root'),
);

10.2. Дополнительные материалы

NextTypeScript

Last updated 3 years ago

Was this helpful?

React мультиплатформенный, разметку можно рендерить на сервере (), писать нативные () или десктопные () приложения.

Пакет предоставляет ряд валидаторов для проверки корректности полученных типов данных во время исполнения кода, уведомляя о несоответствиях в консоли. Все что необходимо сделать это описать типы пропсов получаемых компонентом в специальном статическом свойстве propTypes. Проверка пропов с помощью prop-types происходит только во время разработки, в продакшене в ней нет необходимости.

Single-page application vs. multiple-page application
Next.js
React Native
Electron
Документация Create React App
Devtools в Chrome Web Store
Introducing the New React DevTools
Знакомство с JSX
Различия в атрибутах
Рендеринг элементов
Компоненты и пропсы
JSX в деталях
Spread Attributes
prop-types
Проверка типов с помощью PropTypes
Условный рендеринг
All the Conditional Renderings in React
Списки и ключи