React Frontend | YeaHub
3.13K subscribers
237 photos
3 videos
1 file
441 links
Теория, подготовка к интервью и курсы для React разработчиков

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

Платформа: https://yeahub.ru

Для связи: @ruslan_kuyanets
Download Telegram
❗️ С чего начать? → Всё самое важное здесь

Guru Frontend at YeaHub | РУСЛАН КУЯНЕЦ 👩‍💻

Всем привет!

Я Founder & Tech Lead at YeaHub — Open Source платформа для IT-специалистов. Основатель Frontend-сообщества Reactify, веду YouTube и Telegram каналы. За 4 года в IT прошел путь от Junior до Senior и создал с нуля проект в роли Tech Lead

Помимо разработки, я ментор по Frontend — помогаю новичкам прокачаться и устроиться в IT.

А ещё я Guru по Frontend в YeaHub: отвечаю за качество контента, а вместе с учениками разрабатываю web версию платформы. Проект Open Source, так что можно заглянуть под капот — код открыт в нашем репозитории.

✈️ Мой блог в Telegram

📹 Мой YouTube канал

А ещё подписывайтесь на другие каналы Экосистемы YeaHub

Проект YeaHub:

🚀 YeaHub | 🖥 GitHub | ✈️ Telegram

🎵 TikTok | 📹 YouTube | 👥 Чат
Please open Telegram to view this post
VIEW IN TELEGRAM
3👍2🔥2😁2
#JavaScript

🚩ES6 модули vs CommonJS: в чем принципиальная разница?

Многие думают, что import/export и require/module.exports — это просто разный синтаксис для одного и того же. На самом деле это кардинально разные подходы.

Главное различие — время загрузки. CommonJS загружает модули во время выполнения кода, то есть динамически. ES6 модули анализируются и загружаются еще до выполнения кода, статически.


Статический анализ ES6 модулей позволяет инструментам сборки (webpack, vite) понимать какой код действительно используется и выбрасывать неиспользуемые части (tree shaking). С CommonJS это невозможно — нужно включать весь модуль целиком.

ES6 модули не позволяют условную загрузку напрямую в коде, зато дают гарантии производительности. CommonJS гибче — можно загружать модули по условию, но теряются оптимизации.


Пример:
// CommonJS - загрузка во время выполнения
if (condition) {
const helper = require('./helper'); // Работает
}

// ES6 - статический импорт (анализируется до выполнения)
import helper from './helper.js'; // Загружается всегда

// ES6 - динамический импорт
if (condition) {
const helper = await import('./helper.js'); // Работает
}


Современные приложения используют ES6 модули для основного кода (лучшая оптимизация) и динамические импорты для условной загрузки. CommonJS остается актуальным для Node.js и legacy проектов.


🔎🔎🔎🔎🔄

#⃣Новости

#⃣База вопросов
Please open Telegram to view this post
VIEW IN TELEGRAM
👍52
#React

🚩Почему React Context может убить производительность вашего приложения?

React Context — это удобно, но при неправильном использовании превращается в "пожирателя производительности".

Главная проблема Context в том, что при изменении его значения перерисовываются ВСЕ компоненты, которые его используют. Даже те, которым эти изменения совершенно не нужны.


Типичная ошибка — создать один "супер-контекст" со всеми данными приложения: пользователь, тема, корзина, уведомления. При добавлении товара в корзину перерисовывается весь интерфейс, включая компоненты, которые показывают только тему оформления.

Правильный подход — разделять Context по доменам. Отдельный контекст для пользователя, отдельный для темы, отдельный для корзины. Тогда изменения в корзине не затронут компоненты темы.


Еще одна проблема — передача нового объекта в value каждый рендер. React считает объекты разными, даже если их содержимое одинаковое, и запускает перерисовку всех потребителей.


Пример:
// Плохо - новый объект каждый рендер
function Provider({children}) {
const [user, setUser] = useState();
return (
<Context.Provider value={{user, setUser}}> // Всегда новый объект!
{children}
</Context.Provider>
);
}

// Хорошо - стабильное значение
function Provider({children}) {
const [user, setUser] = useState();
const value = useMemo(() => ({user, setUser}), [user]);
return (
<Context.Provider value={value}>
{children}
</Context.Provider>
);
}


Используйте Context для редко меняющихся данных (тема, язык, пользователь). Для частых обновлений лучше выбрать специализированные решения вроде Zustand или React Query.


🔎🔎🔎🔎🔄

#⃣Вопрос

#⃣Новости

#⃣База вопросов
Please open Telegram to view this post
VIEW IN TELEGRAM
👍72
#JavaScript

🚩Почему массивы в JavaScript — это не настоящие массивы?

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

