True Frontender
1.02K subscribers
143 photos
7 videos
89 links
Сборная солянка про фронтенд.

JavaScript, React, TypeScript, HTML, CSS — здесь обсуждаем всё, что связано с веб-разработкой!

Связь: @pmowq
Download Telegram
Привет! Начнем неделю с задачи которой со мной поделился коллега.

Задача:
Написать порядок вывода в консоль и объяснить.

Решение:
Всего у нас будет 2 рендера, так как в коде есть useEffect, который меняет состояние после первого рендера.

1. App — компонент рендерится, выводится лог.
2. useLayoutEffect — этот эффект срабатывает синхронно после обновления DOM.
3. useEffect — эффект после отрисовки интерфейса.
4. App — повторный рендер компонента из-за обновления состояния.
5. useEffect cleanup — очистка эффекта useEffect с предыдущего рендера.
6. useLayoutEffect cleanup — очистка эффекта useLayoutEffect с предыдущего рендера.
7. useLayoutEffect — повторное выполнение синхронного эффекта с обновлённым состоянием.
8. useEffect — повторное выполнение асинхронного эффекта с обновлённым состоянием.

Такой порядок из-за работы React:
- useLayoutEffect вызывается после обновления DOM, но до того, как браузер нарисует изменения на экране.

- useEffect выполняется после отрисовки.

- Функции очистки вызываются перед повторным выполнением эффекта или при размонтировании компонента.

#react #interview
👏13👍6🔥1
На фронтенде производительность — ключевой фактор для хорошего пользовательского опыта. Чем быстрее загружается и работает приложение, тем лучше.
Одним из инструментов оптимизации является динамический импорт. Он позволяет загружать модули только тогда, когда они действительно нужны.

Как это?
Вместо статического импорта:

import { heavyFunc } from './heavyModule.js';

button.addEventListener('click', () => {
heavyFunc();
});


Используем динамический:

button.addEventListener('click', async () => {
const module = await import('./heavyModule.js');
module.heavyFunc();
});


Здесь модуль heavyModule.js загрузится только после клика по кнопке, а не сразу при загрузке страницы.

Зачем?
- Ускоряет загрузку.
- Уменьшает размер основного бандла.

Импорт в React
В React для динамической загрузки компонентов есть React.lazy.

import React, { Suspense } from 'react';

const LazyComponent = React.lazy(() => import('./MyComponent'));

function App() {
return (
<Suspense fallback={<div>Загрузка...</div>}>
<LazyComponent />
</Suspense>
);
}


Здесь MyComponent загрузится только при первом рендере LazyComponent.


Когда стоит использовать?
Например, если в приложении есть библиотека для работы с PDF, которую что-то делает по клику на кнопку, то нет смысла грузить её сразу. Такие библиотеки обычно тяжёлые, и динамическая загрузка позволяет сэкономить время и ресурсы.
Не забывайте, что никто не любит ждать. Если ваш сайт долго загружается, пользователь просто уйдёт к конкурентам

#BestPractices #react #JavaScript
🔥73👍3
Многие из нас сталкиваются с проблемой, когда нужно сделать сразу несколько запросов. Часто такие запросы выполняются по цепочке, хотя на самом деле это не всегда нужно. Если запросы не зависят от ответа предыдущего, ждать их друг за другом бессмысленно. В таких случаях запросы лучше выполнять параллельно, чтобы ускорить работу и улучшить пользовательский опыт.

Пример работающего, но проблемного кода:

async function getData() {
try {
const user = await fetch('/user');
const posts = await fetch('/posts');
const comments = await fetch('/comments');

console.log('Все данные получены');
} catch (error) {
console.error('Ошибка:', error);
}
}



Здесь каждый запрос дожидается ответа от предыдущего. Время выполнения функции — сумма времени всех запросов.

Как можно улучшить?
Проблема решается с помощью Promise.all. Это функция, которая принимает массив промисов и позволяет запустить их одновременно. Она возвращает новый промис, который завершится успешно, когда все переданные промисы завершатся, или упадёт, если хотя бы один промис вернёт ошибку.

Более правильная реализация:

async function getData() {
try {
const userPromise = fetch('/user');
const postsPromise = fetch('/posts');
const commentsPromise = fetch('/comments');

const [user, posts, comments] = await Promise.all([
userPromise,
postsPromise,
commentsPromise
]);

console.log('Все данные получены');
} catch (error) {
console.error('Ошибка:', error);
}
}

Теперь все запросы отправляются одновременно, и время выполнения функции — это время самого долгого запроса. Такой подход значительно ускоряет загрузку.

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


Помните, что при разработке важно всегда думать о пользователе и его удобстве. Ускорение загрузки и отзывчивость интерфейса делают продукт лучше 🙌

