 
       
                   
               1137
              1137
          
          Сборная солянка про фронтенд. JavaScript, React, TypeScript, HTML, CSS — здесь обсуждаем всё, что связано с веб-разработкой! Связь: @pmowq
 
                    Привет! Отдохнули? Начнем неделю с 🖼️
Каждый из нас работает с TypeScript, но не все знают про оператор satisfies. Он помогает гарантировать, что объект соответствует нужному типу, сохраняя при этом точный тип значения.
Пример
type ButtonTypes = 'primary' | 'secondary';
type ButtonSizes = 'small' | 'big' | number;
interface ButtonConfig {
type: ButtonTypes;
size: ButtonSizes;
}
const button = {
type: 'primary', // тип "primary"
size: 'big' // тип "big"
} satisfies ButtonConfig;
const button2 = {
type: 'secondary', // тип "secondary"
size: 100 // тип "number"
} satisfies ButtonConfig;
console.log(button.size);
console.log(button2.size.toFixed(0));
type и size соответствуют интерфейсу ButtonConfig
type ButtonTypes = 'primary' | 'secondary';
type ButtonSizes = 'small' | 'big' | number;
interface ButtonConfig {
type: ButtonTypes;
size: ButtonSizes;
}
const button: ButtonConfig = {
type: 'primary', // тип "ButtonTypes"
size: 'big' // Тип "ButtonSizes"
};
if (typeof button.size === 'number') {
console.log(button.size.toFixed(0));
}
 
                    Привет! Начнем эту 3-дневную рабочую неделю с небольшой алгоритмической задачи.
Задача
Напишите функцию, которая возвращает массив уникальных значений, отсортированных:
1. по количеству повторений (по убыванию),
2. если количество совпадает — по алфавиту.
Пример входных данных:
const words = [
'banana', 'grapefruit', 'banana', 'grapefruit',
'banana', 'orange', 'banana', 'grapefruit',
'grapefruit', 'grapefruit'
];
['grapefruit', 'banana', 'orange']
function getSortedUnique(words) {
const freq = {};
for (let word of words) {
freq[word] = (freq[word] || 0) + 1;
}
return Object.keys(freq).sort((a, b) => {
if (freq[b] !== freq[a]) {
return freq[b] - freq[a]; // по убыванию частоты
}
return a.localeCompare(b); // по алфавиту
});
}
freq. 
                    Использовали атрибут inert?
Этот атрибут позволяет отключить часть интерфейса от взаимодействия — элементы становятся недоступными для клика, фокуса и скринридеров.
Что делает inert?
Атрибут удаляет элемент из порядка навигации — кликнуть, сфокусироваться или ввести данные внутри него нельзя.
Поведение похоже на disabled, но есть важные отличия:
— inert можно применить к любым элементам
— В отличие от disabled, текст внутри inert нельзя выделить, и он не реагирует на кастомные эффекты наведения или фокуса
💻 Пример использования:
<main inert>
<!-- Здесь всё будет неактивно -->
</main>
inert не влияет на стили по умолчанию — нужно самостоятельно делать элементы визуально неактивными, например:
[inert] {
opacity 0.7;
}
const element = document.body;
// Добавляем inert
element.inert = true;
// Удаляем inert
element.inert = false;
 
                    Работа с API, кэширование, обновление данных и обработка ошибок — всё это неизбежно возникает в любом приложении. На выбор у нас есть много подходов и инструментов. Один из таких — React Query.
Что такое React Query?
Это библиотека для управления данными в React-приложениях. Она помогает работать с запросами и решает типичные задачи, связанные с их загрузкой, кэшированием, обновлением и обработкой ошибок. 
Какие проблемы решает?
1. Вместо написания шаблонного кода для запросов, состояний и обработки ошибок — можно использовать готовые хуки.
2. Данные автоматически кэшируются, и повторные запросы выполняются только при необходимости.
3. Готовые состояния и возможность легко повторить запрос.
4. Минимизирует количество запросов к API за счет кэширования и умной стратегии обновления.
5. Реализация сложных сценариев становится проще.
Как использовать React Query?
1. Установите библиотеку в проект:
npm install @tanstack/react-query
QueryClientProvider:
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<Component />
</QueryClientProvider>
);
}
useQuery для загрузки данных:
const { data, isLoading, error } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
});
useMutation для изменения данных:
const { mutate } = useMutation({
mutationFn: updateTodo,
});
mutate({ id: 1, text: 'Updated Todo' });
 
                    Всем привет! 