JavaScript массивы — это объекты со специальным свойством length и методами для работы с индексами. Под капотом [1, 2, 3] превращается в объект {0: 1, 1: 2, 2: 3, length: 3}. Именно поэтому typeof [] возвращает "object", а не "array".

Эта особенность позволяет делать с массивами то, что невозможно в других языках, но также создает неожиданное поведение.


Примеры странного поведения:
// Можно добавлять произвольные свойства
const arr = [1, 2, 3];
arr.customProperty = 'hello';
arr['weird'] = 'world';

// Можно создавать "дырки" в массиве
const sparse = [];
sparse[100] = 'last';
console.log(sparse.length); // 101
console.log(sparse[50]); // undefined

// Индексы автоматически преобразуются в строки
arr[1] === arr['1']; // true
arr[01] === arr[1]; // true (01 это восьмеричная запись)



📎Ключевые особенности JavaScript массивов:

🟣Индексы всегда строки — даже когда выглядят как числа
🟣Length не всегда равен количеству элементов — может быть больше из-за "дырок"
🟣Могут содержать произвольные свойства — которые не влияют на length
🟣Автоматически расширяются — при присвоении по большому индексу


Понимание этих особенностей критично для избежания багов:

// Плохо - можно случайно создать "дырку"
const items = ['a', 'b', 'c'];
items[items.length + 10] = 'd'; // Создали дырку!
for (let i = 0; i < items.length; i++) {
console.log(items[i]); // Выведет много undefined
}

// Хорошо - используем методы массива
const items = ['a', 'b', 'c'];
items.push('d'); // Безопасное добавление

// Плохо - полагаемся на length для подсчета элементов
const count = arr.length; // Может быть неточно из-за "дырок"

// Хорошо - считаем реальные элементы
const count = arr.filter(item => item !== undefined).length;


Практические последствия:
- for...in итерирует по всем свойствам, включая нечисловые индексы
- Array.from() может создать неожиданно большой массив из sparse array
- JSON.stringify() превращает "дырки" в null



Безопасные практики:
- Используйте методы массива (push, pop, splice) вместо прямого присвоения
- Для итерации используйте for...of или методы типа forEach
- Проверяйте элементы на undefined при работе с внешними данными


Избегайте:
- Присвоения по произвольным индексам
- Добавления нечисловых свойств к массивам
- Полагания на length как индикатор количества элементов

JavaScript массивы гибкие, но эта гибкость требует понимания их истинной природы. Помните: это объекты, притворяющиеся массивами!


🔎🔎🔎🔎🔄

#⃣Вопрос

#⃣Новости

#⃣База вопросов
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍85🔥4
#TypeScript

🚩Почему any в TypeScript хуже, чем отсутствие типов?

Многие разработчики считают any безобидной "заглушкой" для сложных типов. На самом деле any не просто отключает проверку типов — он активно их ломает, создавая ложное чувство безопасности и распространяя "заражение" по всему коду.

TypeScript с any хуже обычного JavaScript, потому что создает иллюзию типизации. В JavaScript вы знаете, что типов нет, и пишете соответствующие проверки. С any вы думаете, что защищены, но на самом деле беззащитны.

Any — это не "я разберусь с типом позже", это "я сломал систему типов для всех, кто использует этот код". Проблема в том, что any заразен — он распространяется на все взаимодействующие с ним типы.

Как any ломает типизацию:
// Плохо - any распространяется как вирус
function processData(data: any) {
return data.someProperty; // Тип результата тоже any!
}

const result = processData(userData); // result: any
const value = result.unknownMethod(); // value: any, ошибка в runtime!

// Хорошо - используйте unknown для неизвестных типов
function processData(data: unknown) {
// Принуждает к проверке типа
if (typeof data === 'object' && data !== null && 'someProperty' in data) {
return (data as {someProperty: string}).someProperty;
}
throw new Error('Invalid data format');
}



📎Проблемы any в типизации:

🟣Заражение типов — все взаимодействующие типы становятся any
🟣Отсутствие автодополнения — IDE не может предложить методы и свойства
🟣Скрытие ошибок — TypeScript не покажет проблемы до runtime
🟣Ложная безопасность — код выглядит типобезопасным, но не является таковым


Когда любой тип лучше any:
unknown — для данных с неизвестной структурой
object — для объектов без конкретной структуры
Record<string, unknown> — для словарей с неизвестными значениями
Дженерики — для переиспользуемых функций
Union типы — когда знаете возможные варианты


Безопасные практики:
- Включите strict режим и noImplicitAny в tsconfig
- Используйте unknown вместо any для внешних данных
- Создавайте type guards для проверки структуры объектов
- Применяйте утилитарные типы (Partial, Pick, Omit)