#JavaScript #BestPractices
👍11🔥4
В одном из предыдущих постов мы кратко познакомились с SOLID. Сегодня начнем серию постов и разберём первую букву — S. Эта буква расшифровывается как Single Responsibility Principle или принцип единственной ответственности.
Этот принцип говорит, что каждый модуль или класс должен отвечать только за одну задачу.

Пример плохого подхода:

class User {
constructor(name, email) {
this.name = name;
this.email = email;
}

save() {
// сохраняет пользователя в базу
}

sendEmail(message) {
// отправляет письмо пользователю
}
}

Класс User хранит данные, отвечает за сохранение и за отправку почты. Такой код сложнее расширять, тестировать и в нём выше риск случайно что-то сломать при изменениях.

Как улучшить?
Разделять и властвовать

class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
}

class UserRepository {
save(user) {
// логика сохранения в базу
}
}

class EmailService {
sendEmail(user, message) {
// логика отправки письма
}
}


Мы разделяем обязанности и теперь каждый класс отвечает за свою конкретную задачу. Такой код будет в разы проще тестровать и поддерживать в будущем)

Надеюсь, что этот пост понравился и можно продолжать. Ставь лайк 😌

#BestPractices #JavaScript
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🔥4
В прошлом посте мы разобрали Promise.all, а сегодня разберём Promise.allSettled. В конце прошлого поста затронули проблему, что если один из промисов падает, то Promise.all сразу выдаёт ошибку и игнорирует остальные.
Promise.allSettled работает иначе. Он ждёт, пока завершатся все промисы, и возвращает массив результатов по каждому из них. Неважно, завершился он успешно или с ошибкой.

Что это за метод?
Метод Promise.allSettled возвращает промис, который ожидает завершения всех переданных промисов, вне зависимости от того, успешно они выполнились или нет.
После этого он возвращает массив объектов, каждый из которых содержит статус выполнения соответствующего промиса и его результат или причину ошибки.

Пример:

