Методическое пособие по JavaScript и React
Оглавление
Введение
Часть I: JavaScript
Основы синтаксиса
Функции
Объекты и массивы
Асинхронность
Примеры взаимодействия с DOM
Часть II: React
Введение в React
Компоненты
Хуки
Роутинг
Практические задания
Дополнительные ресурсы
Сборщик VIte
Введение
Это методическое пособие предназначено для изучения основ JavaScript и библиотеки React. Предполагается, что читатель знаком с базовыми понятиями веб-разработки (HTML, CSS).
Часть I: JavaScript
Основы синтаксиса
Переменные
// Объявление переменных
let name = "John"; // может быть изменено
const age = 25; // не может быть изменено
var oldWay = "value"; // устаревший способ
Типы данных
// Примитивные типы
const string = "Hello";
const number = 42;
const boolean = true;
const nullValue = null;
const undefinedValue = undefined;
const symbol = Symbol("id");
// Объекты
const object = { key: "value" };
const array = [1, 2, 3];
Операторы
// Арифметические
let sum = 5 + 3; // 8
let difference = 5 - 3; // 2
// Сравнение
console.log(5 == "5"); // true (нестрогое)
console.log(5 === "5"); // false (строгое)
// Логические
console.log(true && false); // false
console.log(true || false); // true
Основы синтаксиса
Function Declaration
function greet(name) {
return `Hello, ${name}!`;
}
Function Expression
const greet = function (name) {
return `Hello, ${name}!`;
};
Стрелочные функции
const greet = (name) => {
return `Hello, ${name}!`;
};
// Сокращенная форма
const square = (x) => x * x;
Главные отличия:
Function Declaration
// Можно вызвать ДО объявления
console.log(greet("Alice")); // "Hello, Alice!"
function greet(name) {
return `Hello, ${name}!`;
}
Function Expression
// Вызов ДО объявления вызовет ошибку
console.log(greet("Alice")); // ReferenceError: Cannot access 'greet' before initialization
const greet = function (name) {
return `Hello, ${name}!`;
};
Стрелочные функции
// Тоже ошибка - ведут себя как const
console.log(greet("Alice")); // ReferenceError
const greet = (name) => {
return `Hello, ${name}!`;
};
Также есть различия в контексте (this), в аргументах и в использовании в качестве конструктора, если интересно про это можете сами почитать подробнее.
Объекты и массивы
Работа с объектами
const person = {
name: "Alice",
age: 30,
greet() {
console.log(`Hello, I'm ${this.name}`);
},
};
// Деструктуризация
const { name, age } = person;
// Spread оператор
const updatedPerson = { ...person, city: "Moscow" };
Как работает деструктуризация
JavaScript смотрит на объект person
Ищет свойства с именами name и age
Создает новые переменные name и age
Копирует значения из соответствующих свойств объекта
Что происходит в spread операторе
…person - “распаковывает” все свойства объекта person
Создается новый объект, куда копируются все свойства из person
Добавляется новое свойство city: “Moscow”
Если бы в person уже было свойство city, оно бы перезаписалось
Работа с массивами
const numbers = [1, 2, 3, 4, 5];
// Методы массивов
const doubled = numbers.map((num) => num * 2);
const even = numbers.filter((num) => num % 2 === 0);
const sum = numbers.reduce((acc, num) => acc + num, 0);
Метод map() - Создает новый массив, применяя функцию к каждому элементу исходного массива.
// Пример
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((num) => num * 2);
// Пошагово что происходит:
// 1. num = 1 → return 1 * 2 = 2
// 2. num = 2 → return 2 * 2 = 4
// 3. num = 3 → return 3 * 2 = 6
// 4. num = 4 → return 4 * 2 = 8
// 5. num = 5 → return 5 * 2 = 10
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(numbers); // [1, 2, 3, 4, 5] - исходный массив не изменился!
Метод filter() - Создает новый массив с элементами, которые прошли проверку (вернули true).
// Пример
const numbers = [1, 2, 3, 4, 5];
const even = numbers.filter((num) => num % 2 === 0);
// Пошагово что происходит:
// 1. num = 1 → 1 % 2 === 0? false → исключаем
// 2. num = 2 → 2 % 2 === 0? true → оставляем
// 3. num = 3 → 3 % 2 === 0? false → исключаем
// 4. num = 4 → 4 % 2 === 0? true → оставляем
// 5. num = 5 → 5 % 2 === 0? false → исключаем
console.log(even); // [2, 4]
console.log(numbers); // [1, 2, 3, 4, 5] - исходный массив не изменился!
Метод reduce() - Преобразует массив в единственное значение, последовательно обрабатывая каждый элемент.
// Пример
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((acc, num) => acc + num, 0);
// Пошагово что происходит:
// Начальное значение acc = 0
// 1. acc = 0, num = 1 → return 0 + 1 = 1
// 2. acc = 1, num = 2 → return 1 + 2 = 3
// 3. acc = 3, num = 3 → return 3 + 3 = 6
// 4. acc = 6, num = 4 → return 6 + 4 = 10
// 5. acc = 10, num = 5 → return 10 + 5 = 15
console.log(sum); // 15
Асинхронность
Callbacks
function fetchData(callback) {
setTimeout(() => {
callback("Data received");
}, 1000);
}
fetchData((data) => {
console.log(data);
});
Callback (функция обратного вызова) - это функция, которая передается в другую функцию как аргумент и выполняется после завершения какой-либо операции.
Promises
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data received");
// или reject("Error message");
}, 1000);
});
}
fetchData()
.then((data) => console.log(data))
.catch((error) => console.error(error));
Promise - это объект, который представляет результат асинхронной операции. У него есть 3 состояния:
pending (ожидание)
fulfilled (выполнено успешно)
rejected (выполнено с ошибкой)
Async/await
async function getData() {
try {
const data = await fetchData();
console.log(data);
} catch (error) {
console.error(error);
}
}
Async/await - это синтаксический сахар над Promises, который позволяет писать асинхронный код так, как будто он синхронный.
Про Promise и Async/await можно написать очень много, если интересно, то можно изучить самим
Примеры взаимодействия с DOM
// Получение элементов
const element = document.getElementById("myId");
const elements = document.querySelectorAll(".myClass");
// Создание элементов
const newDiv = document.createElement("div");
newDiv.textContent = "Hello World";
// Добавление в DOM
document.body.appendChild(newDiv);
// Обработка событий
element.addEventListener("click", function () {
console.log("Element clicked!");
});
DOM (Document Object Model) - это программный интерфейс для HTML и XML документов. Он представляет структуру документа в виде дерева объектов, которые можно изменять с помощью JavaScript.
Как устроен DOM (дерево):
text
Document (корень)
↓
<html>
├── <head>
│ ├── <title>
│ └── ...
└── <body>
├── <h1 id="header">
├── <div class="content">
│ └── <p>
└── ...
Каждый элемент становится узлом (node) в дереве:
Element nodes - теги (div, p, h1)
Text nodes - текстовое содержимое
Attribute nodes - атрибуты (id, class)
Часть II: React
Введение в React
React — это JavaScript-библиотека для создания пользовательских интерфейсов.
Создание приложения
npx create-react-app my-app
cd my-app
npm start
Компоненты
Функциональные компоненты
// Welcome.jsx
import React from "react";
function Welcome(props) {
return <h1>Hello, {props.name}!</h1>;
}
export default Welcome;
Использование компонентов
// App.jsx
import React from "react";
import Welcome from "./Welcome";
function App() {
return (
<div>
<Welcome name="Alice" />
<Welcome name="Bob" />
</div>
);
}
export default App;
Хуки
useState
import React, { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
useState - это хук React, который позволяет добавлять состояние в функциональные компоненты. До появления хуков состояние могло быть только в классовых компонентах.
useState(0) инициализирует состояние значением 0
Возвращает массив [currentValue, setterFunction]
Деструктуризация создает переменные count и setCount
count содержит текущее значение состояния
setCount - функция для обновления состояния
useEffect
import React, { useState, useEffect } from "react";
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
// Загрузка данных при изменении userId
fetch(`/api/users/${userId}`)
.then((response) => response.json())
.then((data) => setUser(data));
}, [userId]); // Зависимости
if (!user) return <div>Loading...</div>;
return <div>{user.name}</div>;
}
useEffect - это хук, который позволяет выполнять побочные эффекты в функциональных компонентах. Он заменяет методы жизненного цикла в классовых компонентах.
Эффект выполняется после каждого рендера, где userId изменился
Делает запрос к API когда userId меняется
Обновляет состояние user когда данные приходят
Вызывает ререндер компонента с новыми данными
Роутинг
Установка React Router
npm install react-router-dom
Настройка маршрутов
import React from "react";
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
import Home from "./Home";
import About from "./About";
import Contact from "./Contact";
function App() {
return (
<Router>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/contact">Contact</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</Router>
);
}
Более крутой пример:
Routes.tsx:
import { lazy, useEffect, useState } from 'react'
import { RouteProps } from 'react-router-dom'
const Page1 = lazy(() =>
import('~/pages/Page1').then(module => ({
default: module.PageFirst
}))
)
const Page2 = lazy(() =>
import('~/pages/Page2').then(module => ({
default: module.PageSecond
}))
)
const Page3 = lazy(() =>
import('~/pages/Page3').then(module => ({
default: module.PageThird
}))
)
export const AppRoutes = {
PAGE1: 'page1',
PAGE2: 'page2',
PAGE3: 'page3',
} as const
export type AppRoutesT = (typeof AppRoutes)[keyof typeof AppRoutes]
export const RoutePath: Record<AppRoutesT, string> = {
[AppRoutes.PAGE1]: '/page1',
[AppRoutes.PAGE2]: '/page2',
[AppRoutes.PAGE3]: '/page3',
}
export const routeConfig: Record<AppRoutesT, RouteProps> = {
[AppRoutes.PAGE1]: {
path: RoutePath.page1,
element: <PageFirst />
},
[AppRoutes.PAGE2]: {
path: RoutePath.page2,
element: <PageSecond />
},
[AppRoutes.PAGE3]: {
path: RoutePath.page3,
element: <PageThird />
}
}
app-router.tsx:
import { memo, useCallback } from "react";
import { Suspense } from "react";
import { Route, RouteProps, Routes } from "react-router-dom";
import { routeConfig } from "~/shared/config/routes/Routes";
export const AppRouter = memo(() => {
const renderWithWrapper = useCallback(
({ path, element }: RouteProps) => (
<Route key={path} path={path} element={element} />
),
[]
);
return (
<Suspense fallback={<div>Загрузка...</div>}>
{" "}
<Routes>{Object.values(routeConfig).map(renderWithWrapper)}</Routes>
</Suspense>
);
});
Потом вызываете AppRouter в вашем главном файле (обычно main.tsx/App.tsx)
Сборщик Vite
Сборщик модулей (Module Bundler) - это инструмент, который берет различные модули JavaScript и их зависимости, и объединяет их в один или несколько оптимизированных файлов, готовых для использования в браузере.
Аналогия:
Представьте, что у вас есть:
Модули - отдельные коробки с кодом (файлы .js, .jsx, .ts)
Зависимости - связи между коробками (import/export)
Сборщик - грузовик, который упаковывает все коробки в один контейнер
Исходные файлы → Обход зависимостей → Компиляция → Оптимизация → Бандл
Этапы традиционной сборки:
Entry Point - начальный файл
Dependency Graph - построение графа зависимостей
Transformation - компиляция (JSX → JS, Sass → CSS)
Bundling - объединение в бандлы
Optimization - оптимизация, сжатие
Output - финальные файлы
Проблемы традиционных сборщиков:
# Медленный запуск в development
npm start # Может занимать 10-60 секунд!
# Медленный Hot Module Replacement (HMR)
# Изменение файла → пересборка → 1-5 секунд ожидания
# Сложная конфигурация
Vite
Vite (франц. “быстрый”, произносится “вит”) - это современный сборщик, который решает проблемы традиционных инструментов за счет использования нативных ES модулей.
Ключевая идея Vite:
Разделение сборки на две части:
Development - использует нативные ES модули браузера
Production - использует оптимизированную сборку Rollup
Development Mode:
Браузер ← HTTP ← Vite Dev Server ← ES Modules
Production Mode:
Браузер ← Оптимизированные файлы ← Rollup сборка
Что помогает Vite достигать больших скоростей
1. Нативные ES Modules в разработке:
Традиционный подход
// Все файлы компилируются в один бандл ДО запуска
// Большое время ожидания при старте
Vite подход:
// Браузер загружает модули напрямую через HTTP
<script type="module" src="/src/main.jsx"></script>
// Vite сервер преобразует модули на лету
// Только по запросу браузера
ES Modules (ECMAScript Modules) - это официальная, стандартизированная система модулей в JavaScript, которая позволяет разбивать код на отдельные файлы и организовывать зависимости между ними.
Простая аналогия:
Представьте книгу:
Без модулей - одна большая глава (1000 страниц)
С модулями - много глав с оглавлением (импорты/экспорты)
2. Esbuild для трансформаций
В 10-100x быстрее чем Babel
Мгновенная компиляция TypeScript, JSX
Пример запуска сервера разработки:
Стандартный вариант:
$ npm start
# Compiling...
# Compiled successfully in 15.3s
Vite:
$ npm run dev
# Vite dev server running in 347ms
# Ready in 500ms
Создание React приложения с Vite
# 1. Инициализация проекта
npm create vite@latest
# 2. Интерактивный выбор:
# ✔ Project name: … my-react-app
# ✔ Select a framework: › React
# ✔ Select a variant: › JavaScript + SWC
# 3. Установка зависимостей
cd my-react-app
npm install
# 4. Запуск development сервера
npm run dev
# 5. Открыть в браузере
# → http://localhost:5173/
Структура получившегося проекта:
my-react-app/
├── node_modules/
├── public/
│ └── vite.svg
├── src/
│ ├── assets/
│ ├── App.css
│ ├── App.jsx
│ ├── index.css
│ ├── main.jsx
│ └── components/
├── index.html
├── package.json
├── vite.config.js
└── README.md
Ключевые файлы:
1. index.html (Корневой HTML):
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
2. main.jsx (Точка входа):
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
3. App.jsx (Основной компонент)
import { useState } from "react";
import reactLogo from "./assets/react.svg";
import viteLogo from "/vite.svg";
import "./App.css";
function App() {
const [count, setCount] = useState(0);
return (
<>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.jsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</>
);
}
export default App;
4. Скрипты Package.json
{
"scripts": {
"dev": "vite", // Сервер разработки
"build": "vite build", // Сборка для production
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview" // Предпросмотр production сборки
}
}
5. Настройка Vite (vite.config.js)
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
server: {
port: 3000, // Порт разработки
open: true, // Автоматически открывать браузер
},
build: {
outDir: "dist", // Папка для сборки
sourcemap: true, // Карты исходного кода
},
});