Начнем эту неделю с мемов и настроимся на продуктивную рабочую неделю 🖥
А я начну неделю с дейоффа 😀
 
                    Когда мы пишем приложения, важно обеспечить их стабильность, расширяемость и простоту поддержки. В этом посте узнаем про принципы SOLID, которые помогают с этим)
Используя SOLID-принципы, вы пишете код, который легче тестировать, поддерживать и развивать. 
SOLID — это аббревиатура, которая объединяет 5 принципов:
S - Single Responsibility Principle (Принцип единой ответственности)
Каждый класс или компонент должен отвечать только за одну задачу. Это упрощает тестирование и отладку, потому что если что-то идет не так, вы точно знаете, какой класс или компонент за это отвечает. Такой подход также облегчает расширение приложения, так как изменение в одном месте не затронет другие части.
O - Open/Closed Principle (Принцип открытости/закрытости)
Классы должны быть открыты для расширения, но закрыты для модификации. Это означает, что вы можете добавлять новый функционал, не изменяя существующий код. Это достигается через использование абстракций, таких как интерфейсы и наследование.
L - Liskov Substitution Principle (Принцип подстановки Лисков)
Объекты базового класса должны быть заменяемыми объектами производного класса без нарушения корректности программы. Другими словами, наследуемые классы должны вести себя как их базовые классы, чтобы не ломать логику приложения.
I - Interface Segregation Principle (Принцип разделения интерфейсов)
Не стоит создавать универсальные интерфейсы, которые будут иметь множество методов, не используемых в разных классах. Лучше создавать несколько более специализированных интерфейсов. Это уменьшает зависимость классов и облегчает поддержку кода.
D - Dependency Inversion Principle (Принцип инверсии зависимостей)
Высокоуровневые модули не должны зависеть от низкоуровневых, а обе эти категории должны зависеть от абстракций. Это позволяет сделать код гибким и менее зависимым от конкретных реализаций.
Применение этих принципов поможет писать более чистый и поддерживаемый код, который будет легко расширяться и адаптироваться под изменения.
#BestPractices
 
                    Привет! Хотя регулярность постов и изменилась, неделя по-прежнему начинается с разбора задач с собеседований)
У нас есть пример типизированого массива:
interface Student {
name: string;
age: number;
hasScar: boolean;
}
const students: Student[] = [
{ name: "Harry", age: 17, hasScar: true },
{ name: "Ron", age: 17, hasScar: false },
{ name: "Hermione", age: 16, hasScar: false },
];
function getBy(arr, prop, value) {
return arr.find(item => item[prop] === value) || null;
}
const result = getBy(students, "name", "Hermione");
console.log(result);
getBy. 
function getBy<T, K extends keyof T>(arr: T[], prop: K, value: T[K]) {
return arr.find(item => item[prop] === value) || null;
}
<T, K extends keyof T> — дженерик:T — это универсальный тип, который представляет тип объекта внутри массива arr.K extends keyof T — означает, что K — это ключ объекта T. Тип keyof T — это объединение всех ключей объекта T.   arr: T[] — массив объектов типа T.prop: K — ключ, по которому будем искать совпадение.value: T[K] — значение, с которым будем сравнивать. Тип значения автоматически подтягивается из типа свойства prop.  
                    Сегодня немного затронем работу с ошибками в 🖼️
Создание ошибок 
Чтобы создать ошибку, нужно создать новый экземпляр класса Error и передать сообщение в конструктор:
try {
throw new Error("Что-то пошло не так!");
} catch (error) {
console.error(error.message); // Что-то пошло не так!
}
throw. Ошибка перехватывается в блоке catch, где мы можем вывести её сообщение.message — сообщение об ошибке.name — тип ошибки, по умолчанию это Error.stack — стек вызовов, который позволяет увидеть, откуда была вызвана ошибка.throw и try...catch помогает контролировать ошибки в коде и избежать краша нашего приложения. Например, можно перехватить её и выполнить другие действия:
try {
const result = someFunction(); // Эта функция выбросит ошибку
} catch (error) {
console.error("Произошла ошибка:", error.message);
// Тут можно выполнить альтернативные действия
}
try...catch можно контролировать, как ошибки влияют на дальнейшие действия. 
                    Привет! Отдохнули?) Начнем эту неделю с TypeScript. А вы знали, что в интерфейсы могут сливаться?
