Загрузка...
;\n returnСчётчик: {count}
\n \nCount: {count}
\n \nЧто такое специфичность селекторов в CSS?
Специфичность (Specificity) в CSS — это алгоритм, с помощью которого браузер определяет, какие стилевые правила из множества потенциально применимых к одному элементу будут применены в конечном итоге. Это механизм разрешения конфликтов, когда несколько селекторов с разными правилами нацелены на один и тот же HTML-элемент. Специфичность можно представить как «вес» или «приоритет» селектора: чем выше специфичность, тем больше вероятность, что его стили будут использованы.
Как рассчитывается специфичность?
Специфичность рассчитывается как сумма четырех компонентов, которые часто представляют в виде четырех чисел (a, b, c, d) или (0, 0, 0, 0), где:
Что такое env?
В контексте веб-разработки, особенно frontend, термин env (сокращение от environment — окружение) обычно относится к переменным окружения или файлам окружения (например, .env). Это механизм для управления настройками приложения в зависимости от среды выполнения (разработка, тестирование, продакшн). Для Frontend Developer понимание env критически важно, так как оно обеспечивает безопасность, гибкость и поддержку разных сред без изменения кода.
Основные аспекты env в Frontend
Это динамические именованные значения (например, API_URL, DEBUG_MODE), которые влияют на поведение приложения. Они хранятся в файлах .env, которые не коммитятся в репозиторий (добавляются в .gitignore), чтобы защитить чувствительные данные (ключи API, пароли).
Пример файла .env:
API_URL=https://api.example.com
DEBUG=true
MAX_ITEMS=50
Что такое Responsive Web-страница?
Responsive Web-страница (от англ. «responsive» — отзывчивый, адаптивный) — это веб-страница, которая автоматически адаптирует свой layout, содержание и функциональность для оптимального отображения на различных устройствах и экранах с разными размерами и ориентациями. Ключевая цель — обеспечить одинаково удобный и эффективный пользовательский опыт независимо от того, просматривается сайт на Desktop компьютере (широкий экран), планшете, мобильном телефоне или даже умном телефоне.
Основные принципы Responsive Design
Адаптивный дизайн строится на трёх фундаментальных технических принципах:
em, rem, vw/vh). Контейнеры и колонки макета рассчитываются относительно ширины родительского элемента или области просмотра.Как задать breakpoint в CSS
В современном CSS для создания breakpoint (точки перехода, медиа-запроса) используются медиа-запросы (@media). Они позволяют применять стили в зависимости от характеристик устройства или среды отображения, таких как ширина области просмотра, ориентация или плотность пикселей.
Основные типы медиа-запросов для breakpoint
1. Медиа-запросы по ширине (width) — самые распространённые для адаптивного дизайна:
/* Для экранов шириной до 768px (мобильные) */
@media (max-width: 768px) {
.container {
padding: 10px;
}
}
/* Для экранов шириной от 768px до 1024px (планшеты) */
@media (min-width: 768px) and (max-width: 1024px) {
.container {
padding: 20px;
}
}
/* Для экранов шириной более 1024px (десктоп) */
@media (min-width: 1024px) {
.container {
padding: 30px;
}
}
Вопрос о передаче useState в функцию
Если коротко, то передать в функцию можно, но это почти всегда бессмысленно и приведёт к ошибкам или нерабочему коду. Давайте разберём этот вопрос подробно, так как он затрагивает фундаментальные принципы работы React Hooks.
Что такое useState и что он возвращает?
Хук useState возвращает массив из двух элементов:
const [count, setCount] = useState(0);
// Возвращает: [0, function setCount(newValue) {...}]
В этом примере count — это примитивное число, а setCount — функция. Передать саму функцию useState куда-либо нельзя, так как это импорт из React (import { useState } from 'react'), это готовая функция из библиотеки.
Вопрос, скорее всего, подразумевает: "Что будет, если передать результат вызова useState (то есть массив [state, setState]) или его части в другую функцию?". Рассмотрим оба варианта.
Мой подход к непрерывному обучению в Frontend-разработке
Хотя у меня за плечами более 10 лет профессионального опыта в frontend-разработке, я считаю непрерывное обучение фундаментальным принципом успеха в нашей быстро меняющейся индустрии. Прямо сейчас я не прохожу формальные курсы с фиксированной программой, но мой процесс обучения является систематическим, целенаправленным и интегрированным в ежедневную работу.
Активные направления моего текущего развития
В настоящее время я фокусируюсь на нескольких ключевых областях:
Примеры типовых задач на JavaScript в Frontend-разработке
Как Frontend Developer с 10+ лет опыта, я решаю широкий спектр задач — от простой манипуляции DOM до создания сложных SPA-приложений. Вот ключевые категории с примерами:
1. Манипуляция DOM и работа с событиями
Цель: Динамическое изменение контента и обработка пользовательских взаимодействий.
// Пример: Создание интерактивного списка задач (To-Do)
class TodoList {
constructor(containerId) {
this.container = document.getElementById(containerId);
this.tasks = [];
this.init();
}
init() {
this.renderForm();
this.container.addEventListener('click', (e) => {
if (e.target.classList.contains('delete-btn')) {
this.deleteTask(e.target.dataset.id);
}
});
}
Почему вопрос о Scope (области видимости) — ключевой на собеседовании Frontend Developer
Вопросы о области видимости (Scope) — один из краеугольных камней собеседований во frontend-разработке, особенно для JavaScript. Это не просто проверка теории, а глубокая оценка понимания основ языка, которое напрямую влияет на качество, надёжность и безопасность кода.
Область видимости — фундамент понимания JavaScript
JavaScript, несмотря на кажущуюся простоту, имеет уникальную и порой неочевидную модель видимости переменных. Кандидат, который досконально её понимает:
Ключевые аспекты, которые проверяет вопрос о Scope
Приоритет выполнения: setTimeout vs Promise
Этот вопрос затрагивает фундаментальный аспект работы Event Loop в JavaScript. Чтобы дать точный ответ, нужно разделить его на две ситуации: выполнение в основном потоке и сравнение времени выполнения.
1. Базовый принцип Event Loop
JavaScript имеет однопоточную модель выполнения с асинхронной природой. Event Loop управляет порядком выполнения кода через несколько очередей:
Promise.then/catch/finally, queueMicrotask, MutationObserversetTimeout/setInterval, setImmediate, обработчиков событий, requestAnimationFrameКлючевое правило: Event Loop обрабатывает микрозадачи между макрозадачами и очищает всю очередь микрозадач перед переходом к следующей макрозадаче.
2. Сравнительный пример
Рассмотрим классический пример:
Использование useCallback в современной React-разработке
Да, я действительно часто использую useCallback, но не бездумно, а с четким пониманием, когда это необходимо. За 10+ лет работы с React я выработал прагматичный подход к этому хуку, основанный на анализе реальной производительности и читаемости кода.
Основные сценарии применения useCallback
1. Мемоизация колбэков для дочерних компонентов:
const ProductList = ({ products, onSelect }) => {
// Без useCallback ProductItem будет перерендериваться
// при каждом рендере родителя
const handleSelect = useCallback((productId) => {
onSelect(productId);
}, [onSelect]);
return (
<div>
{products.map(product => (
<ProductItem
key={product.id}
product={product}
onSelect={handleSelect}
/>
))}
</div>
);
};
Введение: Различие в привязке this - ключевая особенность
Нет, методы привязки this у стрелочных функций и обычных функций кардинально различаются. Это одно из самых важных поведенческих отличий в JavaScript, напрямую влияющее на проектирование кода, особенно при работе с контекстом выполнения.
Подробное сравнение механизмов привязки this
1. Обычная функция: Динамический контекст
Обычная функция имеет собственный, динамически определяемый this. Значение this определяется не в момент объявления функции, а в момент её вызова. Оно зависит от того, как была вызвана функция.
const person = {
name: 'Иван',
greet: function() {
console.log(`Привет, я ${this.name}`);
}
};
person.greet(); // Вывод: "Привет, я Иван" - `this` = объект `person`
const externalGreet = person.greet;
externalGreet(); // Вывод: "Привет, я undefined" - `this` = глобальный объект (window/undefined в strict mode)
Различие в ранге асинхронных операций: Promise vs setTimeout
Этот вопрос затрагивает фундаментальный аспект работы Event Loop в JavaScript. Promise и setTimeout действительно представляют собой асинхронные операции, но они имеют разный приоритет (ранг) в механизме событий, что напрямую влияет на порядок их выполнения.
Анатомия Event Loop: микрозадачи и макрозадачи
Ключ к пониманию различий лежит в концепции очередей задач (task queues). В современном JavaScript существует два основных типа очередей:
Promise относится к микрозадачам. К ним же относятся queueMicrotask(), MutationObserver и некоторые другие API.
setTimeout (а также setInterval, setImmediate (Node.js), события ввода-вывода, UI рендеринг) относятся к макрозадачам.
Можно ли у стрелочной функции вызвать метод apply?
Да, у стрелочной функции можно вызвать метод apply() (и аналогичные методы call() и bind()), так как стрелочные функции в JavaScript являются объектами типа Function. Однако результат и поведение этих методов будет отличаться от их применения к обычным функциям из-за фундаментальных особенностей стрелочных функций.
Особенности стрелочных функций
Стрелочные функции отличаются от обычных функций в трех ключевых аспектах:
this).arguments.Как работает apply() с стрелочной функцией
Метод apply() предназначен для вызова функции с заданным значением this и аргументами в виде массива. С обычной функцией он успешно изменяет её контекст:
// Обычная функция
function regularFunc() {
console.log(this.value);
}
Что такое кастомный хук в React?
Кастомный хук (Custom Hook) в React — это JavaScript-функция, имя которой начинается с префикса use, которая может использовать другие хуки (такие как useState, useEffect, useContext и т.д.) и предназначена для переиспользования логики с состоянием между несколькими компонентами. Это одно из ключевых нововведений, появившихся с выходом React Hooks в версии 16.8, которое позволяет "вытащить" и инкапсулировать общую логику из компонентов в отдельные, независимые функции.
Основные характеристики кастомных хуков
Можно ли изменить this при вызове метода объекта?
Да, это возможно, и даже является фундаментальной возможностью JavaScript. Ключевой момент заключается в том, что значение this в JavaScript динамически определяется не местом объявления функции, а способом её вызова (за исключением стрелочных функций). Для управления значением this существуют три специальных метода: call(), apply() и bind().
Основные методы управления this
Эти методы доступны для любой функции, так как они являются свойствами встроенного прототипа Function.prototype.
call(context, ...args)Немедленно вызывает функцию, явно устанавливая this в первый переданный аргумент (context). Последующие аргументы передаются в функцию как обычные параметры.
const user = { name: 'Анна' };
const car = { brand: 'Toyota' };
function showInfo(model, year) {
console.log(`Пользователь: ${this.name}, Машина: ${this.brand}, Модель: ${model}, Год: ${year}`);
}
Что следует избегать в методе render компонента React
Метод render является ключевой частью компонента в React, но он предназначен исключительно для возврата JSX-разметки и должен быть «чистым» (без побочных эффектов). Нарушение этого принципа ведёт к непредсказуемому поведению, ошибкам и проблемам с производительностью. Вот что категорически не нужно делать внутри render.
1. Побочные эффекты (Side Effects)
render вызывается многократно при каждом обновлении компонента, поэтому любые операции, изменяющие состояние приложения или внешний мир, недопустимы. К ним относятся:
document.getElementById).Соответствие useEffect этапам жизненного цикла классовых компонентов
Хук useEffect в функциональных компонентах React заменяет не один, а несколько методов жизненного цикла классовых компонентов. Это фундаментальное отличие парадигм: useEffect абстрагирует логику побочных эффектов, объединяя то, что в классах было разнесено по разным методам.
Основные соответствия
Наиболее прямое соответствие — это комбинация двух методов. useEffect без второго аргумента (массива зависимостей) выполняется после каждого рендера, подобно:
componentDidMount — после первого рендераcomponentDidUpdate — после каждого обновленияИспользование componentDidMount в React
componentDidMount — это жизненный метод класса React-компонента, который вызывается сразу после монтирования компонента в DOM. Это ключевое место для выполнения side effects, таких как загрузка данных из API, подписка на события, инициализация сторонних библиотек или работа с DOM.
Основные сценарии применения
Наиболее распространённый случай — выполнение AJAX-запросов для получения начальных данных компонента.
import React from 'react';
class UserProfile extends React.Component {
constructor(props) {
super(props);
this.state = {
user: null,
loading: true,
error: null
};
}
Плюсы и минусы стека React + Redux
Преимущества стека React + Redux
React и Redux, будучи использованы вместе, образуют мощную комбинацию для построения сложных клиентских приложений. Их совместное применение обеспечивает ряд ключевых преимуществ.
React отвечает за UI-логику и рендеринг компонентов, а Redux управляет состоянием приложения (application state) и бизнес-логикой. Это разделение делает код более организованным, понятным и легким для поддержки. Компоненты React становятся преимущественно "чистыми" (presentational), они получают данные и callback-функции через props и просто отображают интерфейс. Логика изменения данных и сложные взаимодействия концентрируются в reducers, actions и middleware Redux.
Плюсы и минусы async/await в JavaScript
async/await — это синтаксический сахар над Promise, представленный в ES2017, который кардинально изменил подход к асинхронному программированию в JavaScript. Как и любой инструмент, он имеет свои преимущества и недостатки, которые важно понимать для эффективного применения.
Основные преимущества async/await
1. Улучшенная читаемость и поддержка кода
// Сравнение: Promise.then() vs async/await
// Старый подход с цепочкой then
fetchData()
.then(response => response.json())
.then(data => processData(data))
.then(result => saveResult(result))
.catch(error => console.error('Ошибка:', error));
Особенности контекста стрелочных функций в JavaScript
Контекст выполнения (this) — одна из самых важных и фундаментальных особенностей стрелочных функций (Arrow Functions), отличающая их от обычных (Regular Functions). Это ключевое отличие влияет на их поведение, использование и выбор в различных ситуациях.
1. Отсутствие собственного контекста this
Стрелочные функции не имеют собственного this. Они захватывают (или «наследуют») значение this из окружающей лексической области видимости (того контекста, в котором они были созданы). Это поведение противоположно обычным функциям, которые определяют свой this в момент вызова, основываясь на способе вызова (например, как метод объекта, через call/apply, как конструктор или просто вызов).
Методы жизненного цикла классового компонента React
Методы жизненного цикла (Lifecycle Methods) в классовых компонентах React — это специальные функции, которые автоматически вызываются на разных этапах существования компонента: от его создания и монтирования в DOM до обновления и удаления. Они позволяют контролировать поведение компонента, выполнять side-эффекты, оптимизировать производительность и управлять состоянием.
Основные категории методов жизненного цикла
Все методы жизненного цикла можно разделить на три основные фазы:
Также существуют методы обработки ошибок (Error Handling), появившиеся в React 16.
Детальный разбор методов по фазам
Основные хуки React (Hooks)
Я работаю с React с момента выхода хуков в 2019 году (React 16.8) и активно применяю их во всех проектах. Хуки — это функции, которые позволяют использовать состояние и другие возможности React без написания классов. Они революционизировали разработку на React, сделав код более компактным, читаемым и reusable.
Все хуки можно разделить на три основные категории: базовые (используются в 95% компонентов), дополнительные (для оптимизации и side effects) и пользовательские (собственная логика).
📌 Базовые хуки (Basic Hooks)
Эти хуки используются практически в каждом функциональном компоненте.
useState
Управляет внутренним состоянием компонента. Возвращает массив из двух элементов: текущее значение состояния и функцию для его обновления. import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0); // Инициализация
Сбор значений из input в React без использования useState
Хотя useState — самый распространённый и рекомендуемый способ управления состоянием форм в React, существуют альтернативные подходы, которые могут быть полезны в определённых сценариях.
1. Использование useRef для получения значений
Прямое обращение к DOM-элементу без перерисовки компонента. Подходит для чтения значений без необходимости мгновенной реакции интерфейса.
import React, { useRef } from 'react';
function RefForm() {
const inputRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
// Получаем значение напрямую из DOM-элемента
console.log('Input value:', inputRef.current.value);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
ref={inputRef}
placeholder="Введите текст"
/>
<button type="submit">Отправить</button>
</form>
);
}
Разница между Promise.race и Promise.any
Это два похожих на первый взгляд метода для работы с массивом Promise'ов, но они имеют принципиально разное поведение. Нужно четко понимать, когда использовать каждый из них.
Promise.race — первый результат (успех или ошибка)
Promise.race() возвращает Promise, который разрешается или отклоняется со значением первого завершённого Promise'а — неважно, успешно он завершился или с ошибкой.
const promise1 = new Promise((resolve) => setTimeout(() => resolve('First'), 100))
const promise2 = new Promise((resolve) => setTimeout(() => resolve('Second'), 500))
const promise3 = new Promise((reject) => setTimeout(() => reject('Error'), 50))
// Пример 1: первым завершится promise3 с ошибкой
Promise.race([promise1, promise2, promise3])
.then(result => console.log('Success:', result))
.catch(error => console.log('Error:', error)) // Error: Error
Применение методов массива к Map
Map в JavaScript - это отдельная структура данных, но её можно преобразовать в массив для использования методов Array.prototype.
1. Преобразование Map в массив
const map = new Map([
['name', 'John'],
['age', 30],
['city', 'New York']
]);
// Способ 1: Array.from() с entries()
const entries = Array.from(map.entries());
console.log(entries);
// [['name', 'John'], ['age', 30], ['city', 'New York']]
// Способ 2: Spread оператор
const spreadEntries = [...map];
console.log(spreadEntries);
// То же самое
// Способ 3: Array.from() с keys() или values()
const keys = Array.from(map.keys());
const values = Array.from(map.values());
2. Использование методов массива
Теперь с массивом можно использовать все методы:
const map = new Map([
['user1', { name: 'John', score: 100 }],
['user2', { name: 'Jane', score: 95 }],
['user3', { name: 'Bob', score: 110 }]
]);
Оптимизация ререндеров в классовых компонентах React
Хотя функциональные компоненты с хуками стали стандартом, классовые компоненты остаются важной частью большинства приложений. Понимание оптимизации ререндеров критично для производительности.
1. shouldComponentUpdate() - явное управление ререндерами
shouldComponentUpdate() - это метод жизненного цикла, который определяет, нужно ли перерендеривать компонент.
class UserProfile extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// Ререндер только если props или state изменились
return (
this.props.id !== nextProps.id ||
this.state.isLoading !== nextState.isLoading
)
}
render() {
return <div>{this.props.id}</div>
}
}
Осторожно: неправильная реализация может привести к багам, когда UI не обновляется несмотря на изменение данных.
2. React.PureComponent - поверхностное сравнение
Нет, в браузере можно закэшировать не только статику. Кэширование работает для любых ресурсов и API ответов, если это разрешено HTTP заголовками.
HTTP Кэширование
Браузер кэширует ресурсы на основе HTTP заголовков Cache-Control, ETag, Last-Modified и других. Это относится ко всему: HTML, CSS, JS, изображениям, API ответам и даже XHR запросам.
Основные заголовки кэширования
// Статика (длительное кэширование)
// Cache-Control: public, max-age=31536000
// HTML (короткое кэширование)
// Cache-Control: public, max-age=3600
// API ответы (кэшируются, если не запрещено)
// Cache-Control: max-age=300
// Запретить кэширование
// Cache-Control: no-store, no-cache
Кэширование API ответов
// Fetch с использованием браузерного кэша
fetch('/api/users', {
method: 'GET',
// Браузер автоматически использует Cache-Control из ответа
})
.then(response => response.json())
.then(data => console.log(data));
Замыкания и управление памятью
Замыкание (closure) — это функция, которая имеет доступ к переменным из своей области видимости, даже после того, как эта область завершила своё выполнение. Это мощная возможность JavaScript, но неправильное использование может привести к утечкам памяти.
Как замыкание влияет на память
Проблема: утечка памяти через замыкания
Когда функция создаёт замыкание, она сохраняет ссылки на все переменные из внешней области видимости. Если замыкание существует долгое время, эти переменные не будут удалены сборщиком мусора.
// Пример утечки памяти через замыкание
function createLeak() {
const largeArray = new Array(1000000).fill("данные");
return function() {
console.log(largeArray.length); // замыкание удерживает largeArray в памяти
};
}
const leakFunction = createLeak();
// largeArray не будет удалена, даже если нам больше не нужна сам массив
// Память будет удерживаться столько, сколько существует leakFunction
Как useState запоминает прошлое состояние в React
useState — это не магия. React имеет чёткий механизм для запоминания состояния каждого компонента. Понимание этого механизма критично для правильного использования хуков.
Основной механизм: Fiber Architecture
React использует структуру данных "Fiber" для каждого компонента:
// Упрощённая структура Fiber
const fiber = {
component: MyComponent,
props: { name: "John" },
state: [], // Здесь хранятся значения useState
hooks: [], // Информация о хуках
parent: parentFiber,
child: childFiber,
next: siblingFiber
};
Каждому компоненту соответствует уникальный fiber узел в дереве. На этом узле хранится:
Порядок вызовов хуков (Hook Rules)
Критически важно: useState должен вызываться в одном и том же порядке!
Реакт запоминает состояние на основе порядка вызова хуков, а не их названия:
Как this попадает в функцию и правила его определения
this — один из самых сложных концептов в JavaScript. Понимание того, как this определяется при вызове функции, критично для написания корректного кода. Значение this определяется НЕ в момент объявления функции, а в момент её вызова.
Правила определения this
Есть чёткая иерархия правил, которые определяют значение this:
1. Явное указание: call(), apply(), bind()
Высший приоритет — явное связывание контекста:
const user = {
name: 'John',
greet: function() {
console.log(`Hello, ${this.name}`);
}
};
const admin = {
name: 'Admin'
};
// call() — вызывает с явным this
user.greet.call(admin); // 'Hello, Admin'
// apply() — то же самое, но с массивом аргументов
user.greet.apply(admin); // 'Hello, Admin'
// bind() — создаёт новую функцию с привязанным this
const boundGreet = user.greet.bind(admin);
boundGreet(); // 'Hello, Admin'
Как React защищает от XSS?
XSS (Cross-Site Scripting) — это атака, когда вредоносный код (обычно JavaScript) внедряется на веб-сайт и выполняется в браузере пользователя. React имеет встроенные механизмы защиты от этого.
Что такое XSS?
// Пример уязвимости
const userInput = '<img src=x onerror="alert(\"Hacked!\")">'; // Опасные данные от пользователя
// В обычном HTML/jQuery
document.innerHTML = userInput; // Выполнит JavaScript!
// Результат: попап "Hacked!" - атака сработала
Как React защищает
React по умолчанию экранирует все текстовые данные в JSX:
const userInput = '<img src=x onerror="alert(\"Hacked!\")">'; // XSS попытка
function Component() {
return <div>{userInput}</div>; // Безопасно!
}
// React отобразит буквальный текст:
// <div><img src=x onerror="alert(\"Hacked!\")"></div>
// XSS не выполнится!
Как работает экранирование:
Как хорошо знаешь Vue?
Это вопрос о глубине знания Vue.js фреймворка. Я отвечу честно и компrehensively.
Краткий ответ
Профессиональный уровень. Знаю Vue 2 и Vue 3, понимаю architecture, best practices, могу делать сложные приложения с оптимизацией.
Vue 2 vs Vue 3: Основные отличия
export default {
data() {
return {
count: 0,
name: 'John'
};
},
computed: {
doubled() {
return this.count * 2;
}
},
methods: {
increment() {
this.count++;
}
},
watch: {
count(newVal, oldVal) {
console.log(`changed from ${oldVal} to ${newVal}`);
}
},
mounted() {
// инициализация
},
beforeDestroy() {
// очистка
}
};
Проблемы: логика разбросана по разным секциям, сложно следить за реактивностью.
import { ref, computed, watch, onMounted, onUnmounted } from 'vue';
Атрибут async при загрузке скриптов
Атрибут async указывает браузеру загружать JavaScript скрипт асинхронно, параллельно с парсингом HTML, что улучшает производительность страницы.
Как работает async?
Без async (синхронная загрузка):
HTML parsing -> Script request -> Script download -> Script execution -> HTML parsing
Браузер полностью останавливает парсинг HTML при встрече со скриптом, загружает его, выполняет, и только потом продолжает.
С async (асинхронная загрузка):
HTML parsing -> Script request (в фоне)
HTML parsing (продолжается) <- Script download завершена -> Script execution -> HTML parsing
Браузер загружает скрипт в фоне, одновременно продолжая парсить HTML. Когда скрипт загружен, выполнение паузирует парсинг для его выполнения.
Синтаксис
<script async src="https://example.com/script.js"></script>
Как это работает в деталях
Фаза 1: Инициирование загрузки
Архитектура React: разделение на React и ReactDOM
Разделение React на два отдельных пакета — это архитектурное решение, которое отражает принцип разделения ответственности (Single Responsibility). Давайте разберёмся, почему это было сделано.
Основная причина: независимость ядра от платформы
React ядро (пакет react) содержит исключительно логику компонентов: состояние, жизненный цикл, хуки, контекст и алгоритм reconciliation (сравнение и обновление виртуального DOM). Это полностью независимо от того, как компоненты будут отрендерены.
ReactDOM (пакет react-dom) — это рендерер, который берёт результат работы React компонентов и преобразует их в реальный DOM браузера. Это позволяет одному ядру работать с разными целевыми платформами:
// React — ядро, работает везде
import React from 'react';
function MyComponent() {
const [count, setCount] = React.useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
Где писать запросы к серверу в React?
Это один из самых важных вопросов в React архитектуре. Правильное размещение API запросов влияет на производительность, тестируемость и масштабируемость приложения.
Правило: Не в компонентах
Главный принцип: Компоненты должны отвечать только за рендеринг, а не за логику получения данных.
// Плохо — API прямо в компоненте
export function QuestionList() {
const [questions, setQuestions] = useState([]);
useEffect(() => {
fetch('/api/questions')
.then(r => r.json())
.then(data => setQuestions(data))
.catch(error => console.error(error));
}, []);
return <div>{questions.map(q => <div key={q.id}>{q.title}</div>)}</div>;
}
Проблемы:
Правильное место: Custom Hooks
Перерисовка при установке того же значения в useState
Короткий ответ: Нет, перерисовка не происходит, если значение одинаковое. React использует Object.is() для сравнения.
Как работает сравнение в React 18+
const [count, setCount] = useState(0);
// Внутри React
setCount(0); // Object.is(0, 0) === true
// Результат: перерисовка НЕ происходит
setCount(1); // Object.is(0, 1) === false
// Результат: перерисовка ПРОИСХОДИТ
Детальный механизм
React использует Object.is() для сравнения
// Object.is() — это строгое сравнение
Object.is(0, 0); // true
Object.is('hello', 'hello'); // true
Object.is(false, false); // true
// Отличие от ===
Object.is(NaN, NaN); // true (отличие от ===)
Object.is(-0, +0); // false (отличие от ===)
Object.is([], []); // false (разные ссылки)
Примеры
Пример 1: Примитивы (числа, строки)
function Counter() {
const [count, setCount] = useState(0);
Решение проблем без готовых методов
В разработке часто встречаются задачи, для которых нет встроенного метода или готовой библиотеки. Правильный подход требует систематического мышления, а не просто поиска в Google.
Мой процесс (10+ лет опыта)
Если нет готового решения - декомпозируй проблему:
// Задача: Найти все уникальные пары пользователей которые обменивались сообщениями
// Неправильно: "Как это сделать в JavaScript?"
// Правильно: Разбей на части
// 1. Получить все сообщения
// 2. Для каждого сообщения создать пару (от, к)
// 3. Нормализовать пары (сортировать aby их уникальность)
// 4. Удалить дубликаты
// 5. Вернуть результат
Что хранить в state
Нет, не всё. Хранение всего в state — это anti-pattern, который делает приложение медленным и сложным. Нужно критически подходить к тому, что живёт в state.
Правило 80/20
В state хранить только:
НЕ хранить в state:
Примеры НЕправильного подхода
Разница между Events в React и нативном DOM
Это важный вопрос, потому что React имеет свою систему обработки событий, которая отличается от стандартного DOM API. Разработчик должен понимать эти различия для отладки и оптимизации.
Нативный DOM
В стандартном JavaScript события работают так:
// Добавляем слушатель к элементу
const button = document.querySelector('button');
button.addEventListener('click', (event) => {
console.log('Клик!', event);
});
// Удаляем слушатель
button.removeEventListener('click', handleClick);
Фазы события в DOM:
const parent = document.querySelector('.parent');
const child = document.querySelector('.child');
// Фаза захвата
parent.addEventListener('click', () => console.log('Parent capture'), true);
Использование классов в React
В современном React предпочтение отдаётся функциональным компонентам с хуками. Однако класс-компоненты все ещё имеют место в определённых сценариях.
Почему функциональные компоненты - приоритет
В современной разработке функциональные компоненты с хуками - это стандарт. Они:
// Современный подход - функциональный компонент
function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUser(userId).then(setUser).finally(() => setLoading(false));
}, [userId]);
if (loading) return <p>Загрузка...</p>;
return <div>{user?.name}</div>;
}
Сценарии использования класс-компонентов
Благодаря чему появляется ошибка CORS в браузере
CORS (Cross-Origin Resource Sharing) - это механизм безопасности браузера, который контролирует доступ к ресурсам с других доменов. Ошибка CORS появляется, когда браузер блокирует запрос к ресурсу с другого источника.
Что такое Origin (источник)
Origin состоит из трёх частей: схема (protocol) + домен (domain) + порт (port)
https://example.com:443
^ ^ ^
протокол домен порт
Примеры разных Origin-ов:
https://example.com (стандартный)
https://example.com:3000 (другой порт!)
http://example.com (другой протокол!)
https://api.example.com (другой домен!)
https://example.org (совсем другой домен)
Когда появляется ошибка CORS
Same-Origin Policy - браузер разрешает запросы только на тот же Origin.
// Запрос со страницы: https://example.com
// ОК: Same-Origin
fetch('https://example.com/api/users');
UseEffect: синхронно или асинхронно
useEffect выполняется асинхронно, но с важными нюансами. Понимание этого критично для правильной работы с побочными эффектами в React.
Как работает useEffect
Этап 1: Рендер компонента (синхронно)
function MyComponent() {
// Это выполняется синхронно при рендере
const [count, setCount] = useState(0)
console.log('Render: count =', count)
return <div>{count}</div>
}
Этап 2: Обновление DOM (синхронно)
// React обновляет DOM
// Это происходит после рендера, но ДО useEffect
Этап 3: Выполнение useEffect (асинхронно)
function MyComponent() {
const [count, setCount] = useState(0)
// Это выполняется ПОСЛЕ рендера и обновления DOM
useEffect(() => {
console.log('useEffect: count =', count)
}, [count])
return <div>{count}</div>
}
Порядок выполнения
Map: модифицирует или создает новый массив?
Map создает новый массив и НЕ модифицирует исходный. Это критически важное поведение для функционального программирования в JavaScript и одна из основных причин, почему map столь популярен. За 10+ лет разработки я вижу, что правильное понимание этого различия - основа чистого и безошибочного кода.
Основное поведение
Array.prototype.map() создает новый массив:
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(numbers); // [1, 2, 3, 4, 5] (НЕ изменился!)
console.log(doubled === numbers); // false (разные массивы)
Это чистая функция - она не имеет побочных эффектов.
Почему это важно: Immutability (Неизменяемость)
Функциональное программирование предпочитает неизменяемость:
// ❌ Плохо: mutating the array (side effects)
const users = [{id: 1, name: 'Alice'}, {id: 2, name: 'Bob'}];
Умение верстать и создавать разметку
Да, верстка (HTML + CSS) — это фундаментальный навык Frontend Developer. Верстка включает создание структурированной разметки и стилизацию компонентов.
Компетенции в верстке
Верстка начинается с правильной HTML структуры:
<!-- Плохо: не семантично -->
<div class="header">
<div class="logo">Logo</div>
<div class="menu">
<div>Home</div>
<div>About</div>
</div>
</div>
<!-- Хорошо: семантично -->
<header>
<h1>Logo</h1>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>
Семантические теги улучшают SEO, доступность и читаемость кода.
Flexbox — основной инструмент для создания макетов:
Composition API vs Options API в Vue
Vue 3 представил новый подход к написанию компонентов. Composition API похож на React hooks, в то время как Options API — старый синтаксис Vue 2. Оба работают в Vue 3, но имеют разные преимущества.
Options API (старый способ)
export default {
data() {
return {
count: 0,
message: 'Hello'
};
},
computed: {
doubled() {
return this.count * 2;
}
},
methods: {
increment() {
this.count++;
}
},
watch: {
count(newVal) {
console.log('Count changed:', newVal);
}
},
mounted() {
console.log('Component mounted');
}
};
Composition API (новый способ)
import { ref, computed, watch, onMounted } from 'vue';
export default {
setup() {
const count = ref(0);
const message = ref('Hello');
const doubled = computed(() => count.value * 2);
const increment = () => {
count.value++;
};
Для чего нужен Package.json
Package.json — это метаданные и конфигурация Node.js проекта. Это один из самых важных файлов в JavaScript/TypeScript экосистеме. Без него npm/yarn не может управлять зависимостями и скриптами проекта.
Основное назначение
Package.json описывает:
Структура package.json
Для чего нужен UseState?
useState - это хук React, который позволяет добавлять состояние (state) в функциональные компоненты. Это одна из самых важных функций современного React.
Основное назначение
useState позволяет компоненту запомнить данные между рендерами. Каждый раз, когда состояние изменяется, компонент перерендеривается с новыми значениями.
const [count, setCount] = useState(0);
Синтаксис
const [value, setValue] = useState(initialValue);
value - текущее значение состояния setValue - функция для обновления состояния initialValue - начальное значение (опционально)
Простой пример
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Счётчик: {count}</p>
<button onClick={() => setCount(count + 1)}>
Увеличить
</button>
</div>
);
}
Управление различными типами данных
Slice или splice мутирует массив
Ответ: SPLICE мутирует, SLICE не мутирует
Это критически важное различие для понимания работы с массивами в JavaScript:
Сравнение методов
| Метод | Мутирует оригинал | Возвращает | Синтаксис |
|---|---|---|---|
| slice | ❌ Нет | Новый массив | array.slice(start, end) |
| splice | ✅ Да | Удалённые элементы | array.splice(start, deleteCount, ...items) |
slice() — безопасный метод
const original = [1, 2, 3, 4, 5];
// slice() возвращает новый массив
const sliced = original.slice(1, 4);
console.log(sliced); // [2, 3, 4]
console.log(original); // [1, 2, 3, 4, 5] — не изменился!
Зачем нужен state в React?
State (состояние) — это один из ключевых концептов React. Это объект, который содержит данные, изменяемые во время жизни компонента. Понимание state — необходимо для создания интерактивных приложений.
Основное определение
State — это локальные данные компонента, которые контролируют его поведение и внешний вид. Когда state изменяется, React автоматически перерендеривает компонент, отражая эти изменения в интерфейсе.
Зачем нужен state?
1. Управление динамическими данными
State позволяет компонентам хранить и управлять данными, которые меняются со временем:
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Без state каждое нажатие кнопки не повлияло бы на интерфейс.
2. Реактивность пользовательского интерфейса