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

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

Связь: @pmowq
Download Telegram
В проектах часто встречается рендер через логический оператор И:

{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}


Так код читается сразу и не заставляет думать лишний раз.

#BestPractices #JavaScript
7👍5
Привет! Сегодня разберём метод Promise.withResolvers. Он упрощает работу с промисами, особенно когда нужно управлять их состоянием извне.

Что он делает?
Этот метод создаёт промис и возвращает объект, содержащий сам промис и функции для его разрешения или отклонения.

Синтаксис:

const { promise, resolve, reject } = Promise.withResolvers();

- promise — сам промис.
- resolve(value) — функция для успешного завершения промиса.
- reject(reason) — функция для отклонения промиса.

Теперь можно вызывать resolve(data) или reject(error) в любом месте, и промис перейдёт в соответствующее состояние.

Пример:
Допустим, вы ждёте событие от внешнего API

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;
});


Преимущества withResolvers:
1. Упрощает создание управляемых промисов.
2. Убирает антипаттерн ручного присваивания.
3. Делает код более читаемым и безопасным.

Поддержку смотрите через Can I Use.

#JavaScript #BestPractices
7👍6
Заканчиваем серии постов про 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.

Что это даёт?
- Легко менять реализацию (база, файл, API) без правок в сервисе.
- Подставляйте моки для тестов.
- Зависимости явные, меньше связей.


#BestPractices #JavaScript #typescript
🔥10👍6
Привет! Недавно мы разбирали нативный метод 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;
}, {});
}


Как работает?
1. reduce накапливает объект acc.
2. Для каждого item вычисляем ключ через fn(item).
3. Если ключа нет в acc, создаём массив.
4. Добавляем item в этот массив.
5. Возвращаем обновлённый acc.

#JavaScript #interview
🔥9👍4