Пример:
interface User {
name: string;
age: number;
}
interface User {
email: string;
}
const user: User = {
name: "Григорий",
age: 42,
email: "mail@example.com",
};
User сливаются в один, и объект user должен удовлетворять обоим интерфейсам.type).
interface Product {
name: string;
price: number;
}
interface Product {
price: string; // Ошибка: Subsequent property declarations must have the same type.
}
price имеет разные типы (number и string). 
                    На майские беру небольшой перерыв 😋
Советую и вам отложить всю технику и как следует отдохнуть!)
Напоминаю, что у нас есть чат — @TrueFrontenderChat.
В нём можно задавать вопросы, помогать другим и предлагать идеи для будущих постов 🐸
Хороших выходных!
 
                    Видели сайты с красивым скроллом фона? Это легко сделать самому, используя всего одно свойство CSS — background-attachment: fixed;.
Демо и код тут: CodePen 
Поддержка браузерами: CanIUse
#css
 
                    Пятничная подборка мемов 😂
Не забывайте, что у нас есть чат. Всем хороших выходных!
 
                    Сделал небольшую шпаргалку со всеми типами инпутов
Ссылка на шпаргалку: CodePen
 
                    Привет! Начнем неделю с очень крутой задачи на знание основ JS. Наверное, большинство может запутаться и дать неправильный ответ, но в этом нет ничего страшного.
Что выведет этот код?
var a = 5;
function f() {
if (a) {
console.log(a);
var a = 10;
}
}
f();
a внутри функции f поднимается в начало функции, но её значение остаётся undefined до строки var a = 10;.undefined, поэтому console.log(a) не вызывается.var, поднимаются в начало области видимости, но их значение присваивается только на момент выполнения строки с присваиванием. 
                    Один из моих любимых утилити-типов в TypeScript — ReturnType. Сегодня пост о нём.
Что делает ReturnType?ReturnType<T> — извлекает тип возвращаемого значения из функции T.
Пример:
function getUser() {
return { id: 1, name: 'Василий' };
}
type User = ReturnType<typeof getUser>;
// { id: number, name: string }
User всегда будет соответствовать тому, что возвращает getUser. ReturnType — можно получить тип возвращаемого значения хука и использовать его в пропсах компонента.
// Какой-то хук с логикой
function useUserData() {}
// Пропсы компонента
interface Props {
data: ReturnType<typeof useUserData>;
};
function UserInfo({ data }: Props) {}
interface User {}
function useUserData(): User {}
interface Props {
data: User;
};
function UserInfo({ data }: Props) {}
ReturnType. Главное знать о его существовании и сами найдете применение в своем проекте) 
                    Когда-нибудь использовали CSS-функцию attr? С помощью этой функции мы можем подставлять значения HTML-атрибутов прямо в стили.
Что это такое?attr() — CSS-функция, которая извлекает значение HTML-атрибута и подставляет его в стиль или содержимое.  
Раньше она работала только в content, теперь экспериментально доступна и в других свойствах.
Пример:
<button class="tooltip-btn" data-tooltip="Удалить файл">Удалить</button>
.tooltip-btn::after {
content: attr(data-tooltip);
}
attr в таких свойствах, как width, height, margin и др. Пример:
<div data-width="150" data-height="150"></div>
div {
width: attr(data-width px, 100px);
height: attr(data-height px, 100px);
}
 
                    Недавно мы разбирали(тык) работу с ошибками в JS.