Избегайте:
- Использования any как "быстрого решения"
- Передачи any в качестве параметров функций
- Касты через any без веских причин

Any в TypeScript — это не инструмент разработки, это вредитель. Каждый any делает ваш код менее надежным и сложнее в поддержке!


🔎🔎🔎🔎🔄

#⃣Вопрос

#⃣Новости

#⃣База вопросов
Please open Telegram to view this post
VIEW IN TELEGRAM
👍83
#React

🚩Почему key={index} в React — это не просто warning, а реальная проблема?

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

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

Классический пример проблемы:
// Плохо - key={index} ломает логику
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo, index) => (
<TodoItem
key={index} // Индекс как ключ!
todo={todo}
onComplete={() => completeTodo(todo.id)}
/>
))}
</ul>
);
}

// При удалении первого элемента:
// Было: [0: "Купить молоко", 1: "Погулять с собакой", 2: "Написать код"]
// Стало: [0: "Погулять с собакой", 1: "Написать код"]
// React думает, что изменился последний элемент, а не первый!

// Хорошо - уникальный и стабильный ключ
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo) => (
<TodoItem
key={todo.id} // Стабильный уникальный ключ
todo={todo}
onComplete={() => completeTodo(todo.id)}
/>
))}
</ul>
);
}


📎Проблемы key={index}:

🟣Потеря состояния компонентов — фокус, введенный текст, анимации
🟣Неправильные обновления — React обновляет не те элементы
🟣Проблемы с производительностью — лишние перерендеры и создания компонентов
🟣Баги в формах — значения полей "перепрыгивают" между элементами


Когда index может быть приемлемым:
Статический список — никогда не изменяется порядок
Без состояния — компоненты не хранят внутреннее состояние
Простой вывод — только отображение данных без интерактивности
Пагинация — каждая страница загружается заново


Правильные практики:
- Используйте уникальные и стабильные идентификаторы
- Ключ должен быть одинаковым для одного элемента между рендерами
- Избегайте генерации ключей внутри render функции
- Тестируйте изменение порядка элементов

Избегайте:
- key={index} для динамических списков
- Случайных ключей (Math.random(), Date.now() в рендере)
- Одинаковых ключей для разных элементов



🔎🔎🔎🔎🔄

#⃣Вопрос

#⃣Новости

#⃣База вопросов
Please open Telegram to view this post
VIEW IN TELEGRAM
👍82
#JavaScript

🚩Почему замыкания в JavaScript создают утечки памяти и как этого избежать?

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

Это создает скрытые утечки памяти, которые накапливаются со временем и замедляют приложение.


📎Основные источники утечек через замыкания:

🟣Обработчики событий — накапливаются при каждом создании компонента
🟣Таймеры и интервалы — продолжают держать ссылки после очистки
🟣Кеширование функций — сохраняют контекст навсегда
🟣React useCallback — может держать устаревшие зависимости


Частые ошибки в React компонентах:
//  ПЛОХО - утечка памяти
function BadComponent() {
const [name, setName] = useState('');
const [bigData] = useState(new Array(100000).fill('данные')); // 100к элементов

// Замыкание захватывает ВСЁ - включая bigData, хотя не использует его!
const handleClick = useCallback(() => {
console.log(`Привет, ${name}!`); // Использует только name
}, [name]); // bigData НЕ в зависимостях, но ВСЁ РАВНО захвачен замыканием!

return <button onClick={handleClick}>Нажми</button>;
}

// ХОРОШО - без утечки
function GoodComponent() {
const [name, setName] = useState('');
// bigData вынесен за пределы компонента или в отдельный хук

const handleClick = useCallback(() => {
console.log(`Привет, ${name}!`);
}, [name]); // Захватывает только легкую строку name

return <button onClick={handleClick}>Нажми</button>;
}

// Проблема: замыкание в useCallback "видит" ВСЕ переменные из области видимости
// и держит на них ссылки, даже если функция их не использует!



Безопасные практики:
- Выносите тяжелые вычисления за пределы замыканий
- Обнуляйте ссылки на большие объекты после использования
- Используйте WeakMap для кеширования с автоочисткой
- Регулярно профилируйте память в DevTools

Избегайте:
- Захвата больших объектов в замыкания без необходимости
- Бесконечно живущих замыканий с большим контекстом
- Кеширования замыканий без стратегии очистки


🔎🔎🔎🔎🔄

#⃣Вопрос

#⃣Новости

#⃣База вопросов
Please open Telegram to view this post
VIEW IN TELEGRAM
👍92🏆2
#JavaScript

