TGViewer
Channel Public Channel
Frontend Alliance | Автушенко Андрей

Frontend Alliance | Автушенко Андрей

@frontendalliance

Меня зовут Автушенко Андрей. Здесь рассказываю про карьеру во frontend-разработке, менторство и личную жизнь

Менторство - frontend-alliance.ru
Отзывы - https://t.me/palaxius_reviews
Написать лично - @palaxius
YouTube: youtube.com/@FrontendAlliance
Subscribers
2.22K
Photos
101
Videos
3
Links
54

Showing posts older than #120 · Back to latest

Older Posts 11 shown
Post #119 1.91K
Мок собеседование по System Design (Frontend)

Недавно проверял знания Антона в проектировании архитектуры систем с упором на frontend, и в итоге мы записали полноценное мок-собеседование по System Design фронтенд части — проектировали E-commerce маркетплейс, как это делают на реальных интервью.

Формат максимально сконцентрирован именно на фронтенде (так как видео по бекенду огромное количество). Прошлись по всем этапам, начиная от постановки задачи и требований до оптимизаций и метрик.

Пост про выбор пагинаций из видео можно почитать здесь.

В видео разобрали:
— Функциональные и нефункциональные требования
— Высокоуровневый дизайн
— Дерево компонентов
— Проектирование контрактов API
— Подбор стэка и технологий
— Выбор режима рендеринга
— SEO
— Выбор архитектуры и методологий
— FSD
— UI/UX и Доступность
— Различные оптимизации фронтенда и системы мониторинга

Посмотреть можно по ссылке:

https://youtu.be/Ue6wyOFN7Zk
https://youtu.be/Ue6wyOFN7Zk
https://youtu.be/Ue6wyOFN7Zk

✈️ Telegram | 🎓 Менторство | 📹YouTube | 👩‍💻 Roadmap
  • 🔥 27
  • 👍 12
  • 🎉 9
  • ❤ 2
  • 🤯 2
Post #118 1.63K
Оптимизация пагинации

Представьте, что вы проектируете интернет-магазин с тысячами товаров. Показывать их все сразу - плохая идея: страница будет грузиться очень медленно и это может увеличить число отказов.
Здесь на помощь приходит пагинация - способ разбить большой объём данных на равные «порции».

Благодаря пагинации мы можем подгружать контент частями: например, по 20 товаров за раз или по страницам результатов поиска. Это не только ускоряет загрузку, но и делает интерфейс чище и отзывчивее.

Но непосредственная реализация пагинации может так же влиять на производительность и консистентность данных.

Существует два основных подхода к реализации пагинации: offset и cursor.

🟣Offset пагинация

Классический способ, основанный на смещении. Это способ выдачи данных, при котором мы работаем с двумя параметрами: limit (количество элементов на странице) и offset (отступ).

limit: number; // Количество элементов на странице
offset: number; // Смещение (сколько записей пропустить)


Из плюсов:
- Удобно для произвольной навигации: можно легко получить данные по конкретной странице;
- Легко интерпретируется на бекенда: offset вычисляется как (page - 1) * limit;
- Простая реализация (особенно в SQL через LIMIT ... OFFSET);

Из минусов:
- С ростом offset запросы замедляются, так как СУБД должна пройти все записи до нужной позиции. В больших таблицах будет работать медленно (O(n));
- Могут дублироваться записи в ленте для часто обновляемых данных (например, соц. сетей).
Мы посмотрели 5 постов, но за время пока мы их смотрели появилось еще 5 записей в БД. Мы грузим следующую пачку данных, и у нас происходит отступ и мы видим опять те же 5 постов, которые только что посмотрели.
Поправить это можно только дополнительными махинациями на фронте. например дедупликацией данных, где будем отфильтровывать записи перед добавлением в ленту.


🟣Cursor пагинация

Cursor пагинация в свою очередь работает с параметрами size (количество элементов на странице) и cursor (указатель на id, timestamp или другой специфичный параметр), который указывает от какого элемента получать следующую порцию данных.

size: number; // Количество элементов на странице
cursor: string | number; // Указатель на id или timestamp


Из плюсов:
- Нет проблем с замедлением запроса при больших отступах (O(1));
- Нет проблем с дублированием записей. Идеально подходит для часто меняющихся данных, например, лент соц. сетей и infinite scroll;