Сегодня рассмотрим создание кастомных ошибок. Они позволяют точнее описывать, что именно пошло не так, и обрабатывать ошибки более точечно.
Вместо обычного Error можно создать свой класс, унаследованный от него:
class ValidationError extends Error { // 1
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
function validateUser(user) { // 2
if (!user.name) {
throw new ValidationError("Поле 'name' обязательно");
}
}
try {
validateUser({}); // 3
} catch (error) {
if (error instanceof ValidationError) { // 4
console.error("Ошибка валидации:", error.message);
} else {
throw error;
}
}
Error, чтобы определить новый тип ошибки — ValidationError.validateUser проверяет, есть ли у объекта поле name. Если его нет — выбрасывает кастомную ошибку ValidationError с понятным сообщением.try вызываем validateUser, передавая объект без name.catch ловим ошибку и проверяем, является ли она экземпляром ValidationError с помощью instanceof.instanceof. 
                    Вы наверняка видели OTP-поля для ввода кодов. А знаете, как они делаются?  
Сегодня разберёмся, как они делаются с помощью атрибута size.
Что делает атрибут size?
Атрибут size указывает примерное количество видимых символов в поле ввода. Это влияет только на ширину, но не ограничивает количество вводимых символов.
<input type="text" size="6">
<input type="text" size="1" maxlength="1">
<input type="text" size="1" maxlength="1">
<input type="text" size="1" maxlength="1">
<input type="text" size="1" maxlength="1">
size="1" делает поле под один символ.  maxlength="1" ограничивает ввод до одного символа.text, search, tel, url, email, passwordmaxlengthwidth перекроет size, если задать оба 
                    Сегодня у нас очередная задача с собеседования, связанная с рекурсией)
Задача:
Нужно распарсить вложенный объект, чтобы получить значение самого глубокого уровня.
Пример вызова функции:
const obj = { red: { fast: { fancy: { car: "lamba" } } } };
console.log(objParse(obj)); // "lamba"
function objParse(obj) {
const [value] = Object.values(obj); // 1, 2
if (typeof value !== "object") { // 3
return value;
}
return objParse(value); // 4
}
Object.values(obj) возвращает массив всех значений объекта.   value.objParse рекурсивно. 
                    Надеюсь, посты этой недели вам понравились)
Хороших выходных, фронтендеры 😘
 
                    В TypeScript 🖼️ есть два основных способа описания структуры данных — интерфейсы и типы. Когда и что выбирать?
Интерфейсы в TypeScript обычно используются для описания структуры объектов, особенно если они предполагают возможность расширения.
Их можно расширять с помощью extends, который позволяет создавать новые структуры на основе существующих.
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
const dog: Dog = {
name: 'Кнопка',
breed: 'Лабрадор'
};
type Result = 'success' | 'error';
const result: Result = 'success'; // Ок
const error: Result = 'failure'; // Будет ошибка
type Person = { name: string };
type Worker = { job: string };
type Employee = Person & Worker;
const employee: Employee = {
name: 'Иван',
job: 'Frontender'
};
type Coordinates = [number, number];
const point: Coordinates = [10, 20];
 
                    Хотел бы обсудить сохранение постов.
Многие из нас сохраняют интересные посты. Но возвращаемся ли мы к этим сохранёнкам? Чаще всего — нет.
Почему так?
Потому что сохранение — это не запоминание. Это как положить книгу на полку и подумать, что ты уже её прочитал.
И главное мы почти никогда не возвращаемся к сохраненкам) Просто накапливаем.
А как лучше?
1. Если просто интересно — лучше прочитай сейчас, чем потом. Потом не наступит ✅
2. Если хочешь попробовать — сразу пробуй 🍺
3. Сохраняй меньше, но осознаннее 🧠
4. Не сохраняй вообще, а читай и забывай — это тоже нормально ✍️
Сохрани этот пост чтобы не потерять 🚨
Ставь палец вверх если сохранил 👍
#BestPractices #Obsidian
 
                    Я хорошо отдохнул на майских — и, честно говоря, мне это понравилось)
Посты могут выходить чуть реже, но качество контента не меняется. Хуже точно не станет 🙃
 
                    Привет! Небольшая задача с собеседования и отправляемся отдыхать 🏠
У нас есть следующий код:
const o = {
nameObject: "object o",
printName(a, b, c) {
console.log(this.nameObject);
},
};
// 1
o.printName();
// 2
let print = o.printName;
print();
// 3
print.call(o);
// 4
print.bind(o).call({ nameObject: "call name" });
printName вызывается через объект o, и this указывает на сам объект o.printName присваивается переменной print и вызывается как обычная функция. Контекст this теряется, и в non-strict он указывает на глобальный объект (window), у которого нет свойства name. В strict моде this будет undefined, и попытка обращения к this.nameObject вызовет ошибку.call вызывает функцию с переданным контекстом. В данном случае, this указывает на объект o.bind создаёт новую функцию с привязанным контекстом o. После этого вызывается call, который пытается изменить контекст на новый объект, но bind уже установил контекст, и call не может его установить повторно."object o"undefined. В strict: ошибка"object o""object o" 
                    Одна из распространённых ошибок при работе с React — вызов хуков внутри условий, циклов или вложенных функций 🖼️