🚩Почему setTimeout(fn, 0) не выполняется мгновенно и что такое Event Loop на самом деле?

Многие думают, что JavaScript — однопоточный язык, где код выполняется строка за строкой. На самом деле это лишь половина правды. JavaScript действительно однопоточный — у него один Call Stack для выполнения кода. Но браузер предоставляет дополнительные возможности: Web APIs (setTimeout, fetch, DOM events), которые работают параллельно с основным потоком.

Event Loop — это механизм, который решает, когда и в каком порядке выполнять асинхронные задачи.


🖇 Как работает Event Loop:

- Call Stack — выполняет синхронный код по принципу LIFO
- Web APIs — обрабатывают асинхронные операции (таймеры, HTTP запросы)
- Task Queue — очередь для колбэков из Web APIs
- Microtask Queue — приоритетная очередь для промисов и queueMicrotask


Практический пример неожиданного поведения:
console.log('1'); // Синхронно

setTimeout(() => console.log('2'), 0); // Макрозадача

Promise.resolve().then(() => console.log('3')); // Микрозадача

console.log('4'); // Синхронно

// Результат: 1, 4, 3, 2
// НЕ 1, 2, 3, 4 как многие ожидают!


Почему именно такой порядок?
1. Выполняется весь синхронный код (1, 4)
2. Call Stack пустой — Event Loop проверяет очереди
3. Микрозадачи имеют приоритет над макрозадачами (3)
4. Только потом выполняются макрозадачи (2)


🖇 Приоритеты Event Loop (от высшего к низшему):
1. Синхронный код в Call Stack
2. Microtask Queue (промисы, queueMicrotask)
3. Task Queue (setTimeout, setInterval, DOM события)
4. Render Queue (перерисовка браузера)


Частые ошибки в понимании:
//  ПЛОХО - блокировка интерфейса
for (let i = 0; i < 1000000; i++) {
// Синхронный код блокирует Event Loop
heavyCalculation();
}

// ХОРОШО - не блокируем Event Loop
function processChunk(data, chunkSize = 1000) {
const chunk = data.splice(0, chunkSize);

chunk.forEach(item => heavyCalculation(item));

if (data.length > 0) {
setTimeout(() => processChunk(data, chunkSize), 0); // Даем Event Loop передохнуть
}
}



🖇 Проблемы с неправильным пониманием Event Loop:
- Интерфейс "зависает" из-за долгих синхронных операций
- Неправильный порядок выполнения асинхронных задач
- Race conditions в сложных сценариях
- Проблемы с анимациями и пользовательским опытом


Практические правила:
- Разбивайте тяжелые вычисления на части через setTimeout
- Используйте requestAnimationFrame для анимаций
- Помните о приоритете микрозадач при отладке
- Не блокируйте Event Loop долгими синхронными операциями


Избегайте:
- Тяжелых синхронных циклов без перерывов
- Предположений о том, что setTimeout(fn, 0) выполнится мгновенно
- Игнорирования разницы между микро- и макрозадачами

Event Loop — это сердце JavaScript. Понимание его работы критично для создания отзывчивых приложений


🔎🔎🔎🔎🔄

#⃣Вопрос

#⃣Новости

#⃣База вопросов
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🔥21
#JavaScript

🚩Почему var "всплывает", а let/const бросают ошибки? Разбираемся с hoisting и Temporal Dead Zone

Многие разработчики думают, что hoisting — это просто "поднятие переменных наверх". На самом деле это результат двухфазной обработки кода JavaScript движком.

Сначала JavaScript сканирует весь код и создает переменные в памяти (compilation phase), а потом уже выполняет код и присваивает значения (execution phase). Все объявления "всплывают", но инициализируются по-разному.

Var всплывает и сразу инициализируется как undefined. Let и const тоже всплывают, но попадают в "мертвую зону" (Temporal Dead Zone) — они существуют в памяти, но обращение к ним до инициализации вызывает ошибку.


Пример, который ломает мозг:

// Что выведет этот код?
console.log(typeof myVar); // "undefined"
console.log(myVar); // undefined

if (false) {
var myVar = 'never executed';
}

// Var всплывает и инициализируется undefined,
// даже если код в блоке никогда не выполнится!

// А теперь с let:
console.log(typeof myLet); // ReferenceError!
if (false) {
let myLet = 'never executed';
}


Именно поэтому современный JavaScript рекомендует let/const — они защищают от случайного использования переменных до их объявления, превращая потенциальные баги в явные ошибки.


🔎🔎🔎🔎🔄

#⃣Вопрос

#⃣Новости

#⃣База вопросов
Please open Telegram to view this post
VIEW IN TELEGRAM
👍112
#React

