Сборная солянка про фронтенд. JavaScript, React, TypeScript, HTML, CSS — здесь обсуждаем всё, что связано с веб-разработкой! Связь: @pmowq
Привет 👋
Разберём полезный и редко используемый атрибут inputmode. Он говорит браузеру, какую клавиатуру показывать на мобильных устройствах при вводе данных.
Пример:
<input type="text" inputmode="numeric" placeholder="Введите номер телефона" />
text
— обычная клавиатураnone
— клавиатура не показываетсяtel
— клавиатура с цифрами и символами телефонаnumeric
— только цифрыdecimal
— цифры и точкаemail
— клавиатура с @ и .url
— клавиатура с / и .search
— клавиатура для поиска с кнопкой “Поиск”inputmode
отвечает только за то, какую клавиатуру показать пользователю, а type
определяет, какие данные реально можно ввести.Привет! Недавно мы разбирали нативный метод Object.groupBy
, а сегодня разберём задачу с реализацией кастомного groupBy
.
Задача
Дан массив объектов:
const users = [
{ name: 'Алиса', age: 21 },
{ name: 'Макс', age: 25 },
{ name: 'Ваня', age: 21 },
];
groupBy(users, user => user.age);
// Результат:
// {
// 21: [{ name: 'Алиса', age: 21 }, { name: 'Ваня', age: 21 }],
// 25: [{ name: 'Макс', age: 25 }]
// }
reduce
:
function groupBy(array, fn) {
return array.reduce((acc, item) => {
const key = fn(item);
(acc[key] ||= []).push(item);
return acc;
}, {});
}
reduce
накапливает объект acc
.fn(item)
.acc
, создаём массив.item
в этот массив.acc
.Вы пользовались ИИ-редакторами? 🤯
Я недавно попробовал вайбкодить домашние проекты и это просто какой-то шок. Раньше на MVP уходило несколько дней, а сейчас буквально 1–2 часа на фронт и бэк. Главное написать качественный промт, с которым тоже помогает ИИ 😱
Да, это чаще всего платно и нередко говнокодит, но если брать такой код за основу, то реально можно ускорить разработку. Многое ещё зависит от модели, но все они довольно часто допускают тупые ошибки и потом по кругу пытаются их исправить.
Как бы грустно это ни звучало, но зима близко 🥶 Конечно, сложно будет заменить программистов из-за качества сгенереного кода, поддержки и в целом понимания продукта, но со временем определённо станет сложнее искать работу(пока выдыхаем) 😕
#career #frontend
Привет! Сегодня разберём интересный момент в React с вызовом нескольких setState
в одном обработчике.
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("Effect:", count);
}, [count]);
const handleClick = () => {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>+3</button>
</div>
);
}
setCount
используют одно и то же значение count
, так как React не обновляет состояние мгновенно.setState
в одном событии. В итоге React применяет только последний вызов, и count
становится 1.useEffect
срабатывает один раз, так как состояние изменилось только один раз.Effect: 1
setCount
учитывал предыдущее состояние, используйте функциональное обновление:
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("Effect:", count);
}, [count]);
const handleClick = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>+3</button>
</div>
);
}
setCount
получает актуальное значение prev
, и count
увеличивается правильно, но useEffect
всё равно сработает один раз, так как React батчит изменения.В проектах часто встречается рендер через логический оператор И:
{condition && <div>Component</div>}
condition
истинно, то отрендерится компонент. Если же условие ложно, то мы ничего не увидим. Причину этого мы разбирали в этом посте(клац).
{condition || secondCondition && <div>Component</div>}
&&
) выполняется раньше, чем ИЛИ(||
).
{condition || (secondCondition && <div>Component</div>)}
condition
ложно, а secondCondition
истинно.
{(condition || secondCondition) && <div>Component</div>}
{condition || secondCondition ? <div>Component</div> : null}
Продолжаем разбирать SOLID. Сегодня буква I — Interface Segregation Principle (принцип разделения интерфейсов).
О чём этот принцип?
Клиенты не должны зависеть от методов, которые они не используют. Интерфейсы должны быть узкими и заточенными под конкретную задачу.
Если проще — интерфейсы и контракты классов должны быть узкими и содержать только те методы, которые действительно нужны конкретному клиенту.
Иначе классам придётся реализовывать ненужные методы или выбрасывать исключения.
Пример плохого подхода
interface Device {
print(doc: string): void;
scan(doc: string): void;
fax(doc: string): void;
}
class SimplePrinter implements Device {
print(doc: string): void {
console.log(`Печатаю: ${doc}`);
}
scan(doc: string): void {
throw new Error('Не поддерживает сканирование');
}
fax(doc: string): void {
throw new Error('Не поддерживает факс');
}
}
SimplePrinter
вынужден наследовать методы scan
и fax
, которые ему не нужны, что нарушает принцип.
interface Printable {
print(doc: string): void;
}
interface Scannable {
scan(doc: string): void;
}
interface Faxable {
fax(doc: string): void;
}
class SimplePrinter implements Printable {
print(doc: string): void {
console.log(`Печатаю: ${doc}`);
}
}
Бывают ситуации, когда нужно развернуть вложенный массив. Некоторые начинают писать reduce
и вручную перебирать вложенность. В JS есть готовое решение — метод flat()
.
Что делает?
Метод flat()
разворачивает вложенные массивы на указанную глубину, создавая новый массив.
Примеры
const arr = [1, [2, 3], [4, [5]]];
console.log(arr.flat()); // [1, 2, 3, 4, [5]]
const arr = [1, [2, 3], [4, [5, [6]]]];
console.log(arr.flat(2)); // [1, 2, 3, 4, 5, [6]]
flat(2)
развернул массив на два уровня.Infinity
:
const arr = [1, [2, [3, [4, [5]]]]];
console.log(arr.flat(Infinity)); // [1, 2, 3, 4, 5]
lat()
не изменяет исходный массив, а возвращает новый.Часто люди говорят, что выгорели, но на деле это не выгорание, а профессиональная скука или застой.
Что такое настоящее выгорание?
Выгорание — это серьёзный психологический синдром с тремя признаками:
1. Постоянная усталость. Отдых не помогает, сил нет ни на работу, ни на личную жизнь.
2. Потеря интереса. Всё кажется бессмысленным, работа раздражает, мотивация пропадает.
3. Снижение эффективности. Даже простые задачи даются с трудом, уверенность падает.
Что делать, если выгорание накрыло?
1. Не игнорируй проблему. Проблема не решится сама, смена проекта не поможет.
2. Обратись с психологу. Выгорание часто связано не только с работой.
3. Больше отдыхай. Перестань заниматься рабочими задачами в свободное время.
Что не является выгоранием?
Если тебе скучно, это, скорее всего, застой. Его признаки:
— Однообразные задачи.
— Нет профессионального роста.
— Работаешь на автомате.
— Хочешь развития, но нет возможностей.
Вроде всё нормально. Но скучно. И мозг потихоньку тухнет 🧟
Как справиться с застоем?
1. Бери задачи, которые кажутся сложными или непривычными.
2. Участвуй в хакатонах, делай что-то для души и роста.
3. Ты не просто пишешь код — ты решаешь задачи бизнеса. И это важно.
4. Ставь цели.
5. Смени проект 🧠
Застой — это сигнал, что ты перерос текущую зону комфорта. Пора двигаться дальше и выходить из зоны комфорта ⬆️
В прошлом посте мы разобрали 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();
timeout(ms)
создаёт промис, который отклоняется с ошибкой через заданное время.Promise.race
запускает гонку между dataPromise
и timeout
.timeout
, и мы ловим ошибку.Привет! Сегодня разберёмся в разнице между 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
используется, когда класс реализует интерфейс. Интерфейс — это просто контракт, набор правил, которые класс должен соблюдать. Он не содержит никакой логики.
interface Flyable {
fly(): void;
}
class Bird implements Flyable {
fly() {
console.log('Flying');
}
}
Bird
не будет метода fly
, TS сразу покажет ошибку — потому что мы обязались его реализовать.implements
, когда: Продолжаем серию постов про 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()));
Всем привет!
Ухожу в мини-отпуск 🏝, но постараюсь не исчезать и выкладывать посты)
Всем хороших выходных 😘
В одном из предыдущих постов мы кратко познакомились с 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) {
// логика отправки письма
}
}
На фронтенде производительность — ключевой фактор для хорошего пользовательского опыта. Чем быстрее загружается и работает приложение, тем лучше.
Одним из инструментов оптимизации является динамический импорт. Он позволяет загружать модули только тогда, когда они действительно нужны.
Как это?
Вместо статического импорта:
import { heavyFunc } from './heavyModule.js';
button.addEventListener('click', () => {
heavyFunc();
});
button.addEventListener('click', async () => {
const module = await import('./heavyModule.js');
module.heavyFunc();
});
heavyModule.js
загрузится только после клика по кнопке, а не сразу при загрузке страницы. React.lazy
.
import React, { Suspense } from 'react';
const LazyComponent = React.lazy(() => import('./MyComponent'));
function App() {
return (
<Suspense fallback={<div>Загрузка...</div>}>
<LazyComponent />
</Suspense>
);
}
MyComponent
загрузится только при первом рендере LazyComponent
.Закончим неделю на CSS 🖼️
Иногда нужно стилизовать элемент только если он единственный в родителе. В CSS это можно сделать с помощью псевдокласса :only-child
.
Как он работает?
Это псевдокласс выбирает элемент, если он единственный потомок родителя.
Пример:
.parent > div:only-child {
background-color: blue;
}
.parent
будет один div
.:only-child
позволяет находить потомков, но иногда нам нужно стилизовать их родителя. Для этого можно комбинировать с псевдоклассом :has()
.
.parent:has(:only-child) {
background-color: red;
}
:only-child
: Can I Use:has
: Can I UseПривет! Сегодня разберём StrictMode
для разработки в React 🖼️ Он помогает находить потенциальные ошибки и улучшать качество кода.
Что делает StrictMode?StrictMode
активирует дополнительные проверки в режиме разработки:
- Дважды вызывает функции (например, useEffect
, useState
, конструкторы классов), чтобы выявить побочные эффекты.
- Подскажет, если вы используете методы, которые скоро удалят.
- Находит потенциальные баги, такие как мутации состояния или неправильная работа с хуками.
Как подключить?
Оберните приложение:
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
useEffect
и другие хуки идемпотентны (не ломаются при повторных вызовах).StrictMode
в легаси проект, будьте готовы к сюрпризам.StrictMode
без веской причины.Заканчиваем серии постов про SOLID! Сегодня разберём последнюю букву - D, которая расшифровывается как Dependency Inversion Principle (Принцип инверсии зависимостей).
О чём этот принцип?
Высокоуровневые модули не должны зависеть от низкоуровневых. Оба должны зависеть от абстракций. А абстракции не должны зависеть от деталей — детали зависят от абстракций.
Если проще - зависите от интерфейсов, а не от конкретных классов. Это делает код гибким и независимым от деталей реализации.
Пример плохого подхода:
class Database {
save(data: string): void {
console.log(`Сохранено в базе: ${data}`);
}
}
class UserService {
private db = new Database();
saveUser(user: string): void {
this.db.save(user);
}
}
const service = new UserService();
service.saveUser("Alice"); // Сохранено в базе: Alice
UserService
жёстко привязан к Database
. Хотите сохранить данные в файл или API? Придётся переписывать UserService
.
interface Storage {
save(data: string): void;
}
class Database implements Storage {
save(data: string): void {
console.log(`Сохранено в базе: ${data}`);
}
}
class FileStorage implements Storage {
save(data: string): void {
console.log(`Сохранено в файл: ${data}`);
}
}
class UserService {
constructor(private storage: Storage) {}
saveUser(user: string): void {
this.storage.save(user);
}
}
const dbService = new UserService(new Database());
dbService.saveUser("Alice"); // Сохранено в базе: Alice
const fileService = new UserService(new FileStorage());
fileService.saveUser("Bob"); // Сохранено в файл: Bob
UserService
зависит от абстракции Storage
.Сегодня разберём CSS-свойство — display: contents
. Оно редко встречается, но может сильно упростить вёрстку в некоторых кейсах.
Что такое display: contents?
Это свойство заставляет элемент "исчезнуть" из рендера, но его дочерние элементы остаются и ведут себя так, будто родителя нет.
Пример
У нас есть сетка, но лишняя обёртка ломает структуру:
<style>
.wrapper {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.inner {
display: contents;
}
p {
background: lightblue;
padding: 10px;
}
</style>
<div class="wrapper">
<div class="inner">
<p>Привет</p>
<p>Мир</p>
</div>
</div>
display: contents
блок .inner
создаёт лишний уровень в сетке, и не попадают в ячейки .wrapper
. С display: contents
элемент .inner
исчезает из рендера, и <p/>
становятся прямыми детьми .wrapper
для сетки, занимая ячейки.Привет! Сегодня разберём метод Promise.withResolvers
. Он упрощает работу с промисами, особенно когда нужно управлять их состоянием извне.
Что он делает?
Этот метод создаёт промис и возвращает объект, содержащий сам промис и функции для его разрешения или отклонения.
Синтаксис:
const { promise, resolve, reject } = Promise.withResolvers();
promise
— сам промис.resolve(value)
— функция для успешного завершения промиса.reject(reason)
— функция для отклонения промиса.resolve(data)
или reject(error)
в любом месте, и промис перейдёт в соответствующее состояние.
const { promise, resolve, reject } = Promise.withResolvers();
const ws = new WebSocket('wss://example.com');
ws.onmessage = event => resolve(event.data);
ws.onerror = error => reject(error);
promise
.then(data => console.log('Получено:', data))
.catch(error => console.error('Ошибка:', error));
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
Привет! Обычно для группировки я использую groupBy
из Lodash, но в самом JavaScript тоже есть встроенные методы:
1. Object.groupBy
2. Map.groupBy
Пример данных с которыми будем дальше работать:
const users = [
{ name: "Аня", age: 25, city: "Москва" },
{ name: "Иван", age: 30, city: "Москва" },
{ name: "Оля", age: 22, city: "Казань" },
{ name: "Петя", age: 30, city: "Казань" },
];
const byCity = Object.groupBy(users, user => user.city);
console.log(byCity);
// {
// "Москва": [ { name: "Аня", age: 25 }, { name: "Иван", age: 30 } ],
// "Казань": [ { name: "Оля", age: 22 }, { name: "Петя", age: 30 } ]
// }
const byAge = Map.groupBy(users, user => user.age);
console.log(byAge);
// Map(3) {
// 25 => [ { name: "Аня", age: 25 } ],
// 30 => [ { name: "Иван", age: 30 }, { name: "Петя", age: 30 } ],
// 22 => [ { name: "Оля", age: 22 } ]
// }
Object.groupBy
- возвращает объект, ключи всегда строки.Map.groupBy
- возвращает Map, где ключи могут быть любыми.Привет! Начнем неделю с разбора интересного метода padStart
.
Метод padStart
добавляет в начало строки указанные символы (пробелы по умолчанию), пока строка не достигнет заданной длины.
Синтаксис:
string.padStart(targetLength, padString)
targetLength
: желаемая длина строки после дополнения.padString
: символы, которыми дополняется строка (по умолчанию — пробел).targetLength
, метод добавляет padString
в начало, пока строка не станет нужной длины. Если строка уже длиннее или равна targetLength
, она остаётся без изменений.
const num = '42';
console.log(num.padStart(5, '0')); // "00042"
const day = '7';
const month = '3';
console.log(day.padStart(2, '0')); // "07"
console.log(month.padStart(2, '0')); // "03"
// Получаем: "03.07"
padString
длиннее, чем нужно, он обрезается до необходимой длины.targetLength
меньше длины строки, метод возвращает строку без изменений.padStart
возвращает новую строку.Привет) Начнем неделю с разобора as const в 🖼️
Что такое as const?
Это утверждение константности в TypeScript, которое делает объект, массив или примитив иммутабельным на уровне типов. Оно говорит компилятору, что значение не будет меняться, а его свойства или элементы становятся литеральными типами. С as const
фиксируются точные значения.
Рассмотрим объект с настройками:
const config = {
mode: "development",
port: 3000,
};
// TypeScript выводит тип: { mode: string; port: number }
config.mode = "production"; // Без проблем
mode
— это просто string
.
const config = {
mode: "development",
port: 3000,
} as const;
// Тип теперь: { readonly mode: "development"; readonly port: 3000 }
config.mode = "production"; // Ошибка: Cannot assign to 'mode' because it is a read-only property
config
имеет точный тип, где mode
— это буквально "development", а port
— буквально 3000. Плюс, свойства стали readonly
, что защищает их от изменений.string
, number
и т.д.).as const
предотвращает случайные изменения объектов или массивов.as const
, когда хотите зафиксировать точные значения объектов, массивов или примитивов, особенно в конфигурациях, словарях или наборах констант, но не злоупотребляйте)Продолжаем серию постов про 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
реализует оба метода. Это делает код предсказуемым и безопасным.Продолжаем разбираться с промисами. Сегодня разберем 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 сразу перейдёт в состояние ошибки.Promise.race
не отменяет остальные промисы. Даже если один промис завершился, остальные продолжают выполняться в фоне.Привет! Сегодня обсудим TypeScript и поймем нужно ли его учить. TypeScript — это не просто хайп, а инструмент, который делает разработку проще.
Что такое TypeScript?
TypeScript — это надстройка над JavaScript, которая добавляет статическую типизацию. Код на TS компилируется в обычный JS, но с кучей бонусов для разработчика.
Зачем использовать TypeScript?
1. Типы помогают ловить ошибки ещё на этапе написания кода. Например, если вы случайно передадите строку вместо числа, TS сразу укажет на проблему.
function add(a: number, b: number): number {
return a + b;
}
add("2", 3); // TS не даст передать строку вместо числа
В прошлый раз мы познакомились с методом 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 }))
)
);
}
Promise.all
ждёт, пока все обёрнутые промисы завершатся, и возвращает массив результатов.В прошлом посте мы разобрали 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: ... // ошибка
}
Многие из нас сталкиваются с проблемой, когда нужно сделать сразу несколько запросов. Часто такие запросы выполняются по цепочке, хотя на самом деле это не всегда нужно. Если запросы не зависят от ответа предыдущего, ждать их друг за другом бессмысленно. В таких случаях запросы лучше выполнять параллельно, чтобы ускорить работу и улучшить пользовательский опыт.
Пример работающего, но проблемного кода:
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
, который позволяет получить результаты всех промисов, даже если некоторые из них упали. Но эту тему мы затронем в одном из следующих постов.Привет! Начнем неделю с задачи которой со мной поделился коллега.
Задача:
Написать порядок вывода в консоль и объяснить.
Решение:
Всего у нас будет 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
В CSS появилось новое экспериментальное свойство: anchor-name
. Оно позволяет задавать якорь, к которому можно привязывать другие элементы через CSS.
Что и для чего?
Свойство anchor-name
позволяет задать якорь для элемента. Этот якорь может быть использован другими элементами для позиционирования.
Мы указываеем точку привязки, а другие элементы могут позиционироваться относительно неё.
Пример работы
1. Якорь объявляется на элементе:
.anchor { anchor-name: --info; }
.tooltip {
position: absolute;
position-anchor: --info;
}
anchor
.tooltip {
left: anchor(right);
top: anchor(top);
}