Лабораторная работа №F3 - Стилизация веб-приложения (React)
Цель работы
- Освоение принципов стилизации элементов при помощи конструкций CSS.
- Освоение использования компонентов готовых дизайн-систем в React.
Ход работы
- Подключить к проекту дизайн-систему.
- Для компонентов, которые в работе F2 разрабатывались вручную, найти аналоги.
- Начать использовать компоненты из дизайн-системы.
- Добавить собственные стили при помощи CSS-модулей, чтобы вёрстка сайта была похожа на макет из работы F1.
- При необходимости добавить изображения и фон.
Для любителей хардкора пункты 1-3 можно пропустить, и всё делать самому.
Подключение дизайн-системы
В nodejs есть стандартный менеджер пакетов NPM. По аналогии с NuGet в DotNet и Maven в Java.
Обычно подключение дизайн-системы, как и любых других библиотек, происходит через указанный выше NPM командой npm install
.
Её необходимо запускать в той директории, где располагается файл package.json
.
После успешной установки библиотеки (или "зависимости" для нашего приложения) изменятся файлы package.json
и package-lock.json
.
В этих файлах описывается базовая информация о проекте и закрепление конкретных зависимостей сответственно.
Что за закрепление зависимостей? Всё дело в том, что в
package.json
обычно указаны диапазоны зависимостей (например,"zustand": "^4.5.5"
, то есть "любая версия от 4.5.5 строго до 5.0.0"). Вpackage-lock.json
указывается конкретная версия, которая должна удовлетворять требованиям и быть использована при работе нашего приложения.
Пример команды подключения consta design:
npm install @consta/uikit @consta/icons @consta/header
Это команда установит компоненты из Consta UI и Consta Header, а также иконки из Consta Icons.
Собственная стилизация через CSS-модули
В классическом документе HTML с подключёнными CSS все стили для всех элементов описываются глобально, а элементы, для которых и нужно определить какие-то стили, выбираются через "селекторы".
Пример селектора:
.text-danger
- все элементы, у которых определён аттрибутclass="<...> text-danger <...>"
, то есть хотя бы один класс - этоtext-danger
;.text.text-danger
- все элементы, у которых определены сразу два класса: иtext
, иtext-danger
;p,h1
- этот селектор выбирает все абзацы (<p>это абзац</p>
) и все заголовки первого уровня (если что, их 6 уровней);a:hover
- для ссылок (<a href="https://ulstu.ru/">Политех!</a>
), на которые пользователь навёл мышь.
Затем для каждого селектора объявляется набор правил стилизации, например:
.text {
font-size: 16px; /* Размер шрифта */
}
.text.text-danger {
color: #ff0000; /* Красный цвет текста */
background-color: #ff9999; /* Розовый - цвет фона */
}
h1 {
border: 1px solid black; /* Стиль границ элемента */
border-radius: 10px; /* Радиус скругления границ */
}
Этот подход существует уже как несколько десятков лет, но есть нюанс. Если сайт (веб-приложение, система) очень сложная и некоторые стили ещё и подключаются снаружи (например, та же дизайн-система), то можно легко схлопотать конфликт стилей.
Представим, что для примера выше где-то другой разработчик определил следующее:
.text {
font-size: 14pt;
}
И что получится?
Размер шрифта должен быть 16px
или 14pt
?
Чтобы частично решить эту проблему, было предложено множество подходов.
Один из них: БЭМ (Блок, Элемент, Модификатор). Он описывает правила, как должны называться классы, чтобы не было конфликта стилей, как выше.
Другой подход - использование модулей CSS.
При этом подходе для каждого компонента создаётся отдельный файл %component_name%.module.css
, где можно указывать стили для конкретных элементов компонента.
Импортируется такой набор стилей как import styles from './%component_name%.module.css'
, далее можно обращаться к переменной styles
. Это - объект, в котором каждое поле - это название класса из CSS-модуля.
Предположим CSS-модуль для карточки (Card.module.css
) описан таким образом:
.header { /* Стили заголовка */ }
.text { /* Стили текста. Да, тут нет комментариев // */ }
.footer { /* Стили подвала */ }
Тогда если импортировать этот модуль в Card.tsx
как import styles from './Card.module.css'
, то в объекте styles
будут следующие строки:
styles.header
- дляclassName
, связанного с заголовком;styles.text
- дляclassName
, связанного с основным текстом;styles.footer
- дляclassName
, связанного с подвалом.
Пример описания компонента Card.tsx
может быть таким:
import { ReactNode, PropsWithChildren } from 'react';
import styles from './Card.module.css';
interface Props {
header: string;
footer: ReactNode;
}
export const Card = (props: PropsWithChildren<Props>) => (
<div>
<h3 className={styles.header}>{props.header}</h3>
<div className={styles.body}>{props.children}<div>
<footer className={styles.footer}>{props.footer}</footer>
</div>
);
Вся мощь CSS-модулей - это уникальные названия селекторов.
Предположим, есть 2 CSS-модуля, и в обоих определён селектор .header
.
Тогда при компиляции нашего приложения для одного из компонентов итоговое название класса, которое "увидит" браузер, может быть _header_mdccx_73
, а для другого - _header_mdccx_78
.
Добавление изображений
Как добавлять статические файлы (к которым относятся документы, картинки, и т.д.) для vite описаны в этой документации.
Если кратко, есть несколько путей:
- Скопировать файлы с изображениями в папку
public
и использовать в качестве ссылки на фон или изображение просто имя файла, например,<img src="img.png" alt="" />
. - Скопировать файлы с изображениями рядом с компонентом и импортировать их как
import imgBg from './img.png'
. Тогда в переменнуюimgBg
будет помещён сгенерированный url, который надо использовать по назначению, например,<img src={imgBg} alt="" />
. - Скопировать файлы с изображениями рядом с CSS-модулем и использовать значение свойства по типу
url('./img.png')
. Например,.text{ background: url('./bg.png'); }
.