Из минусов:
- Нет возможности перейти на конкретную страницу - только листать вперед/назад;
- Реализация сложнее, особенно если нужны сортировки по нескольким полям;
- Возможны коллизии, когда пропускаются некоторые записи, если использовать указатель по timestamp.
Последнюю проблему можно решить используя гибридный указатель, например по id + timestamp.


🟣Для системы каталога в интернет магазине нужно использовать offset-пагинацию потому что:

- Нам нужно уметь навигироваться и переходить на конкретную страницу в каталоге
- В результатах по товарам проблема «устаревших» результатов не так критична: новые товары добавляются не очень быстро и не слишком часто.
- Нам нужно знать общее количество результатов, а при offset‑пагинации это проще реализовать

В целом для финального выбора можно опираться на следующий принцип:

Если нужны переходы на конкретные страницы и данные мало меняются - можно спокойно использовать offset.
Если же у вас длинные ленты, большой объём данных и частые обновления - разумнее сразу закладываться на cursor, чтобы не упереться в лаги и дубли в проде.

✈️ Telegram | 🎓 Менторство | 📹YouTube | 👩‍💻 Roadmap
  • 🔥 23
  • ✍ 8
  • 👍 6
  • 👌 1
  • 🤓 1
  • 👨‍💻 1
Post #115 1.44K
Специальные типы: unknown, never, any и void

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

🟣Unknown - это множество всех возможных типов. В него можно присвоить что угодно - любое значение будет корректно относиться к типу unknown.
Это максимально широкий тип в системе типов TypeScript.

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

Использовать unknown следует в ситуациях, когда мы не знаем тип данных в данный момент времени.
Например, после парсинга значения из localStorage.

🟣Его полная противоположность - тип never.

Тип never представляет пустое множество: не существует ни одного значения, которое соответствовало бы этому типу.

type Union = number & string // never


Например, если мы попытаемся сделать пересечение строк и чисел, мы получим never, потому что эти два множества никак не пересекаются.

При использовании конструкции switch-case по типам - в дефолтной ветке результатом тоже будет never.

type Input = boolean | number

function handleInput(x: Input): boolean | number {
if (typeof x === 'boolean') {
return !x; // инвертируем boolean
} else if (typeof x === 'number') {
return x * 2; // удваиваем число
} else {
const unreachable = x; // exhausted check
return 0;
}
}


Рассмотрим пример, у нас есть функция handleInput, которая принимает x, который может быть буленовым или числом. Внутри первого if'a мы сузили эти значения через проверку на typeof только до значений false и true, внутри второго if'a мы сузили до множества чисел, а вот в последнем else блоке уже будет never, что говорит о том, что мы сузили со всех возможных вариантов до пустого множества.

И это может быть очень полезно, так как при масштабирование кода, мы сразу получим ошибку, если кто-то добавит новое значение в тип, которые мы используем в условиях. Это так же работает и с конструкцией switch-case (скрин 1).

🟣Any - это специальный тип в TypeScript.
С точки зрения системы типов, any находится везде одновременно.

Например, если проверить через тернарный оператор, расширяет ли any never, результат будет неожиданным - мы получим пересечение, хотя ожидалось, что будет одно конкретное значение (скрин 2).
Это происходит потому, что any ведёт себя иначе, чем обычные типы: он раскладывает варианты для всех подтипов и может вернуть оба результата.
Any одновременно является подтипом любого типа и супертипом любого типа, поэтому его поведение максимально «размытое».

const a: any = 5

Мы не объявляем здесь, что тут переменная типа any, мы намеренно выключаем проверку типов.

При этом ключевое слово any «вирусное» - все дальнейшие операции с этой переменной тоже будут восприниматься как any: проверки отключаются, и результат вновь становится типа any.

Поэтому это опасный тип, который не стоит использовать в разработке. Это сводит пользу тайпскрипта на нет.

🟣 Void - это тип, который состоит из одного значения: undefined.

Он используется для обозначения того, что функция не возвращает никакого значения.
Даже если TypeScript может сам вывести тип функции как void, рекомендуется указывать его явно.

Это защитит код на случай, если кто-то при рефакторинге случайно попробует вернуть из функции другое значение - TypeScript сразу выдаст ошибку (скрин 3).

✈️ Telegram | 🎓 Менторство | 📹 YouTube | 👩‍💻 Roadmap
  • 🔥 20
  • ✍ 6
  • 👍 6