🚩Почему useEffect с пустым массивом зависимостей — это не componentDidMount?

Многие React разработчики думают, что useEffect(() => {}, []) — это прямой аналог componentDidMount из классовых компонентов. На самом деле это опасное заблуждение, которое приводит к тонким багам и проблемам с производительностью.

Главное отличие в том, что useEffect выполняется ПОСЛЕ рендера и обновления DOM, а componentDidMount — сразу после монтирования. Но есть гораздо более серьезная проблема: в React 18+ с Strict Mode компоненты могут размонтироваться и смонтироваться заново для проверки корректности cleanup функций.

Это значит, что ваш "одноразовый" useEffect может выполниться дважды!


Типичная ошибка — думать, что код выполнится только один раз:
// Плохо - может выполниться дважды в development!
useEffect(() => {
fetchUserData(); // Запрос может дублироваться
initializeAnalytics(); // Аналитика инициализируется дважды
startWebSocket(); // Создается два подключения!
}, []); // Пустой массив НЕ гарантирует одноразовое выполнение


Правильный подход — всегда писать cleanup функции:
// Хорошо - учитываем возможное повторное выполнение
useEffect(() => {
let cancelled = false;

const fetchData = async () => {
const data = await fetchUserData();
if (!cancelled) {
setUser(data);
}
};

fetchData();

return () => {
cancelled = true; // Cleanup предотвращает обновление состояния
};
}, []);



📎Ключевые отличия от componentDidMount:

🟣Время выполнения — после обновления DOM, не сразу после монтирования
🟣Strict Mode — может выполняться дважды в development режиме
🟣Cleanup обязателен — React ожидает возможность "отменить" эффект
🟣Concurrent Features — в будущих версиях React эффекты могут прерываться

Еще одна проблема — попытка имитировать componentWillUnmount через cleanup функцию. Cleanup вызывается не только при размонтировании, но и перед каждым повторным выполнением эффекта.

// Неправильное понимание cleanup
useEffect(() => {
const timer = setInterval(tick, 1000);

return () => {
clearInterval(timer); // Вызывается НЕ только при размонтировании!
};
}, [someValue]); // При изменении someValue cleanup тоже сработает



Безопасные практики:
- Всегда пишите cleanup функции для асинхронных операций
- Тестируйте компоненты с включенным Strict Mode
- Используйте флаги отмены для предотвращения race conditions
- Помните: cleanup ≠ componentWillUnmount

Избегайте:
- Предположений о единоразовом выполнении useEffect
- Побочных эффектов без возможности их отмены
- Прямого сравнения с методами жизненного цикла классов


🔎🔎🔎🔎🔄

#⃣Вопрос

#⃣Новости

#⃣База вопросов
Please open Telegram to view this post
VIEW IN TELEGRAM
👍71
#CSS

🚩Почему margin: auto не центрирует элемент, а иногда центрирует?

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

Margin: auto работает только тогда, когда браузер может вычислить "лишнее" пространство. Для блочных элементов это работает горизонтально (если есть ширина), но НЕ работает вертикально. Для инлайн элементов не работает вообще.


Типичные ошибки с margin: auto:
/* Плохо - не сработает */
.center-me {
margin: auto; /* Вертикально НЕ центрирует! */
width: 300px;
height: 200px;
}

/* Плохо - инлайн элементы игнорируют margin: auto */
span {
margin: auto; /* Бесполезно для инлайн */
}


Когда margin: auto действительно работает:

/* Хорошо - центрирование блока горизонтально */
.container {
width: 300px; /* Обязательно указать ширину! */
margin: 0 auto; /* Только горизонтально */
}

/* Современное решение - используйте flexbox */
.parent {
display: flex;
justify-content: center; /* Горизонтально */
align-items: center; /* Вертикально */
}



📎Почему margin: auto такой "капризный":

🟣Нужна явная ширина — без width браузер не знает, что центрировать
🟣Работает только горизонтально — вертикальное центрирование требует других методов
🟣Не работает с float — плавающие элементы выходят из нормального потока
🟣Игнорируется инлайн элементами — нужно сделать display: block


Современные альтернативы намного надежнее:
- Flexbox для одномерного центрирования
- Grid для двумерного позиционирования
- Position + transform для абсолютного центрирования


Современный подход:
- Используйте flexbox/grid вместо margin: auto для центрирования
- Margin: auto оставьте для горизонтального центрирования блоков
- Изучите современные методы layout вместо старых трюков


Избегайте:
- Попыток вертикального центрирования через margin: auto
- Использования без явной ширины элемента
- Применения к инлайн элементам


🔎🔎🔎🔎🔄

#⃣Вопрос

#⃣Новости