Пример кода с ошибкой:
function Component({ show }) {
if (show) {
useEffect(() => {
console.log("Какой-то эффект");
}, []);
}
return <div>Тут что-то интересное!</div>;
}
Invalid hook call
function Component({ show }) {
useEffect(() => {
if (show) {
console.log("Какой-то эффект");
}
}, [show]);
return <div>Тут что-то интересное!</div>;
}
 
                    Привет! Готовы к короткой рабочей неделе? Давайте начнем ее с формошлепства. 
Иногда дизайнеры рисуют сложные формы — поля в одном месте, кнопки в другом. Cамый простой вариант, который приходит в голову — обернуть всё в форму. Да, это работает, но в большинстве случаев это приведёт как минимум к багам, а максимум — к архитектурным проблемам.
Что делать?
Связать кнопку с формой через атрибут form.
Этот атрибут позволяет кнопке отправлять форму, даже если она расположена вне её, благодаря привязке через id.
<form id="contactForm">
<input type="text" name="name" />
</form>
<!-- Кнопка вне формы -->
<button type="submit" form="contactForm">Сохранить</button>
<form> получает id.id через атрибут form.submit. 
                    Разберём ещё один полезный утилити-тип — Awaited 🖼️. Он позволяет извлечь тип результата из промиса.
Что делает Awaited?Awaited<T> возвращает тип, в который разрешается промис T.
Проще говоря — убирает Promise-обёртку и достаёт внутренний тип.
Пример:
async function fetchData(): Promise<string> {
return "Строка с данными";
}
type ResultType = Awaited<ReturnType<typeof fetchData>>;
// Тип: string
ReturnType (о котором я писал в этом посте) и Awaited.ReturnType<typeof fetchData> даёт нам Promise<string>, а затем Awaited вытаскивает из него string. 
                    Почти каждый из нас работает с TypeScript, и большинство сталкивалось с пересечением типов.
Что такое пересечение типов?
Пересечение типов позволяет объединить несколько типов в один. Это означает, что переменная будет иметь свойства всех типов, которые пересекаются.
interface Employee {
name: string;
position: string
}
interface ContactInfo {
email: string;
phone: string
}
type EmployeeContact = Employee & ContactInfo;
const employee: EmployeeContact = {
name: 'Иван',
position: 'Разработчик',
email: 'ivan@google.com',
phone: '+7(900)-000-0000'
};
EmployeeContact включает в себя как поля из типа Employee, так и поля из типа ContactInfo.never, что делает невозможным присваивание значений.  
                    Сегодня база про cookie. Мы работаем с ними каждый день, и про них спрашивают почти на каждом собеседовании.
Что такое cookie?  
Cookie — это строка с данными, которую браузер сохраняет и автоматически отправляет серверу при каждом HTTP-запросе.
Как устанавливаются куки?
document.cookie = "token=abc123; path=/; max-age=3600";
Set-Cookie. Когда сервер хочет установить куку на клиенте, он отправляет этот заголовок в ответе на запрос.path: Определяет путь, для которого кука будет доступна. По умолчанию: / — доступна на всем сайте.expires: Устанавливает дату истечения срока действия куки. По умолчанию: кука сессионная и удаляется при закрытии браузера.max-age: Устанавливает время жизни куки в секундах.  По умолчанию: не указан, кука сессионная.secure: Указывает, что кука будет передаваться только по HTTPS. По умолчанию: не установлен, кука будет передаваться как по HTTP, так и по HTTPS.samesite: Определяет, можно ли отправлять куку с запросами на другие сайты.  По умолчанию: не указан, кука отправляется с запросами как с того же сайта, так и с внешних источников.httpOnly: Указывает, что кука будет доступна только для сервера и не может быть прочитана через JavaScript. Это улучшает безопасность, предотвращая атаки. По умолчанию: не установлен.max-age равным 0 или отрицательным числом.