Post #114 1.19K
Пересечение и объединение типов

🟣Типы в TypeScript могут как пересекаться, так и объединяться.

Объединение множеств выполняется через вертикальную черту "|".

type StringOrNumber = string | number
type StringOrBoolean = string | boolean

const x1: StringOrNumber = 'hello'
const x2: StringOrNumber = 5
const y1: StringOrBoolean = false
const y2: StringOrBoolean = 'foo'

type Union = StringOrNumber | StringOrBoolean // string | number | boolean
type Intersected = StringOrNumber & StringOrBoolean // string


Тип StringOrNumber включает в себя все множество строк и все множество чисел. То есть мы можем создать как переменную числового значения, так и строкового.
Тип StringOrBoolean, включает в себя все множество строк и два буленовых значения false и true.

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

Пересечение достигается через знак амперсанд "&"
Если мы попробуем пересечь эти два множества, то получим на выходе только string, потому что это единственное множество, которое входит в оба типа.

В TypeScript можно использовать условные типы, где тип вычисляется по условию - это похоже на тернарный оператор, но для типов.

type Union = StringOrNumber | StringOrBoolean // string | number | boolean
type Intersected = StringOrNumber & StringOrBoolean // string

type R1 = number extends Union ? 'yes' : 'no' // "yes"

type R2 = number extends Intersected ? 'yes' : 'no' // "no"

Например, мы можем проверить для прошлого примера, расширяет ли number тип Union - результат будет положительный, потому что number является подмножеством множества строк-чисел-и-буленовых значений.

А вот Intersected уже не расширяет, поэтому в итоговый тип попадет строка 'no'

🟣Нюансы объектных типов

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

// Пример: состояние кнопки

type ButtonState =
| { type: 'loading'; spinner: boolean }
| { type: 'error'; message: string }
| { type: 'default'; label: string };

const btn1: ButtonState = { type: 'loading', spinner: true }; // ок
const btn2: ButtonState = { type: 'error', message: 'Oops!' }; // ок
const btn3: ButtonState = { type: 'default', label: 'Click me' }; // ок
const btn4: ButtonState = { type: 'loading', message: 'Oops!' }; // ❌ TS ругается, потому что объект должен соответствовать *только одному из вариантов*


Например, у нас есть тип ButtonState, где в зависимости от поля type может быть одно из полей: spinner, message или label.

🟣Если пересечение примитивов обычно бесполезно (так как тип не может одновременно быть и строкой и числом), то пересечение объектов (&) работает иначе: мы объединяем все свойства вместе.

// Пример: пользователь с настройками

type User = { id: number; name: string };
type Settings = { darkMode: boolean; notifications: [] };

type UserWithSettings = User & Settings;

const user1: UserWithSettings = {
id: 1,
name: 'Alice',
darkMode: true,
notifications: [],
}; // ✔️ ок

const user2: UserWithSettings = {
id: 2,
name: 'Bob',
}; // ❌ нет свойств из Settings (darkMode и notifications)


В данном примере есть два объекта:
- User с полями id и name
- Settings с полями darkMode и notifications

При пересечении этих типов (User & Settings) мы получаем новый тип, который содержит все свойства сразу: id, name, darkMode и notifications.

✈️ Telegram | 🎓 Менторство | 📹 YouTube | 👩‍💻 Roadmap
  • 🔥 23
  • ✍ 6
  • 👍 6
  • ❤ 1
Post #113 1.34K
Система типов TypeScript

🟣Что такое тип?

Тип - это множество возможных значений и набор операций, которые можно выполнять над этими значениями

🟣Что такое система типов?

Система типов - это совокупность всех доступных типов и правила для работы с ними.

В TypeScript каждая переменная имеет свой тип. Тип может быть указан явно (мы прописываем его сами) или определён неявно, когда TS выводит его автоматически.

let x = 'hello world'
let y: string = 'hello world'


Тип гарантирует, что в переменной хранится значение из определенного «набора возможных значений».

TypeScript включает все нативные типы JavaScript: number, string, boolean, bigint, symbol, null, undefined и object.

const age: number = 30;

const adress: string = "Moscow";

const isActive: boolean = true;

const largeNumber: bigint = 100n;

const id: symbol = Symbol("uniqueId");

const value: null = null;

const data: undefined = undefined;

const object: object = { foo: 'bar' };