#⃣База вопросов
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍101
#TypeScript

🚩Почему interface и type в TypeScript НЕ взаимозаменяемы?

Ключевое различие: interface предназначен для описания структуры объектов и может быть расширен другими разработчиками. Type — это алиас для любого типа, он закрыт для расширения после объявления.


Типичная ошибка — использовать type для публичных API:
// Плохо - закрытый тип, нельзя расширить
export type UserConfig = {
theme: string;
language: string;
};

// Пользователи НЕ могут добавить свои поля!
// UserConfig & { customField: string } - приходится создавать новый тип


Правильный подход — interface для расширяемых структур:
// Хорошо - открытый для расширения
export interface UserConfig {
theme: string;
language: string;
}

// Пользователи могут легко расширять
declare module 'your-lib' {
interface UserConfig {
customField: string; // Declaration merging!
}
}



📎Когда использовать каждый:

🟣Interface — объекты, классы, расширяемые API, библиотеки
🟣Type — union типы, primitive алиасы, computed типы, закрытые структуры

Уникальные возможности type:
// Только type может делать union типы
type Status = 'loading' | 'success' | 'error';

// Computed типы на основе других
type UserKeys = keyof User; // 'name' | 'email' | 'id'

// Условные типы
type NonNullable<T> = T extends null | undefined ? never : T;


Уникальные возможности interface:
// Declaration merging - объединение объявлений
interface Window {
myGlobalVar: string;
}

interface Window {
anotherVar: number; // Автоматически объединится!
}

// Расширение через extends (более читаемо чем &)
interface AdminUser extends User {
permissions: string[];
}



Практические правила:
- Interface для объектов и расширяемых API
- Type для union типов и алиасов
- Interface для библиотек (позволяет пользователям расширять)
- Type для внутренней логики приложения


Избегайте:
- Type для публичных API, которые могут расширяться
- Interface для простых алиасов примитивов
- Смешивания подходов в одном проекте без правил


🔎🔎🔎🔎🔄

#⃣Вопрос

#⃣Новости

#⃣База вопросов
Please open Telegram to view this post
VIEW IN TELEGRAM
👍105
#React

🚩Почему useState с объектами и массивами ломает React и как это исправить?

React использует Object.is() для сравнения состояния, а не глубокое сравнение. Когда вы мутируете объект или массив напрямую, ссылка остается той же, и React думает, что ничего не изменилось. Компонент не перерендеривается, UI не обновляется.

Это создает "призрачные баги" — состояние в DevTools показывает новые данные, но интерфейс отображает старые.


Типичные ошибки с мутациями:
// Плохо - мутация массива
const [items, setItems] = useState([1, 2, 3]);

const addItem = () => {
items.push(4); // Мутация!
setItems(items); // React не увидит изменений
};

// Плохо - мутация объекта
const [user, setUser] = useState({name: 'John', age: 25});

const updateAge = () => {
user.age = 26; // Мутация!
setUser(user); // Ссылка та же, React не обновится
};


Правильный подход — иммутабельные обновления:
// Хорошо - новый массив
const addItem = () => {
setItems([...items, 4]); // Новая ссылка
};

const removeItem = (index) => {
setItems(items.filter((_, i) => i !== index));
};

// Хорошо - новый объект
const updateAge = () => {
setUser({...user, age: 26}); // Новая ссылка
};

// Для сложных объектов используйте immer
const updateUser = () => {
setUser(produce(user, draft => {
draft.profile.settings.theme = 'dark';
}));
};



📎 Проблемы мутаций в React:

🟣Пропущенные рендеры — UI не синхронизирован с состоянием
🟣Баги в useEffect — зависимости не отслеживают мутации
🟣Проблемы с memo — оптимизация не работает
🟣Race conditions — асинхронные обновления перезаписывают изменения


Особенно опасны мутации в useEffect:
// Плохо - бесконечный цикл или пропущенные обновления
useEffect(() => {
data.processed = true; // Мутация в зависимости!
setData(data);
}, [data]); // data ссылка не изменилась, эффект может не сработать



Инструменты для иммутабельности:
- Spread оператор для простых случаев
- Immer для сложных структур
- Ramda/Lodash fp для функционального подхода
- useImmer хук для удобства


Избегайте:
- Прямых мутаций state объектов и массивов
- Методов типа push(), pop(), sort() на state массивах
- Изменения вложенных свойств без создания новых объектов


🔎🔎🔎🔎🔄

#⃣Вопрос

#⃣Новости

#⃣База вопросов
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12🔥32
#JavaScript

🚩Почему == в JavaScript сравнивает не то, что вы думаете?

