Срез 6
Изучение теории. Неделя 6
1. Data structures
1.1. Основные понятия структур данных
Структура данных — это специфический способ организации, хранения и управления данными в компьютере, который позволяет эффективно получать к ним доступ и модифицировать их. Эффективно подобранная структура данных позволяет оптимизировать выполнение операций над данными, таких как поиск, вставка или удаление.
Основная цель их использования — повышение производительности алгоритмов за счет эффективного управления данными.
1.2. Типы структур данных и их использование
Структуры данных делятся на два основных типа:
- Линейные: массивы, списки, стеки и очереди, где элементы располагаются последовательно.
- Нелинейные: деревья, графы, хэш-таблицы, где элементы располагаются иерархически или произвольно.
1.3. Сравнение массивов и связных списков
Основное различие между массивами и связными списками заключается в организации памяти и эффективности операций.
- Массивы:
- элементы лежат в памяти подряд;
- доступ по индексу за O(1);
- вставка/удаление в начало или середину требует сдвига всех последующих элементов, что имеет сложность O(n);
- фиксированный размер.
- Связный список:
- элементы разбросаны, каждый ссылается на следующий;
- доступ по индексу за O(n);
- вставка/удаление за O(1) при условии наличия указателя на узел;
- динамический размер.
1.4. Хранение уникальных значений в структурах данных
Структуры данных для хранения уникальных значений:
- Множество (Set)
- Хеш-таблица
Их внутренняя реализация основана на механизме хеширования и сравнения, который автоматически проверяет уникальность каждого добавляемого элемента. Это исключает возможность дубликатов. Все три операции в среднем работают за O(1), но возможно падение скорости до O(n) при возникновении коллизий.
1.5. Преимущества хеш-таблиц
- Основные операции (добавление, удаление, поиск) выполняются в среднем за O(1)
- Проверка на уникальность каждого добавляемого элемента
1.6. Основы алгоритмов
Алгоритм — это четкая последовательность действий, выполнение которых приводит к решению конкретной задачи или достижению поставленной цели.
Свойства:
- Дискретность
- Детерминированность
- Конечность
- Массовость
1.7. Описание и функции базовых структур данных
Массив:
- Описание: пронумерованный набор элементов одного типа, расположенных в памяти подряд
- Функция: быстрый доступ к данным по индексу
Связный список:
- Описание: узлы, разбросанные по памяти, где каждый элемент хранит ссылку на следующий
- Функция: динамическое управление данными
Стек:
- Описание: работает по принципу LIFO (Last In, First Out)
- Функция: управление историей вызовов, отмена операций (Undo)
Очередь:
- Описание: работает по принципу FIFO (First In, First Out)
- Функция: обработка задач в порядке поступления
1.8. Операции со стеком
У стека две операции:
- push — добавление элемента на вершину стека
- pop — удаляет и возвращает элемент, находящийся на вершине
Эти операции выполняются за O(1).
1.9. Концепция и реализация очередей
Очередь — это линейная структура данных, работающая по принципу FIFO (First-In, First-Out). Элементы добавляются в конец очереди и извлекаются из её начала.
Очередь может быть реализована на базе массива или связного списка.
1.10. Отличия деревьев поиска от обычных деревьев
Главное отличие заключается в упорядоченности данных. Обычное дерево — это просто иерархическая структура, в то время как дерево поиска (BST — Binary Search Tree) накладывает строгие правила на размещение элементов.
В BST для каждого узла все значения в левом поддереве меньше, а в правом — больше значения самого узла. Это правило обеспечивает возможность быстрого поиска, вставки и удаления элементов со средней сложностью O(log n), но возможен худший случай, когда дерево поиска вырождается в связный список, тогда сложность равна O(n).
В обычном дереве никакой упорядоченности нет, поэтому сложность трех операций в нем O(n).
2. 3 redux main principles
2.1. Основы Redux
Redux — это библиотека для управления глобальным состоянием приложения (обычно на React).
2.2. Принципы работы Redux
Redux работает на трех фундаментальных принципах:
- Единый источник истины. Всё состояние приложения хранится внутри единственного Store
- Состояние только для чтения. Изменить состояние можно только через отправку action
- Изменения вносятся чистыми функциями. Редьюсеры принимают текущее состояние и action и возвращают новое состояние, не мутируя исходный объект
2.3. Компоненты Redux (Store, Action, Reducer)
- Store — хранит всё состояние приложения
- Action — объект, который описывает, что именно произошло (обязательно имеет поле type)
- Reducer — чистая функция, которая определяет, как изменится состояние. Она принимает текущее состояние и action, и возвращает новое состояние
2.4. Redux vs Context API
- Context API идеален для простых сценариев с редкими обновлениями и небольшим количеством данных
- Redux, с его строгим однонаправленным потоком данных, middlewares (например, для асинхронных действий) и мощными инструментами разработчика, лучше подходит для сложных приложений с большим количеством часто изменяющихся данных, где важны предсказуемость и возможности отладки
2.5. Использование Redux вне React
Хотя Redux чаще всего ассоциируется с React, он является независимой библиотекой на чистом JavaScript, которую можно использовать с другими фреймворками (Angular, Vue) или даже с ванильным JavaScript. Для интеграции с другими фреймворками существуют соответствующие библиотеки-обертки, которые связывают хранилище с системой реактивности конкретного фреймворка.
2.6. Интеграция Redux с React
Для интеграции Redux с React используется официальная библиотека react-redux. Она предоставляет два основных инструмента:
- Компонент высшего порядка
<Provider>, который оборачивает приложение и делает store доступным всем вложенным компонентам через контекст React - Хуки
useSelectorиuseDispatch. useSelector позволяет компоненту подписываться на нужные данные из глобального состояния, а useDispatch возвращает функцию dispatch, которая используется для отправки action (что изменяет глобальное состояние)
2.7. Middleware в Redux
Middleware в Redux — это механизм, который позволяет перехватывать, обрабатывать и модифицировать actions до того, как они достигнут редьюсера. Middleware находится между отправкой action и моментом его попадания в reducer. Middleware чаще всего используется для выполнения асинхронных операций (например, API-запросов) с помощью библиотеки Redux Thunk, а также для логирования или обработки ошибок.
2.8. Асинхронные действия в Redux
По умолчанию actions в Redux являются синхронными. Для обработки асинхронных операций необходимо использовать middleware. Наиболее популярным решением является Redux Thunk. Thunk позволяет action creator’у возвращать не просто объект action, а функцию (thunk), которая может выполнять асинхронные операции и по их завершении диспатчить обычные actions.
2.9. Использование Redux DevTools
Redux DevTools — это мощное расширение для браузера, которое предоставляет инструменты для отладки состояния приложения. Оно позволяет отслеживать историю dispatched actions, состояние до и после каждого action, “перематывать” время, и даже диспатчить actions вручную. Это расширение значительно упрощает процесс отладки и понимания того, как и почему изменяется состояние в приложении.
2.10. Оптимизация производительности в Redux
Ключевая оптимизация в react-redux приложениях — предотвращение ненужных повторных рендеров компонентов. Поскольку react-redux по умолчанию делает ререндер компонента при любом изменении состояния в store, важно, чтобы компоненты подписывались только на те части состояния, которые они фактически используют. Это достигается с помощью селекторов с мемоизацией (например, из библиотеки Reselect), которые пересчитываются только при изменении релевантных данных.
3. HOC connect
3.1. Определение и назначение HOC (Higher-Order Component) в React
HOC (Higher-Order Component) в React — это функция, которая принимает компонент и возвращает новый компонент, добавляя ему дополнительную логику.
Назначение HOC:
- переиспользование логики между компонентами
- добавление функциональности (например, авторизация, загрузка данных, обработка ошибок)
- разделение логики и UI
3.2. Основные функции и цели использования функции connect в Redux
Функция connect из библиотеки react-redux используется для связывания React-компонента с хранилищем Redux. Она позволяет получать данные из store и отправлять actions в store.
3.3. Аргументы функции connect в Redux
Функция connect принимает два основных аргумента:
mapStateToProps— функция, которая определяет, какие части состояния store будут переданы компоненту как propsmapDispatchToProps— функция, которая определяет, какие функции для диспетчеризации actions будут переданы компоненту как props
3.4. Влияние функции connect на архитектуру React/Redux приложения
connect связывает UI-компоненты с Redux-хранилищем и помогает разделить бизнес-логику и интерфейс.
3.5. Сравнение HOC connect и хуков Redux (useSelector, useDispatch)
connect:
- Хорошо подходит для классовых компонентов, но можно использовать и с функциональными
- Автоматически мемоизирует props, чтобы минимизировать лишние ререндеры
Хуки useSelector и useDispatch:
- Только для функциональных компонент
- Код короче и проще
- Легко комбинировать несколько селекторов и actions
- Нет встроенной оптимизации. Компонент будет ререндериться при любом изменении выбранного значения, если селектор не мемоизирован
4. Actions
4.1. Основы Redux
Redux — это библиотека для управления глобальным состоянием приложения (обычно на React).
4.2. Понятие и использование Action в Redux
Action в Redux — это простой JavaScript-объект, который описывает, что произошло в приложении. Каждый action должен иметь поле type, которое является строковой константой и описывает произошедшее событие (например, ‘INCREMENT’). Action может также содержать дополнительные данные для обновления состояния (payload).
Actions можно создавать вручную или через action creators. Actions отправляются в store с помощью функции dispatch.
4.3. Основные принципы работы с Redux
Redux работает на трех фундаментальных принципах:
- Единый источник истины. Всё состояние приложения хранится внутри единственного Store
- Состояние только для чтения. Изменить состояние можно только через отправку action
- Изменения вносятся чистыми функциями. Редьюсеры принимают текущее состояние и action и возвращают новое состояние, не мутируя исходный объект
4.4. Создание и структура Action в Redux
Action создается как простой литерал объекта. Его структура должна всегда включать свойство type. Остальные свойства могут быть любыми, но общепринятым паттерном является использование свойства payload для передачи данных, связанных с action.
1
2
3
4
{
type: 'ADD_TODO',
payload: { id: 1, text: 'Learn Redux' }
}
Для стандартизации создания actions часто используются action creators.
1
const increment = () => ({ type: 'INCREMENT' });
4.5. Передача данных в Action
Стандартной практикой является передача всех данных в свойство с именем payload.
4.6. Типы данных в Action
- Свойство type всегда строка
- Свойство payload должно содержать сериализуемые данные. Нельзя передавать функции, промисы, классовые экземпляры или другие несериализуемые значения. Рекомендуется передавать простые данные: строки, числа, булевы значения, объекты и массивы, состоящие из таких же простых данных. Это требование важно для корректной работы DevTools, логирования и предсказуемости состояния
4.7. Обработка Action в Reducer
Reducer — это чистая функция, которая обрабатывает action. Она принимает текущее состояние и action в качестве аргументов и возвращает новое состояние на основе типа action и его payload (если он передан).
Reducer должен всегда возвращать новое состояние, а не изменять переданное ему текущее состояние. Для удобства обработки различных типов actions внутри редьюсера используется оператор switch.
4.8. Правила создания Action в Redux
- Actions должны быть чистыми объектами
- Свойство type всегда строка
- Свойство payload должно содержать сериализуемые данные
- Для удобства переиспользования лучше создавать actions с помощью action creators (функции, возвращающие action)
В контексте Redux чистый объект — это обычный JavaScript-объект, который не содержит методов, прототипов, функций или нестандартных данных, а хранит только сериализуемые значения.
4.9. Реализация асинхронных Action
По умолчанию actions в Redux являются синхронными. Для обработки асинхронных операций необходимо использовать middleware. Наиболее популярным решением является Redux Thunk. Thunk позволяет action creator’у возвращать не просто объект action, а функцию (thunk), которая может выполнять асинхронные операции и по их завершении диспатчить обычные actions.
4.10. Использование middleware для обработки Action
Middleware в Redux — это механизм, который позволяет перехватывать, обрабатывать и модифицировать actions до того, как они достигнут редьюсера. Middleware находится между отправкой action и моментом его попадания в reducer. Middleware чаще всего используется для выполнения асинхронных операций (например, API-запросов) с помощью библиотеки Redux Thunk, а также для логирования или обработки ошибок.
5. Reducers
5.1. Определение и основные правила редьюсеров в Redux
Reducer — это чистая функция, которая обрабатывает action. Она принимает текущее состояние и action в качестве аргументов и возвращает новое состояние на основе типа action и его payload (если он передан).
Reducer должен всегда возвращать новое состояние, а не изменять переданное ему текущее состояние.
Чистая функция — это функция, которая удовлетворяет двум ключевым условиям:
- Детерминированность — при одинаковых входных данных она всегда возвращает один и тот же результат
- Отсутствие побочных эффектов
5.2. Принципы работы редьюсеров в Redux
Редьюсер — это чистая функция, которая принимает текущее состояние и action, возвращает новое состояние, не мутируя текущее, и всегда возвращает состояние даже если action не распознан.
Для управления большим состоянием его разбивают на несколько мелких редьюсеров, которые затем комбинируются с помощью combineReducers.
5.3. Примеры редьюсеров с использованием switch/case конструкции
Использование switch по action.type — это стандартный и самый распространенный паттерн написания редьюсеров.
1
2
3
4
5
6
7
8
9
10
11
12
function todosReducer(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return [...state, { text: action.payload, completed: false }];
case 'TOGGLE_TODO':
return state.map((todo, index) =>
index === action.payload ? { ...todo, completed: !todo.completed } : todo
);
default:
return state;
}
}
5.4. Использование Redux-toolkit для работы с редьюсерами
Redux Toolkit (RTK) — это официальная библиотека для упрощения работы с Redux. Она предоставляет функцию createSlice, которая автоматически генерирует action creators и action types на основе переданных ей reducer функций. Внутри createSlice используется библиотека Immer, которая позволяет писать код, который выглядит как “мутация” состояния, но на самом деле создаёт новую неизменяемую копию состояния.
5.5. Преимущества createReducer и createSlice в Redux-toolkit
Преимущества createReducer и createSlice в Redux Toolkit:
- Они позволяют писать редьюсеры с меньшим количеством boilerplate-кода
- Использование Immer внутри этих функций устраняет необходимость использовать spread-операторы для обновления состояния, что снижает вероятность ошибок
createSliceавтоматически генерирует action creators и action types, избавляя от необходимости вручную определять типы actions- В результате код становится более читаемым, поддерживаемым и менее подверженным ошибкам.
5.6. Примеры использования createReducer в Redux-toolkit
createReducer принимает начальное состояние и объект редьюсер-функций, где ключами являются action types.
1
2
3
4
5
const counterReducer = createReducer(0, {
INCREMENT: (state) => state + 1,
DECREMENT: (state) => state - 1,
ADD: (state, action) => state + action.payload,
});
6. Middlewares
6.1. Определение и основная функция middleware в Redux
Middleware в Redux — это предлагаемый механизм для расширения функциональности dispatch. Это функция, которая образует цепочку вокруг метода store.dispatch. Основная функция middleware — перехватывать actions до того, как они достигнут редьюсера, и делать что-то с ними: логировать, модифицировать, откладывать, отправлять куда-либо или даже полностью отменять их обработку. Это мощный инструмент для side effects и асинхронности.
6.2. Влияние middleware на обработку действий в Redux
Middleware кардинально меняет поток данных в Redux. Без middleware каждый dispatched action немедленно и синхронно попадает в редьюсер. Middleware становится промежуточным слоем, который может задерживать, фильтровать или преобразовывать actions. Это позволяет управлять асинхронными операциями, где action не сразу обрабатывается, а только после завершения асинхронной task (например, HTTP-запроса).
6.3. Общие применения middleware в Redux
Наиболее базовое применения middleware: асинхронные API вызовы (Thunk, Saga), логирование actions и state для отладки, составление отчетов об ошибках (crash reporting), routing (например, перенаправление на другой route upon определенном action), синхронизация состояния с localStorage или сервером, и управление отменой/повтором действий (undo/redo).
6.4. Разработка собственного middleware для Redux
Собственный middleware создается как функция, возвращающая функцию, возвращающую функцию (трехкратная вложенность).
1
2
3
4
5
6
7
8
const myMiddleware = store => next => action => {
// Логика до next: можно логировать, модифицировать action
console.log('Dispatching:', action);
let result = next(action); // Передает action следующему middleware или редьюсеру
// Логика после next: можно что-то сделать с обновленным state
console.log('New state:', store.getState());
return result;
};
6.5. Использование нескольких middleware вместе в Redux
Несколько middleware могут быть объединены в цепочку с помощью функции applyMiddleware из Redux. Порядок, в котором middleware передаются в applyMiddleware, определяет порядок их выполнения. Middleware, переданное первым, будет самым внешним в цепочке и получит контроль над action самым первым. Последнее middleware в цепочке будет action и ultimately вызовет исходный dispatch, который передаст action редьюсерам.
7. Selectors
7.1. Основы селекторов в Redux
Селекторы — это функции, которые принимают состояние Redux (state) и извлекают из него данные или вычисляют полученные данные. Их основная цель — инкапсулировать знания о структуре состояния. Это означает, что если структура состояния изменилась, нужно обновить только селекторы, а не все компоненты, которые используют эти данные. Селекторы являются мостом между состоянием store и UI компонентами.
7.2. Простые селекторы и их использование
Простой селектор — это обычная функция, которая знает, как получить данные из состояния.
1
2
const getTodos = (state) => state.todos;
const getIncompleteTodos = (state) => state.todos.filter(todo => !todo.completed);
В компоненте, подключенном с помощью connect, селекторы передаются в mapStateToProps: mapStateToProps = (state) => ({ todos: getIncompleteTodos(state) }). С хуками используется useSelector: const todos = useSelector(getIncompleteTodos).
7.3. Понимание производительности и оптимизации через селекторы
Каждый раз, когда состояние в store изменяется, все функции mapStateToProps (и селекторы внутри них) всех подключенных компонентов пересчитываются. Если селектор выполняет complex вычисления (like фильтрацию или сортировку большого массива) и возвращает новый объект/массив, это будет вызывать re-render компонента, даже если фактические данные не изменились. Для оптимизации используются мемоизированные селекторы (например, с помощью Reselect), которые пересчитывают результат только при изменении input-аргументов.
7.4. Использование селекторов без дополнительных библиотек
Без дополнительных библиотек селекторы остаются простыми функциями. Для базовой оптимизации можно использовать memoization с помощью useMemo внутри useSelector или careful написание mapStateToProps, чтобы она возвращала стабильные ссылки на objects. Однако для сложных сценариев это становится cumbersome, и именно здесь библиотеки like Reselect shine, предоставляя мощные инструменты для создания мемоизированных селекторов с minimal boilerplate.
7.5. Примеры простых селекторов в Redux
Эти селекторы инкапсулируют knowledge о том, где и как хранятся данные в состоянии.
1
2
3
4
5
6
// Селектор для извлечения всего списка пользователей
const getUsers = state => state.users.byId;
// Селектор для извлечения пользователя по ID
const getUserById = (state, userId) => state.users.byId[userId];
// Селектор для извлечения текущего пользователя
const getCurrentUser = state => getUserById(state, state.auth.currentUserId);
7.6. Определение и роль селекторов в Redux
Селекторы играют critical роль в архитектуре Redux приложения. Они отделяют knowledge о structure состояния от компонентов. Это делает компоненты более “глупыми” и переиспользуемыми, because они не depend от точной формы состояния. Селекторы также позволяют easily вычислять производные данные (derived state), например, отфильтрованные списки, статистику и т.д., without хранения этих данных в состоянии.
7.7. Переиспользование логики состояния и селекторы
Селекторы способствуют переиспользованию логики. Если несколько компонентов need access к одним и тем же данным из состояния или need выполнить одинаковые вычисления, они can использовать один и тот же селектор. Это гарантирует consistency и предотвращает дублирование кода. Если logic извлечения данных changes, требуется обновить только селектор, а not множество компонентов.
7.8. Преимущества использования библиотеки Reselect
Reselect предоставляет функцию createSelector для создания мемоизированных селекторов. Главное advantage — это мемоизация. Селектор, созданный с помощью createSelector, пересчитает свое значение only тогда, когда изменится хотя бы один из его input-селекторов. Если inputs остались прежними, селектор возвращает previously вычисленное значение (по ссылке). Это предотвращает дорогостоящие повторные вычисления и ненужные re-render’ы компонентов.
7.9. Важность доступа к данным через селекторы
Доступ к данным состояния только через селекторы является best practice. Это создает level абстракции между raw состоянием и компонентами. Если structure состояния changes (например, переименование свойства, изменение nesting), developer должен обновить only соответствующие селекторы, и все components, которые их используют, продолжат работать without изменений. Это значительно упрощает рефакторинг и поддержку кода.
7.10. Оптимизация селекторов для улучшения производительности
Оптимизация селекторов достигается через мемоизацию (Reselect), чтобы избежать expensive пересчетов. Также важно design селекторы так, чтобы они принимали минимальное количество arguments (ideally only state) и были независимы от props компонента. Если селектор depends от props, его нужно carefully мемоизировать для каждого instance компонента, чтобы avoid memory leaks и ensure корректность.
8. Redux alternatives
8.1. Основы Redux и его использование в React
Redux — это библиотека для управления состоянием (state) JavaScript-приложений. Её основная концепция заключается в том, что всё состояние приложения хранится в едином централизованном хранилище — Store. Состояние является иммутабельным и может изменяться только предсказуемым способом: путем отправки действий (Actions) и обработки их редьюсерами (Reducers). Этот подход облегчает отладку, тестирование и предсказуемость поведения приложения.
8.2. Альтернативы Redux для управления состоянием
Популярные альтернативы Redux включают MobX, который использует наблюдаемое состояние и автоматический вывод зависимостей, предлагая более императивный и object-oriented подход. Zustand предоставляет минималистичный API для создания store с хуками. Context API React подходит для передачи данных через дерево компонентов без глобального store. Выбор зависит от предпочтений команды и сложности приложения.
8.3. Сравнение Redux с другими методами управления состоянием
Redux выделяется своим strict unidirectional data flow и emphasis на иммутабельность и pure functions, что обеспечивает предсказуемость и easy отладку. Альтернативы like MobX часто предлагают less boilerplate и более простую кривую обучения, но могут быть less предсказуемыми в very large приложениях. Context API интегрирован с React, но not optimized для frequent изменений состояния и может lead к ненужным re-render’ам.
8.4. Особенности и сравнение MobX с Redux
MobX использует observable state: вы помечаете данные как наблюдаемые, а компоненты автоматически trackят их изменения и re-render’ятся. Это создает more “магический” и less явный flow данных compared к Redux’s explicit dispatch -> action -> reducer. MobX часто требует less кода (less boilerplate), но может быть harder отлаживать, because изменения состояния happen automatically в разных places. Redux provides более clear и traceable flow.
8.5. Применение Redux Saga в React-приложениях
Redux Saga — это middleware библиотека для управления side effects (как асинхронные actions) в Redux приложениях. Она использует генераторы ES6, что позволяет писать асинхронный код, который выглядит как синхронный. Саги особенно хороши для сложных асинхронных сценариев, которые включают отмену задач, debouncing, или управление множественными actions в определенном order. Они легко тестируются, потому что эффекты declarative.
9. Nginx
9.1. Как настроить Nginx для отдачи React-приложения?
- Соберите React-приложение: npm run build => получится папка build
- В конфиге Nginx укажите root на папку build и index на index.html
1
2
3
4
5
6
7
server {
listen 80;
server_name example.com;
root /var/www/react-app/build;
index index.html;
}
9.2. Что делает параметр try_files?
- Он проверяет, существует ли запрошенный файл
- Синтаксис:
try_files $uri /index.html; - Если файл есть — отдаёт его, если нет — отдаёт запасной файл (например,
index.html) - Часто используется для SPA, чтобы все маршруты работали корректно
9.3. Как настроить редирект с HTTP на HTTPS?
1
2
3
4
5
server {
listen 80;
server_name my-app.com;
return 301 https://$host$request_uri;
}
9.4. Как настроить проксирование API-запросов через Nginx?
1
2
3
4
5
location /api/ {
proxy_pass http://backend:5000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
9.5. Как реализовать SPA-routing в Nginx?
1
2
3
4
5
# Обработка маршрутизации React (SPA)
location / {
# Пытаемся найти файл, если нет — отдаем index.html
try_files $uri /index.html;
}
9.6. Как ограничить доступ по IP?
1
2
3
4
5
# Разрешает доступ только с указанного IP, остальные получают 403
location /admin/ {
allow 192.168.1.100;
deny all;
}
10. Docker
10.1. Зачем фронтенд-разработчику Docker?
Docker позволяет запускать приложение в изолированной среде, где уже установлены все необходимые зависимости и инструменты.
Основные причины использования Docker во фронтенде:
- Единая среда разработки — у всех разработчиков одинаковые версии Node.js, npm / yarn, системных зависимостей
- Простое развертывание — одной командой
docker runбез необходимости устанавливать зависимости и настраивать окружение - Удобство CI/CD — Docker часто используется в GitHub Actions, GitLab CI, Jenkins
- Контейнеризация инфраструктуры — фронтенд может запускаться вместе с другими сервисами, например, через
docker-compose
10.2. Как написать Dockerfile для React-приложения?
Обычно используется multi-stage build.
Сначала приложение собирается, затем результат кладется в Nginx.
1
2
3
4
5
6
7
8
9
10
11
12
13
# Stage 1 — сборка приложения
FROM node:18 AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN npm run build
# Stage 2 — запуск через nginx
FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
10.3. В чем разница между образом и контейнером?
Образ — это шаблон для создания контейнера. Он содержит файловую систему, зависимости, код приложения, настройки запуска.
Образ не запускается сам по себе.
1
2
3
node:18
nginx:alpine
my-react-app
Контейнер — это запущенный экземпляр образа.
1
docker run nginx
При выполнении этой команды Docker:
- берет образ nginx
- создает контейнер
- запускает его
10.4. Как использовать multi-stage build в Docker?
Multi-stage build — это сборка образа в несколько этапов.
Это позволяет:
- использовать разные базовые образы
- уменьшить размер финального образа
- не включать лишние инструменты
1
2
3
4
5
FROM image AS builder
# сборка приложения
FROM image2
# финальный контейнер
10.5. Как уменьшить размер образа?
Есть несколько распространенных методов.
Использовать lightweight образы
1 2
node:18-alpine nginx:alpine
Alpine Linux — минимальный дистрибутив.
Использовать multi-stage build
- Позволяет удалить build-tools, dev dependencies, исходный код из финального образа
- Использовать .dockerignore
- Копировать только нужные файлы с помощью команды
COPY - Удалять кэш
10.6. Как передать переменные окружения в контейнер?
Переменные окружения используются для настройки приложения, это могут быть API URL, режим разработки, ключи.
- Через параметр
-e- При запуске контейнера:
docker run -e API_URL=https://api.com my-app - Внутри контейнера:
process.env.API_URL
- При запуске контейнера:
Через файл
.env1 2
API_URL=https://api.com NODE_ENV=production
Запуск:
1
docker run --env-file .env my-appЧерез Docker Compose
1 2 3 4 5 6
services: app: image: my-app environment: - API_URL=https://api.com - NODE_ENV=production
11. GitLab CI / Jenkins
11.1. Что такое pipeline в GitLab CI?
Pipeline (пайплайн) — это автоматизированный процесс выполнения набора задач (jobs), которые запускаются при определённых событиях в репозитории.
Обычно пайплайн запускается при:
- push в репозиторий
- создании merge request
- ручном запуске
Pipeline описывается в файле .gitlab-ci.yml, который находится в корне проекта.
11.2. Как настроить job для сборки React-приложения?
Чтобы собрать React-приложение в GitLab CI, нужно создать job, который:
- устанавливает Node.js
- устанавливает зависимости
- запускает сборку (npm run build)
1
2
3
4
5
6
build:
stage: build
image: node:18
script:
- npm install
- npm run build
11.3. Для чего нужны stages в .gitlab-ci.yml?
Stages — это этапы выполнения pipeline. Они определяют порядок выполнения задач.
Pipeline выполняет stages последовательно, а jobs внутри stage могут выполняться параллельно.
11.4. Пример .gitlab-ci.yml для сборки и линтинга фронтенда
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
image: node:18
stages:
- install
- lint
- build
cache:
paths:
- node_modules/
install:
stage: install
script:
- npm install
lint:
stage: lint
script:
- npm run lint
build:
stage: build
script:
- npm run build
artifacts:
paths:
- build/
11.5. Как кэшировать зависимости в пайплайне?
В CI установка зависимостей занимает много времени, чтобы ускорить pipeline, используется cache. Кэш позволяет сохранять зависимости между запусками pipeline.
1
2
3
cache:
paths:
- node_modules/
GitLab сохранит папку node_modules. При следующем pipeline зависимости будут восстановлены из кэша.
11.6. Как задеплоить на staging?
Staging — это промежуточная среда между development и production.
Она используется для:
- тестирования
- проверки перед релизом
- демонстрации заказчику
Шаги деплоя:
- собрать проект
- получить build-файлы
- загрузить их на сервер
1
2
3
4
5
6
7
8
deploy_staging:
stage: deploy
script:
- scp -r build/* user@server:/var/www/app
environment:
name: staging
only:
- main