Кроме того, он добавляет и дополнительные специальные типы: void, any, unknown и never (о них будет отдельный пост).

🟣К посту прикрепил схему системы типов в TypeScript.

Она показывает, что существует пространство всех возможных значений - его описывает тип unknown, самый широкий тип в системе.
Внутри находятся все остальные типы.

При этом каждый тип может иметь своё подпространство - более «узкие» типы, которые содержат типы меньших размеров.

const x1 = 5

В данной строчке типом переменной будет не number, а конкретное значение 5.

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

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

const x: 5 = 5
const y: 'bar' = 'bar'

То же самое работает и для всех примитивных типов.

✈️ Telegram | 🎓 Менторство | 📹 YouTube | 👩‍💻 Roadmap
  • 🔥 20
  • 👍 8
  • ✍ 6
Post #110 2.12K
Брендирование (branding) в TypeScript

🟣Представим, что у нас есть следующий кусок кода для системы заказов в нашем приложении (скрин 1).

У нас есть интерфейс заказа Order и интерфейс карты пользователя UserCard. Есть функция proceedOrders, которая валидирует заказы и возвращает только успешные.

В реальной системе бизнес-логики может быть очень и очень много, и часть заказов не будет выполнена по различным причинам: недостаточно средств на карте, ошибка на бэкенде, сломался какой-то сервис и тд.
С точки зрения системы типов, успешный заказ - это более узкий тип по сравнению с обычным заказом.
Было бы хорошо отразить это в коде, поэтому на выходе мы возвращаем именно тип SuccessOrder.

Но из-за того, что TypeScript использует структурную типизацию, два типа считаются одинаковыми, если у них одинаковая структура. Это значит, что мы легко можем приравнять обычный заказ к успешному и потерять важный кусок информации.
Мы можем обезопасить себя с помощью брендирования.

🟣Брендирование - это искусственное примешивание поля в тип, чтобы сделать его более специфичным и уникальным и предотвратить неправильные присваивания.

Мы добавляем в интерфейс успешный заказ дополнительное поле brand и так же внутрь функции, так как ТС будет от нас ожидать этого поля (скрин 2).

После этого TypeScript начинает различать обычный и успешный заказ на уровне типов.

🟣Но даже при таком подходе остаётся проблема: поле brand можно подделать вручную, и проверку получится обойти.
Более надежный вариант — использовать уникальные символы (скрин 3).

В брендировании через символы мы определяем бренд как уникальный символ и описываем поле через typeof. Затем внутри функции присваиваем это брендированное поле.

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

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

type Branded<T extends { id: string }> = T & {
id: T['id'] & { __brand: T }
}

type User = Branded<{
id: string,
email: string
}>

type Wallet = Branded<{
id: string
amount: bigint
}>


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

🟣Резюмируя: брендирование - это способ повысить надежность типов в условиях структурной типизации TypeScript, гарантируя, что важные различия между типами не будут случайно потеряны.

✈️ Telegram | 🎓 Менторство | 📹 YouTube | 👩‍💻 Roadmap
  • 🔥 20
  • ✍ 6
  • 👍 3
  • ❤ 1
Post #106 2.78K
Структурная и номинальная типизация

🟣 Номинальная типизация (nominal typing) - это когда совместимость типов определяется по их имени или идентификаторам, а не по внутренней структуре.
Даже если структура одинаковая - типы разные.
Два типа считаются совместимыми, только если они явно связаны иерархией.

Это более строгий и безопасный, но менее гибкий подход по сравнению со структурной типизацией, характерный для языков типа Java и C#.

Рассмотрим пример кода на Java (скрин 1).
У нас есть класс User и класс Admin. И если мы попытаемся создать переменную user типа User и присвоить в нее инстанс класса Admin, то будет ошибка, потому что в джаве номинальная типизация и фактически эти типы разные.

🟣Структурная типизация (structural typing) - это система проверки типов, где совместимость определяется не по имени, а по внутренней структуре.

TypeScript как раз использует структурную типизацию, то есть:

Совместимость типов определяется по структуре - если объект имеет все требуемые свойства и методы, он считается совместимым, независимо от имени типа.

Тот же самый пример (скрин 2).
TypeScript разрешает это присваивание, потому что у обоих типов одна и та же структура (свойство name типа string).

🟣Но мы можем заставить типы в классах вести себя как номинальные, для это нужно добавить приватное поле (скрин 3).