JavaScript использует алгоритм Type Coercion с 11 шагами, который может давать совершенно неожиданные результаты. Проблема не просто в том, что [ ] == false (что логично), а в том, что [ ] == ![ ] возвращает true. То есть массив равен своему отрицанию!


Примеры, которые сломают мозг:
// Логика JavaScript'а
[] == false // true (массив пустой, false пустой)
[] == ![] // true (массив равен собственному отрицанию!)
[] == 0 // true (массив преобразуется к 0)
'0' == false // true (строка '0' становится числом 0)

// Но внимание:
false == 'false' // false (!)
false == undefined // false
false == null // false

// И совсем сумасшествие:
null == 0 // false
null >= 0 // true (!)
null > 0 // false

// undefined ведет себя по-другому:
undefined == 0 // false
undefined >= 0 // false
undefined == null // true


Безопасная альтернатива — строгое сравнение:
// Хорошо - предсказуемое поведение
[] === false // false
'0' === 0 // false
null === 0 // false
undefined === 0 // false

// Для проверки на null/undefined используйте явные проверки:
if (value != null) { // Единственный полезный случай для ==
// value не null и не undefined
}

// Или еще лучше:
if (value !== null && value !== undefined) {
// Явно и понятно
}



📎Почему == такой непредсказуемый:

🟣Асимметричность — x == y может отличаться от y == x
🟣Контекстная зависимость — результат зависит от порядка операндов
🟣Множественные преобразования — один операнд может преобразовываться несколько раз
🟣Исключения из правил — null и undefined имеют особое поведение

Алгоритм Type Coercion настолько сложен, что даже опытные разработчики не могут предсказать результат без документации. Он включает преобразования к примитивам, числам, строкам, с особыми правилами для каждого типа.


Современные практики:
- Всегда используйте === и !== для сравнений
- Единственное исключение: value != null для проверки на null/undefined
- Используйте TypeScript для предотвращения случайных сравнений разных типов
- Применяйте ESLint правило eqeqeq для автоматического контроля


Никогда не используйте == когда:
- Сравниваете с boolean (используйте !!value)
- Работаете с числами и строками одновременно
- Не уверены в типах операндов


🔎🔎🔎🔎🔄

#⃣Новости

#⃣База вопросов
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥21
🖥 Ты не сможешь решить эти Event loop задачки

На собеседованиях часто просят определить порядок вывода в консоль — это типичные задачи на понимание работы Event Loop в JavaScript. Их нужно не просто уметь решать, но и грамотно объяснять, почему вывод именно такой.

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

Кстати, у меня есть видео, где я подробно разбираю 5 разных задач, показывая на схеме каждый шаг:
— что куда попадает
— в какую очередь отправляется
— в какой момент выполняется

Рекомендую посмотреть, особенно если хочешь уверенно чувствовать себя на техническом собеседовании.

📹 Разбор задач по Event Loop с собеседований


console.log('Start');

Promise.resolve()
.then(() => {
console.log('Promise 1');
Promise.resolve()
.then(() => console.log('Nested Promise'));
})
.then(() => console.log('Promise 2'));

console.log('End');



console.log('Start');

setTimeout(() => console.log('Timeout 1'), 0);

Promise.resolve()
.then(() => {
console.log('Promise 1');
setTimeout(() => {
console.log('Timeout 2');
Promise.resolve().then(() => console.log('Promise inside Timeout'));
}, 0);
});

setTimeout(() => console.log('Timeout 3'), 0);

console.log('End');



Promise.resolve('Data')
.then(res => { throw new Error('Problem'); })
.catch(err => {
console.log('Caught:', err.message);
return 'Recovered';
})
.finally(() => console.log('Cleanup'))
.then(res => console.log('Final:', res));
Please open Telegram to view this post
VIEW IN TELEGRAM
👍62🔥2
#JavaScript

🚩Почему Promise.all() может убить ваше приложение одной неудачной операцией?

Представьте: вы загружаете профили 100 пользователей. 99 запросов успешны, но один пользователь удалил аккаунт. Promise.all() отбросит ВСЕ результаты и вернет только ошибку. Вы потеряете 99 валидных результатов из-за одной неудачи!


Типичная ошибка — использовать Promise.all() для независимых операций:
// Плохо - одна ошибка убивает всё
const users = await Promise.all([
fetchUser(1), // успех
fetchUser(2), // успех
fetchUser(3), // 404 ошибка
fetchUser(4), // успех
]);
// Результат: только ошибка, успешные результаты потеряны!


Правильный подход — Promise.allSettled() для независимых операций:
// Хорошо - получаем все результаты
const results = await Promise.allSettled([
fetchUser(1),
fetchUser(2),
fetchUser(3),
fetchUser(4),
]);