async function getData() {
try {
const userPromise = fetch('/user');
const postsPromise = fetch('/posts');
const commentsPromise = fetch('/comments');

const results = await Promise.allSettled([
userPromise,
postsPromise,
commentsPromise
]);

results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Запрос ${index + 1} выполнен`, result.value);
} else {
console.warn(`Запрос ${index + 1} упал`, result.reason);
}
});
} catch (error) {
console.error('Ошибка:', error);
}
}


Теперь даже если один из запросов упал, остальные продолжат работать.

Результат выполнения
Если промис выполнился:

{
status: "fulfilled",
value: ... // значение, которое вернул промис
}


Если промис отклонился:

{
status: "rejected",
reason: ... // ошибка
}



Выбор метода всегда остаётся за вами и зависит от конкретной задачи) В будущем разберем другие методы и некоторые задачи по промисам.

#JavaScript
🔥9👍4
Всем привет!
Ухожу в мини-отпуск 🏝, но постараюсь не исчезать и выкладывать посты)

Всем хороших выходных 😘
Please open Telegram to view this post
VIEW IN TELEGRAM
14🔥54
В прошлый раз мы познакомились с методом Promise.allSettled, а сегодня разберём задачу с собеседования с реализацией кастомного allSettled.

Задача:
Напишите функцию allSettled, которая работает аналогично встроенному Promise.allSettled.

Пример использования:

const p1 = Promise.resolve(1);
const p2 = Promise.reject('Ошибка');
const p3 = new Promise(res => setTimeout(() => res(42), 100));

allSettled([p1, p2, p3]).then(results => {
console.log(results);
/*
[
{ status: 'fulfilled', value: 1 },
{ status: 'rejected', reason: 'Ошибка' },
{ status: 'fulfilled', value: 42 }
]
*/
});


Решение:

function allSettled(promises) {
return Promise.all(
promises.map(p =>
Promise.resolve(p)
.then(value => ({ status: 'fulfilled', value }))
.catch(reason => ({ status: 'rejected', reason }))
)
);
}


Что происходит:
1. Каждый промис оборачивается так, чтобы вернуть объект с результатом и статусом.

2. Обычные значения тоже оборачиваются в промис через Promise.resolve, чтобы с ними можно было работать как с промисами.

3. Promise.all ждёт, пока все обёрнутые промисы завершатся, и возвращает массив результатов.


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

#interview #JavaScript
🔥9👍4
Продолжаем серию постов про SOLID.
Сегодня разберём вторую букву — O, которая расшифровывается как Open/Closed Principle или принцип открытости/закрытости.

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

Пример

class Shape {
constructor(type) {
this.type = type;
}

getArea() {
if (this.type === 'circle') {
// Логика для круга
return Math.PI * 5 * 5;
} else if (this.type === 'rectangle') {
// Логика для прямоугольника
return 10 * 5;
}
}


Если нужно добавить новую фигуру, придётся менять метод getArea, добавляя новую ветку в if. Это нарушает принцип закрытости, так как мы модифицируем существующий код.

Как улучшить?
Используем наследование, чтобы сделать класс открытым для расширения:


class Shape {
getArea() {
throw new Error('Method getArea() must be implemented');
}
}

class Circle extends Shape {
constructor(radius) {
super();
this.radius = radius;
}

getArea() {
return Math.PI * this.radius * this.radius;
}
}

class Rectangle extends Shape {
constructor(width, height) {
super();
this.width = width;
this.height = height;
}

getArea() {
return this.width * this.height;
}
}

// Теперь можно легко добавить новую фигуру
class Triangle extends Shape {
constructor(base, height) {
super();
this.base = base;
this.height = height;
}

getArea() {
return 0.5 * this.base * this.height;
}
}

// Использование
const shapes = [new Circle(5), new Rectangle(10, 5), new Triangle(6, 8)];
shapes.forEach(shape => console.log(shape.getArea()));


Теперь, чтобы добавить новую фигуру, мы просто создаём новый класс, не трогая существующий код.

Какие плюсы это даёт?
— Новый функционал добавляется без риска сломать старый код.
— Меньше багов, так как основной код остаётся нетронутым.
— Легче тестировать отдельные классы с конкретной логикой.

#BestPractices #JavaScript
👍9👏6
Привет! Сегодня обсудим TypeScript и поймем нужно ли его учить. TypeScript — это не просто хайп, а инструмент, который делает разработку проще.

Что такое TypeScript?
TypeScript — это надстройка над JavaScript, которая добавляет статическую типизацию. Код на TS компилируется в обычный JS, но с кучей бонусов для разработчика.

Зачем использовать TypeScript?
1. Типы помогают ловить ошибки ещё на этапе написания кода. Например, если вы случайно передадите строку вместо числа, TS сразу укажет на проблему.

function add(a: number, b: number): number {
return a + b;
}
add("2", 3); // TS не даст передать строку вместо числа


2. TS делает код понятнее: типы и интерфейсы — как документация, которая всегда под рукой.

3. В больших приложениях TS спасает от хаоса. Он помогает управлять сложной логикой и предотвращает ошибки при рефакторинге.

4. Редакторы кода с TS подсказывают методы, свойства и типы.

5. Любой JS-код — это валидный TS-код. Можно добавлять типы постепенно, не переписывая проект с нуля.

Когда точно надо использовать TS?
— В проектах с большим количеством разработчиков.
— Когда нужно поддерживать сложную бизнес-логику.

Стоит ли учить TS в 2025?
Однозначно да! TypeScript — стандарт в индустрии. Плюс, знание TS жирный плюс на собеседовании.

#typescript #JavaScript
👍13🔥3
Привет! Сегодня разберёмся в разнице между extends и implements.

Что такое extends?
Ключевое слово extends используется, когда один класс наследует другой. Это значит, что он получает все свойства и методы родительского класса и может их переопределить или дополнить.


class Animal {
move() {
console.log('Moving');
}
}

class Dog extends Animal {
bark() {
console.log('Woof!');
}
}

const dog = new Dog();
dog.move(); // Moving
dog.bark(); // Woof!


Класс Dog получил доступ ко всем методам Animal.

Используем extends, когда:
— хотим переиспользовать код
— строим иерархию классов
— нужно расширить поведение базового класса


Что делает implements?
implements используется, когда класс реализует интерфейс. Интерфейс — это просто контракт, набор правил, которые класс должен соблюдать. Он не содержит никакой логики.


interface Flyable {
fly(): void;
}

class Bird implements Flyable {
fly() {
console.log('Flying');
}
}


Если в классе Bird не будет метода fly, TS сразу покажет ошибку — потому что мы обязались его реализовать.

Используем implements, когда:
— хотим задать чёткие правила, что должен уметь класс
— пишем код в стиле “контрактов”
— делаем архитектуру более предсказуемой


#typescript #JavaScript
👍9🔥5
Продолжаем разбираться с промисами. Сегодня разберем Promise.race.

Что такое Promise.race?
Promise.race - это метод, который принимает массив промисов и возвращает новый промис, который завершается или отклоняется так же, как самый быстрый промис в массиве. То есть, как только один из промисов завершается (успешно или с ошибкой), Promise.race сразу возвращает его результат, игнорируя остальные.

Пример использования
Представим, что у нас есть несколько API, и нам нужен результат от того, который ответит быстрее:

async function getFastestData() {
try {
const api1 = fetch('https://api1.example.com/data');
const api2 = fetch('https://api2.example.com/data');
const api3 = fetch('https://api3.example.com/data');

const winner = await Promise.race([api1, api2, api3]);
console.log('Самый быстрый API:', await winner.json());
} catch (error) {
console.error('Ошибка:', error);
}
}


Здесь Promise.race дождётся первого ответа от любого API. Если один из запросов завершится с ошибкой раньше остальных, Promise.race сразу перейдёт в состояние ошибки.

Когда использовать?
Например, вы отправляете запросы на несколько зеркал API, и вам нужен самый быстрый ответ.

Важно
Promise.race не отменяет остальные промисы. Даже если один промис завершился, остальные продолжают выполняться в фоне.


В одном из следующих постов разберём интересную задачу, которая встречается на некоторых собеседованиях, но не все находят её решение.

#JavaScript
👍9👏3🔥2
В прошлом посте мы разобрали Promise.race, который возвращает результат самого быстрого промиса. Сегодня решим задачу с собеседований по ограничению времени выполнения промиса с его помощью.

Задача
Ограничить время выполнения промиса, чтобы он завершался с ошибкой, если не уложился в 5 секунд.

Решение

function timeout(ms) {
return new Promise((_, reject) => {
setTimeout(() => reject(new Error('Время ожидания истекло!')), ms);
});
}

async function getDataWithTimeout() {
try {
const dataPromise = fetch('https://api.example.com/data');
const result = await Promise.race([dataPromise, timeout(5000)]);
console.log('Данные:', await result.json());
} catch (error) {
console.error('Ошибка:', error.message);
}
}

getDataWithTimeout();


Как это работает?
1. Функция timeout(ms) создаёт промис, который отклоняется с ошибкой через заданное время.
2. Promise.race запускает гонку между dataPromise и timeout.
3. Если API отвечает быстрее 5 секунд — получаем данные. Если нет — срабатывает timeout, и мы ловим ошибку.

#interview
🔥11👍4
Продолжаем серию постов про SOLID. Сегодня разберём третью букву — L, которая расшифровывается как Liskov Substitution Principle или принцип подстановки Лисков.

О чём этот принцип?
Принцип гласит:
Объекты базового класса должны быть заменяемыми объектами производного класса без нарушения корректности программы.


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

Пример плохого подхода
Рассмотрим пример с платёжными системами:

class Payment {
process(amount) {
return `Обработка платежа на ${amount} рублей`;
}
}

class CreditCardPayment extends Payment {
process(amount) {
return `Оплата ${amount} рублей картой`;
}
}

class CashPayment extends Payment {
process(amount) {
throw new Error("Оплата наличными онлайн не поддерживается!");
}
}

// Использование
function makePayment(payment, amount) {
console.log(payment.process(amount));
}

const creditCard = new CreditCardPayment();
const cash = new CashPayment();

makePayment(creditCard, 100); // "Оплата 100 рублей картой"
makePayment(cash, 100); // Ошибка: Оплата наличными онлайн не поддерживается!


Здесь CashPayment нарушает принцип, потому что не может выполнить метод process, который ожидается от базового класса Payment. Это ломает логику функции makePayment.

Как улучшить?
Разделим поведение, чтобы подклассы соответствовали ожиданиям:

class Payment {
process(amount) {
throw new Error("Метод должен быть переопределён");
}
}

class OnlinePayment extends Payment {
process(amount) {
return `Онлайн-оплата на ${amount} рублей`;
}
}

class CreditCardPayment extends OnlinePayment {
process(amount) {
return `Онлайн-оплата картой на ${amount} рублей`;
}
}

class OfflinePayment extends Payment {
process(amount) {
return `Офлайн-оплата на ${amount} рублей`;
}
}

class CashPayment extends OfflinePayment {
process(amount) {
return `Оплата наличными в кассе на ${amount} рублей`;
}
}

// Использование
function makePayment(payment, amount) {
console.log(payment.process(amount));
}

const creditCard = new CreditCardPayment();
const cash = new CashPayment();

makePayment(creditCard, 100); // "Онлайн-оплата картой на 100 рублей"
makePayment(cash, 100); // "Оплата наличными в кассе на 100 рублей"


Теперь CashPayment не обязан поддерживать онлайн-платежи, а CreditCardPayment реализует оба метода. Это делает код предсказуемым и безопасным.

Что это даёт?
— Подклассы не ломают логику, заданную базовым классом.
— Легче добавлять новые подклассы без риска ошибок.
— Разделение ответственности упрощает поддержку.
— Нарушения контракта базового класса исключены.


Совет
При создании наследования проверяйте: "Может ли подкласс заменить базовый без сюрпризов?". Если нет, подумайте о разделении классов или используйте композицию.

#BestPractices #JavaScript
🔥8👍4