Когда мы объявляем поле с ключевым словом private, то TS применяет ограничение на уровне компиляции. Это означает, что приватное поле может быть доступно только внутри того же класса.

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

При необходимости номинальное поведение так же можно симулировать через брендирование (расскажу в отдельном посте).

🟣Структурная типизация хоть и считается "гибкой", но может привести к неожиданным проблемам. Рассмотрим следующий пример (скрин 4):

У нас есть тип DatabaseConfig, который имеет обязательное поле url и необязательное поля retries. И тип APIConfig, который также имеет обязательное поле url и необязательно поле timeout.
Есть функция startApi, которая принимает конфиг, который должен быть типа APIConfig.
Но из-за структурной типизации мы можем передать внутрь функции переменную, которая имеет другой тип, так как их структура типов совпала.
ТС это разрешил, но в реальности внутри функции мы не сможем обращаться к полю retries - будет ошибка.

В реальности подобные проблемы можно ловить через сотни строчек кода и десятки файлов. Поэтому я считаю, что наличие структурной типизации в ТС - это большое допущение, которое приходится фиксить дополнительными танцами с бубном ради безопасной системы типов.

✈️ Telegram | 🎓 Менторство | 📹 YouTube | 👩‍💻 Roadmap
  • 🔥 25
  • 👍 8
  • ✍ 7
  • ❤ 3
Post #104 2.14K
Статическая и динамическая типизация

В JavaScript есть типы. Существует 8 типов данных.
Но при этом JS имеет динамическую и неявную типизацию.

🟣Динамическая означает, что тип переменной определяется не при ее объявлении, а во время выполнения программы в момент присваивания значения и может меняться динамически.

let value = 42;      // Тип: number
console.log(typeof value); // "number"

value = "текст"; // Тип изменился во время выполнения
console.log(typeof value); // "string"

value = true; // Тип снова изменился
console.log(typeof value); // "boolean"


🟣Неявная говорит о том, что тип выводится автоматически без явного указания.

let x = 'hello world!'
console.log(typeof x); // "string"

В данной строке javascript автоматически определит, что в переменной x лежит строка.

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

При необходимости в TypeScript можно явно указать тип для переменных, чтобы повысить читаемость и надежность кода:
let x: string = 'hello world!'


🟣TypeScript является надстройкой над JavaScript и добавляет статическую типизацию.

Это означает, что тип переменной определяется на этапе компиляции кода, что позволяет получать мощные подсказки от IDE и выявлять ошибки на этапе написания кода.
То есть код анализируется не запускаясь.

🟣Это круто, но при этом имеет свои ограничения, например, при работе с данными в райнтайме.

Если мы делаем fetch-запрос к бэкенду и ожидаем данные определённого типа - TS вынужден «доверять» разработчикам, потому что не сможет проверить этот тип во время выполнения.

Посмотрим на примере следующего кода. У нас есть тип Mentor и функция fetchMentor, когда получает данные с бекенда по айди ментора.
Но допустим, в какой-то момент, бекенд изменил контракт и данные пришли не те, которые мы ожидали. TypeScript этого не знает, потому что функция fetchMentor обещала, что результат будет типа Mentor.

type Mentor = {
id: string;
name: string;
email: string;
};

async function fetchMentor(id: number): Promise<Mentor> {
const res = await fetch(`/api/mentors/${id}`);
return await res.json(); // ← TypeScript верит, что это тип Mentor
}

const mentor = await fetchMentor(1);

// А с сервера пришло { "id": 1, "fullname": "Frontend Alliance" }

console.log(mentor.name.toUpperCase()); // Упали в рантайме, так как нет поля name


TypeScript проверяет типы только во время компиляции, а не в рантайме. 

Тоже самое касается например парсинга localStorage - мы не можем в райтайме проверить, что там в действительности будет.

const x: number = JSON.parse(localStorage.getItem('my-favorite-number') || '');


🟣Когда мы видим запись вида:
const x: number


мы думаем, что в переменной x лежит число, но это не до конца верно.

Эта запись означает, что мы верим, что там лежит число, но формально этого может не быть.

На этапе статического анализа мы не можем гарантировать, что после fetch'a и LS'a данные будут определенного типа.

О том как себя обезопасить в рантайме - будет в отдельном посте.