const users = results
.filter(result => result.status === 'fulfilled')
.map(result => result.value);
// Получили 3 из 4 пользователей вместо 0!



📎Когда использовать каждый метод:

🟣Promise.all() — когда ВСЕ операции критичны (оплата + списание + логирование)
🟣Promise.allSettled() — когда операции независимы (загрузка данных, аналитика)
🟣Promise.race() — когда нужен первый результат (timeout, fallback)

Еще одна проблема — Promise.all() не отменяет остальные промисы при ошибке. Они продолжают выполняться в фоне, тратя ресурсы и потенциально вызывая побочные эффекты.


Безопасные практики:
- Используйте Promise.allSettled() для независимых операций
- Promise.all() только когда все операции действительно критичны
- Добавляйте timeout для защиты от зависших промисов
- Логируйте частичные неудачи для мониторинга


Избегайте:
- Promise.all() для операций, которые могут независимо падать
- Игнорирования частичных результатов
- Отсутствия обработки ошибок в критичных операциях


🔎🔎🔎🔎🔄

#⃣Вопрос

#⃣Новости

#⃣База вопросов
Please open Telegram to view this post
VIEW IN TELEGRAM
👍111
🖥 Middle Frontend разработчик в компанию Ай-Теко

Техническое собеседование. ЗП: от 200к. Май 2025. Проект: Сбер. Опыт: 4 года. Задачи: Отсортировать список нечетных чисел, валидность скобок.

Вопросы:
- Перечислите основные методы массивов
- Какие есть способы создания объектов и в чем их отличия?
- Что такое Promise?
- Расскажите про все методы Promise?
- Расскажите про this и контекст?

Все 14 вопросов можно посмотреть на нашей платформе

#собес
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🔥21
#JavaScript

🚩Почему closures в JavaScript создают утечки памяти и как их избежать?

Замыкания (closures) — одна из самых мощных фич JavaScript, но они могут незаметно "съедать" память вашего приложения. Проблема в том, что замыкание сохраняет ВСЮ лексическую область видимости, даже если использует только одну переменную из нее.

Когда внешняя функция завершается, все ее переменные должны быть удалены сборщиком мусора. Но если есть замыкание — переменные остаются в памяти навсегда!

Типичная ошибка — случайные замыкания в циклах и обработчиках:
// Плохо - каждый обработчик держит ссылку на огромный объект
function setupEventListeners(massiveDataArray) {
const hugeObject = { data: massiveDataArray }; // 100MB данных

for (let i = 0; i < 1000; i++) {
document.getElementById(`btn-${i}`).addEventListener('click', () => {
console.log(`Button ${i} clicked`);
// Замыкание держит ВЕСЬ hugeObject в памяти!
});
}
} // hugeObject никогда не будет удален из памяти
Правильный подход — минимизация замыканий:
javascript// Хорошо - передаем только нужные данные
function setupEventListeners(massiveDataArray) {
for (let i = 0; i < 1000; i++) {
const buttonId = i; // Копируем только нужное значение

document.getElementById(`btn-${i}`).addEventListener('click', () => {
console.log(`Button ${buttonId} clicked`);
// Замыкание держит только buttonId, не весь scope
});
}
}

// Еще лучше - вынести функцию наружу
function createClickHandler(buttonId) {
return () => console.log(`Button ${buttonId} clicked`);
}


📎Опасные места для утечек памяти:

🟣Event listeners — особенно в циклах и с внешними переменными
🟣Timers и intervals — setInterval с замыканием на большие объекты
🟣React useCallback — неправильные зависимости кэшируют старые данные
🟣Node.js серверы — замыкания в обработчиках запросов

Особенно опасны замыкания в React:
// Плохо - компонент держит все предыдущие состояния
const MyComponent = () => {
const [count, setCount] = useState(0);
const [heavyData, setHeavyData] = useState(null);

const handleClick = useCallback(() => {
// Замыкание на старое значение heavyData!
console.log(heavyData?.length);
setCount(count + 1);
}, []); // Пустые зависимости = замыкание на первый рендер

return <button onClick={handleClick}>Click</button>;
};


Безопасные практики:
Используйте правильные зависимости в useCallback/useMemo
Очищайте event listeners при размонтировании
Выносите функции наружу, если они не нуждаются в замыкании
Используйте WeakMap/WeakSet для слабых ссылок


Избегайте:
Замыканий на большие объекты без необходимости
Пустых массивов зависимостей в React хуках
Забывать удалять обработчики событий



🔎🔎🔎🔎🔄

#⃣Вопрос

#⃣Новости

#⃣База вопросов
Please open Telegram to view this post
VIEW IN TELEGRAM
👍71