✈️ Telegram | 🎓 Менторство | 📹 YouTube | 👩‍💻 Roadmap
  • 👍 15
  • 🔥 14
  • ✍ 6
  • ❤ 1
Post #101 2.35K
Разница между type и interface в TypeScript

В TS есть два способа описать структуру данных - type и interface.
На первый взгляд они похожи: оба позволяют объявлять объекты, описывать сигнатуры функций и имплементацию классов.
Но есть важные отличия, которые стоит понимать.

В современном TS типы и интерфейсы практически взаимозаменяемые. Но тип может использоваться для примитивов, а интерфейс нет (всегда объектный тип).

В простых случаях интерфейс и тип почти одно и то же (скрин 1).

И TUser, и IUser описывают одинаковую структуру объекта.
Чтобы объединить несколько типов: для интерфейсов используется extends, а для типов - пересечение (&).

При этом TypeScript рекомендует использовать интерфейсы. Почему так?

Есть несколько причин.

1️⃣ Производительность и проверки типов:

TypeScript кэширует отношение интерфейсов внутри компилятора, что делает компиляцию более эффективной.
А вот пересечения типов каждый раз пересчитываются заново.
Из-за этого скорость проверки типов выше, особенно в больших и сложных типах.

2️⃣Как TypeScript проверяет совместимость.
Когда мы сравниваем объект с типом A & B - TS сначала проверяет совместимость с A, потом с B
При interface extends - TS работает с единым структурным описанием типа.

3️⃣Отсюда третий момент - ошибки.

У интерфейсов ошибки обычно короче и понятнее: TS сразу показывает все конфликтующие поля.
Через type - вычисляет пошагово и сначала сообщит, что не хватает одного ключа, а уже потом другого.

В примере (скрин 2) ошибка при использования type, говорит, что отсутствует поле agе, но не говорит про поле address, которое тоже отсутствует.

4️⃣Интерфейсы поддерживают объединение объявлений (declaration merging).

Если объявить интерфейс с тем же именем, что и уже существующий, TS объединит их свойства.

Это может показаться небезопасным.
Однако у этого есть важный плюс: часто необходимо расширять уже существующие интерфейсы, например:
- Добавлять новые свойства к браузерным событиям.
- Расширять глобальные объекты, например Window, если внешняя SDK добавляет на него свой инстанс класса (скрин 3).

В таких случаях declaration merging позволяет TS понимать новые свойства и типизация при этом остается корректной. С type так делать нельзя.

Я в проектах предпочитаю везде использовать интерфейсы, а типы только для примитивов.

✈️ Telegram | 🎓 Менторство | 📹 YouTube | 👩‍💻 Roadmap
  • 🔥 28
  • 👍 9
  • ✍ 7
  • ❤ 5
  • 😁 1
  • 🤣 1
Post #99 1.58K
Итоги 2025 года

Друзья, всех с наступающим Новым годом! 🎄
Этот год вышел очень насыщенным на работу и активности. Хочу закрепить основные.

— Сообщество Frontend Alliance практически дошло до 200 участников.
Это безумно мотивирует. Особенно радует, когда ребята разбираются в новом материале, растут и получают офферы.
Мы, в свою очередь, постоянно улучшаем программу и добавляем новые активности.

— Запустили свой сайт.

— Сделали бесплатный frontend роудмап.

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

— Даже успели запустить свой первый мерч!)

— Начали активную работу над внутренним продуктом для менторства.
В следующем году я уже точно смогу его показать и подробно рассказать.

Сделал предложение своей возлюбленной 💍
И ещё успел побывать на свадьбе Димы (вдохновился).

— Записал два технических курса (не на свой канал), которые выйдут примерно в феврале–марте следующего года. По System Design и TypeScript.
В следующем году хочу еще больше прокачаться в этом ремесле.

— Начал ходить в зал и целый год стабильно его посещал.
Одно из лучших решений года: набрал 12 кг, почти пожал сотку, чувствую себя прекрасно.

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

Следующий год обещает быть не менее насыщенным и продуктивным.

Спасибо, что читаете и поддерживаете!
🎄С наступающим новым годом!
Искренне желаю, чтобы 2026 стал стартом чего-то большего в вашей жизни!

✈️ Telegram | 🎓 Менторство | 📹 YouTube | 👩‍💻 Roadmap
  • 🔥 25
  • ❤ 13
  • 🎉 10
  • 👍 2
  • 🥰 1
Older posts →
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →