Как стать автором
Поиск
Написать публикацию
Обновить
37.01

TypeScript *

Cтрого типизированная надстройка для JavaScript

Сначала показывать
Порог рейтинга

Жёсткая структура React-компонентов

При работе над React-приложениями я часто сталкиваюсь с тем, что мои коллеги смешивают в одном файле и JSX, и CSS-in-JS стили, и логику, и типы компонента. Работать с таким месивом очень сложно. Даже если настаивать на выделении логики, стилей и типов в отдельные файлы, это то делается, то нет. Для введения жёсткой структуры компонентов мною была написана простейшая библиотека react-component-structure.

https://github.com/sergeyshpadyrev/react-component-structure

Работает она простейшим образом. Любой компонент необходимо разделить на три хука и файл с типами:

-| Component
    -| index.ts
    -| logic.ts
    -| render.tsx
    -| style.ts
    -| types.ts

В файле logic.ts мы пишем хук useLogic - контроллер компонента, включающий в себя всю его бизнес-логику - все хуки useCallback, useEffect, useMemo и подобные. В этом хуке у нас есть доступ к props компонента.

import { useCallback, useState } from 'react';
import type { Props } from './types';

const useLogic = (props: Props) => {
    const [count, setCount] = useState(props.defaultCount);

    const onClickMinus = useCallback(() => setCount((c) => c - 1), []);
    const onClickPlus = useCallback(() => setCount((c) => c + 1), []);

    return {
        count,
        onClickMinus,
        onClickPlus,
    };
};

export default useLogic;

В файле styles.ts мы помещаем хук useStyle со стилями нашего компонента. Тут мы можем использовать inline-стили, CSS-in-JS или Tailwind. В этом хуке у нас есть доступ к props нашего компонента и к его контроллеру.

import type { Props } from './types';
import useLogic from './logic';
import { useMemo } from 'react';

const useStyle = (props: Props, logic: ReturnType<typeof useLogic>) =>
    useMemo(
        () => ({
            counter: {
                fontSize: logic.count + 10,
            },
            title: {
                color: props.color,
            },
        }),
        [logic.count, props.color],
    );

export default useStyle;

В файле render.tsx мы помещаем хук useRender с JSX, то бишь отображение компонента. В этом хуке у нас есть доступ и к props компонента, и к его контроллеру logic, и к стилям.

import type { Props } from './types';
import type useLogic from './logic';
import type useStyle from './style';

const useRender = (props: Props, logic: ReturnType<typeof useLogic>, style: ReturnType<typeof useStyle>) => {
    return (
        <div>
            <div style={style.title}>Hello {props.greeting}!</div>
            <div style={style.counter}>Count: {logic.count}</div>
            <div onClick={logic.onClickMinus}>Decrease</div>
            <div onClick={logic.onClickPlus}>Increase</div>
        </div>
    );
};

export default useRender;

В index.ts файле мы соединяем все три хука с помощью функции createComponent:

import { createComponent } from 'react-component-structure';

import useLogic from './logic';
import useRender from './render';
import useStyle from './style';

const Component = createComponent({ useLogic, useRender, useStyle });

export default Component;

И в файле types.ts мы объявляем тип для props компонента:

export interface Props {
    color: string;
    defaultCount: number;
    greeting: string;
}

Если у компонента нет props, то можно объявить их так:

export type Props = unknown

При использовании каждый компонент нашего приложения имеет чёткую структуру, состоящую из файлов контроллера, отображения, стилей и типов. Это разделение подобно разделению на HTML (отображение), CSS (стили) и JavaScript (контроллер) в ванильных JS-приложениях.

Если подход и библиотека вам понравились, поставьте репозиторию звезду на гитхабе. Надеюсь этот подход будет вам полезен.

Теги:
+1
Комментарии10

TypeScript и C++ в одном бинаре. Первый open source-проект от МойОфис

Команда МойОфис выложила в open source собственную разработку — компилятор tsnative. Это кроссплатформенный инструмент, который объединяет удобство TypeScript с производительностью C++ в одном приложении. Исходники — на GitHub под лицензией Apache 2.0.

Компилятор tsnative — это первая и ключевая часть более масштабного проекта под названием AntiQ, в котором мы переосмысляем подход к кроссплатформенной разработке без тяжёлых фреймворков.

AntiQ разделен на два самостоятельных компонента. Сейчас в open source опубликован только tsnative — главный модуль, в котором сосредоточена основная логика и заложены архитектурные принципы всей платформы.

Зачем это нужно?

tsnative — это кроссплатформенный компилятор, преобразующий TypeScript в нативный машинный код. Он обеспечивает бесшовную интеграцию с C++ без glue-кода или JavaScript-движков, объединяя удобство высокоуровневой разработки с производительностью системного кода. В результате вы получаете один бинарник, собранный из двух языков.

Как это работает:

  • C++-функции помечаются TS_EXPORT;

  • генерируются .d.ts-декларации для TS;

  • TypeScript- и C++-код собираются через LLVM;

  • на выходе — один исполняемый файл, без обёрток.

// math.cpp
#include "TS.h"
TS_EXPORT int add(int a, int b) {
return a + b;
}
// index.ts
console.log(add(2, 3)); // -> 5
// index.ts
console.log(add(2, 3)); // -> 5

Проект может быть интересен тем, кто:

  • делает нативные приложения, но хочет писать часть логики на TS;

  • ищет замену закрытым коммерческим компиляторам;

  • любит ковыряться в сборке, кросс-компиляции и низкоуровневом коде.

Открытый код — не просто «выложили и забыли». Мы хотим, чтобы проектом пользовались, коммитили, обсуждали. Поэтому будем рады pull request’ам, issue и звёздочкам. Всё как обычно :)

Исходники на GitHub

Если у вас есть вопросы или комментарии, вы можете задать их в чате проекта: https://t.me/antiqmyoffice

Теги:
+36
Комментарии12

Под пост в телеграм про наш ответ аналог Grafana пришли руководители "Лаборатории Числитель" и конкретно "Пульта", в чью систему входит платформа мониторинга и анализа "Графиня".

Дмитрий Унтила и Владимир Утратенко любезно ответили на ряд вопросов по платформе, и я хотел бы подвести краткое резюме:

  • Запрос на аналог Grafana появился в момент продажи "Пульта", так как "не всем можно брать забугорный опесорс себе в контур. Потому решили делать. А название Графиня просто ради лулзов."

  • Графиня писалась с полного нуля. Хотя предложение взять за основу Grafana было, но команда смогла обосновать геморрой, который на неё упадёт, если взяться за доработку, и объяснила руководству, почему лучше сделать с нуля.

  • Время разработки: 3 месяца - MVP, 6 месяцев - 1 релиз.

  • Технологический стек: фронтенд: TypeScript + React 18, бэкенд: TypeScript + Node.js, база данных: MongoDB, плагины: Java.

  • На мое предложение сделать проект опенсорс Дмитрий Унтила пообещал подумать. Но, понятное дело, если это часть коммерческого продукта, прям сильно думать в лаборатории не будут))

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

Благодарю парней за такой актив и обратную связь, рад, что у людей есть на это время!

Теги:
+1
Комментарии3

Ко мне в телеграм канал заглянул один из разработчиков Графини (убийцы Grafana), с пояснением, зачем они её родили, и что писали полностью с нуля.

Я верю.

Теги:
-4
Комментарии2

Сила RxJS. scan + mergeScan = 'Загрузить еще'

Кнопка 'Загрузить еще' (либо автоматическая подгрузка данных при скролле) довольно часто встречается в проектах и обычно решение связано с большим количеством подписок и переменных.

Как всегда, для оптимизации чего либо нам на помощь приходит великий и могучий RxJS, а в данной ситуации конкретно операторы scan & mergeScan.

Код:

  readonly loadTrigger$ = new Subject<void>();
  private readonly batchSize = 5;

  private readonly posts$ = this.loadTrigger$.pipe(
    startWith(void 0),
    scan((offset) => offset + this.batchSize, -this.batchSize),
    mergeScan(
      (accPosts: Post[], offset: number) =>
        getPosts(offset, this.batchSize).pipe(
          map((newPosts) => [...accPosts, ...newPosts]),
        ),
      [] as Post[],
    ),
  );
  1. scan – калькулятор + хранитель состояния для offset:

    • Управляет состоянием загрузки (текущее смещение)

    • Начинается с -batchSize, чтобы первая загрузка была с 0

    • Увеличивает смещение на batchSize при каждом срабатывании

  2. mergeScan – волшебный оператор для инкрементальной загрузки:

    • Сохраняет массив накопленных постов

    • Объединяет новые данные с существующими

    • Корректно обрабатывает параллельные запросы (в отличие от обычного scan)

Где полезен этот паттерн?

  • Постраничные API (пагинация)

  • Бесконечная прокрутка

  • Порционная загрузка данных

  • Любые сценарии накопления асинхронных данных

scan - https://rxjs.dev/api/operators/scan
mergeScan - https://rxjs.dev/api/operators/mergeScan
Больше об Angular - https://t.me/grandgular

Теги:
Рейтинг0
Комментарии0

afterEveryRender и afterNextRender

В Angular 20 afterRender был переименован в afterEveryRender, и это очень логично, так как теперь он более четко отражает суть (нейминг решает). Сам afterRender (далее afterEveryRender) и его брат afterNextRender появились в версии 17. Рассмотрим, почему эти два мощных инструмента управления рендерингом — не просто альтернативы ngAfterViewInit, а полноценные хуки жизненного цикла с бесшовной поддержкой SSR!

Это хуки?
Да! Это хуки нового типа, которые выполняются после рендеринга компонента:

  • Они не заменяют ngAfterViewInit/ngAfterContentInit, а дополняют их

  • Включают гранулярные реакции на рендеры, включая обновления

Почему идеально подходит для SSR?
Главное преимущество: обратные вызовы выполняются только на клиенте!
✅ После гидратации (в SSR)
✅ После первоначального рендеринга (в CSR)
✅ Больше никаких ошибок «документ не определен»

Использование:
constructor() {
// 🚫 Не запускается на сервере
// ✅ Запускается только один раз после загрузки браузера!
// 📊 Идеально подходит для однократной инициализации
afterNextRender(() => {
console.log('Next');
});

// 🚫 Не запускается на сервере
// 🔄 Запускается после каждого цикла обнаружения изменений
// ✨ Отлично подходит для обновлений, зависящих от DOM
afterEveryRender(() => {
console.log('Every');
});
}


Когда использовать?

afterNextRender

  • Одноразовые операции (инициализация библиотеки, загрузка данных)

  • Безопасная замена ngAfterViewInit для SSR

afterEveryRender

  • Отслеживание изменений DOM (измерения элементов, позиции)

    ⚠️ Внимание: может повлиять на производительность

Основные выводы

  • Интегрировано в систему жизненного цикла Angular

  • Автоматический пропуск на стороне сервера - больше никаких хаков isPlatformBrowser!

  • afterNextRender - "один раз после рендеринга"

  • afterEveryRender - "после каждого обновления"

"Я пока не использовал afterEveryRender в своих проектах - есть ли у вас практические примеры использования? Поделитесь в комментариях!"

Больше об 🅰️ngular в моём Telegram-канале

Теги:
Рейтинг0
Комментарии0

withComponentInputBinding()
Упрощение работы с параметрами маршрутизатора в Angular.

Как было раньше?

  1. Создаем переменную/свойство (Signal, BehaivorSubject, Observable, неважно)

  2. Инжектим и подписываемся на ActivatedRoute

  3. Получаем параметры маршрута

  4. Записываем в BS/Signal

😵‍💫😵‍💫😵‍💫

Манипуляций довольно много, но мы все к этому привыкли и это кажется нормальным.

Но с withComponentInputBinding() все стало намного проще:
1. Создаем сигнальный инпут... и...
Вот и все!

Никаких дополнительных манипуляций, и значение «у вас в кармане». Все, что вам нужно, чтобы это работало, — это передать withComponentInputBinding() в качестве аргумента в provideRouter().

Функция не новая (кажется, появилась в Angular 16), но я редко ее видел в проектах.

Немного технической информации из документации:

🔍Маршрутизатор передает данные в input() из:

  1. Параметров запроса (?page=1&sort=asc)

  2. Параметров пути и матрицы (/users/123;details=true)

  3. Статических данных маршрута (data: { role: 'admin' })

  4. Результатов резолвера (resolve: { user: userResolver })

🔍 Приоритеты:
Если есть дублирующиеся ключи, данные переопределяются в порядке выше — резолверы имеют наивысший приоритет и перезапишут остальные.

🚩 Важный нюанс
Если в маршруте нет данных для input(), он получит undefined (например, если параметр запроса удален из URL).

ℹ️ Как задать значения по умолчанию?

✔ Через resolver (чтобы данные всегда были в маршруте)
✔ Через transform в input() (если нужно обрабатывать undefined)

Спасибо разработчикам Angular за эту функциональность 🙏.

Больше об 🅰️ngular в моём Telegram-канале

Теги:
Всего голосов 2: ↑1 и ↓1+2
Комментарии0

🦥 RxJS defer — ленивая инициализация Observable

defer — это фабрика, которая создает Observable только при подписке, а не во время объявления. Идеально подходит для:

  • HTTP-запросов (чтобы избежать преждевременного выполнения)

  • динамических данных (которые должны быть свежими при каждой подписке)

  • условных потоков (когда Observable зависит от состояния времени выполнения)

📌 Основные варианты использования

  1. Свежие данные при каждой подписке

    const freshData$ = defer(() => of(Date.now()));

    // Новая временная метка при каждой подписке()

  2. Работа с изменяемым состоянием

    const token$ = defer(() => of(localStorage.getItem('token')));

    // Всегда получает текущий токен, даже если обновлен

  3. Условные наблюдаемые

    const api$ = defer(() => isLoggedIn ? http.get('/user') : http.get('/guest') );

  4. Генерация случайного значения

    const random$ = defer(() => of(Math.random()));

    // Новое случайное число на подписку

🚫 Ограничения defer

  • нет кэширования → используйте shareReplay, если вам нужно повторно использовать результаты.

  • нет отмены запроса → объедините с switchMap/takeUntil для управления отменой

⚡Когда следует выбирать defer вместо обычных наблюдаемых?

  • данные должны быть свежими при каждом subscribe()

  • cоздание наблюдаемого стоит дорого и должно быть отложено

  • поток зависит от изменяемых условий (флаги функций, статус аутентификации и т. д.)

Больше об 🅰️ngular в моём Telegram-канале

Теги:
Рейтинг0
Комментарии0

Angular Hack: Цикл без данных (в тэмплейте)

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

Вот мой способ (по крайней мере, я нигде такого не видел):

@for (_ of [].constructor(10); track $index) {
<div class="item"></div>
}

Используется Array.constructor, чтобы создать пустую массив фиксированной длины, который @for может перебрать по индексам.

Плюсы ✅

  • Чудо-код (удивит коллег)

  • Минимум кода (не нужно объявлять массив в компоненте)

Минусы ⚠️

  • Чудо-код (может ненадолго ввести в ступор чающего код человека)

Конечно, можно просто использовать Array.from({length: 10})... но так все делают, не интересно)

Норм тема? Как считаете?

Теги:
Всего голосов 2: ↑1 и ↓10
Комментарии3

Приглашаем вас на бесплатный вебинар «TypeScript за час: основы, плюсы и практика». Познакомим вас с TypeScript. Расскажем, зачем он появился, в чём его преимущества и как он помогает писать понятный и надёжный код. Подойдёт и новичкам, и практикам.

⁉️ На вебинаре вы сможете задать вопросы спикеру.

📅 Дата: 13.05.2025

Время: 17:00-18:00 (Мск)

На вебинаре:

✔️ История возникновения TypeScript

✔️ Преимущества использования TypeScript

✔️ Пример написания кода на TypeScript

👨‍🎓 Спикер: Кучин Евгений — разработчик на Java и JavaScript.

✍️Записаться на вебинар

Возможно, вам будет интересен курс «Язык программирования TypeScript». Вы освоите TypeScript, изучив типизацию, интерфейсы и классы. Узнаете, как использовать статическую типизацию, интегрировать TypeScript с существующим JavaScript и настраивать окружение разработки. Это поможет сделать ваш код более безопасным и структурированным.

Старт: 19 мая

Цена: 14 900 8 940 ₽ (-40%)

✍️ Записаться на курс

Теги:
Всего голосов 4: ↑1 и ↓30
Комментарии0

Приглашаем на новый бесплатный вебинар «Pattern matching в TypeScript: делаем код предсказуемым».

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

На вебинаре мы обсудим, как избежать этих ошибок, используя подходы из Domain-Driven Design (DDD) и функционального программирования (FP). Вы узнаете, как отделить бизнес-логику от реализационных деталей, создать качественные доменные модели и эффективно применять discriminated unions и паттерн-матчинг в Typescript.

⁉️ На вебинаре вы сможете задать вопросы спикеру.

📅 Дата: 24.04.2025

Время: 18:00-19:30 (мск)

На вебинаре:

✔️ Discriminated Unions

✔️ DDD

✔️ Business Logic as Data

✔️ FP

✔️ Declarative Programming

👨‍🎓 Спикер: Борисов Никита — эксперт в области фронтенд-разработки.

👉Записаться👈

Теги:
Рейтинг0
Комментарии0

Как я выбил 350к в месяц, не написав ни одной строчки кода сам

2025 год. Реалии
2025 год. Реалии

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

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

Инструменты

Harvi.pro — мой основной инструмент. Норм генерит фронт по текстовому описанию, меньше заморочек с оплатой для наших:

  • 999₽ — 10М токенов

  • 2499₽ — 25М + 5М бонусом

  • 4999₽ — 50М + 10М бонусом

Под капотом Claude 3.7 Sonnet. Беру средний тариф, хватает на 3-4 недели активной работы.

V0.dev от Vercel — тоже неплохой, но дороговат для меня:

  • Free — 20 генераций

  • Pro — $20 в месяц

  • Team — $30 в месяц

Под капотом тоже Claude 3.7 Sonnet. Качество вроде чуть лучше бывает, но с оплатой сами знаете что.

Replit — тут я собираю всё в кучу, тестирую и деплою. Удобно, что можно быстро показать клиенту результат.

Как это на самом деле работает

Не буду врать, что просто нажимаю кнопку и получаю готовый продукт. Это все еще работа:

  1. Собираю с клиента максимум инфы и референсов

  2. Генерю компоненты по частям (целые страницы редко получаются с первого раза)

  3. Много времени уходит на склеивание и фиксы

  4. Приходится знать хотя бы основы, чтобы понимать, что пошло не так

Но при этом скорость выросла раза в 3-4. Раньше лендинг делал неделю, сейчас — день-два. Простое приложение с формами — было 2 недели, стало 3-4 дня.

Беру в среднем 80-100к за проект, делаю 3-4 в месяц. Вот и выходит около 350к.

Да, чувствую, что теряю навыки в некоторых областях. Зато прокачался в составлении промптов — это теперь как отдельная специальность.

Ручной кодинг... скоро только в музее)))

Теги:
Всего голосов 7: ↑4 и ↓3+3
Комментарии7

Ближайшие события

Регулярно на Хабре выходят статьи с рекомендацией использовать moment.js. В комментариях обязательно начинают советовать какой-нибудь dayjs или js-joda, но не потому, что они чем-то сильно лучше, а потому, что первый задепрекейчен авторами.. в пользу luxon.

Что за мания такая у JS-еров использовать раздутые тормозные библиотеки? Есть же быстрый и миниатюрный $mol_time с гораздо более удобным и функциональным API, почти полностью поддерживающим ISO8601, в отличие от всех остальных библиотек.

Бенчмарки говорят сами за себя
Бенчмарки говорят сами за себя

Что мотивирует людей довольствоваться не самым лучшим решением в индустрии? Я, наверно, странный, но я не могу этого понять.

Теги:
Всего голосов 19: ↑7 и ↓12-3
Комментарии39

Энтузиасты запустили Doom с помощью типов TypeScript.

Проект запуска Doom исключительно в системе ввода TypeScript занял 12 дней по 18 часов в сутки. Разработчики провели проверку 3,5 триллиона строк текста, создали виртуальную машину WebAssembly на основе TypeScript и 177 ТБ типов TypeScript.

Теги:
Всего голосов 2: ↑2 и ↓0+4
Комментарии2

Немного о резолверах в Angular 19 (теперь в них есть редиректы).

В логике использования гвардов применяется подход:

проверить что-то, если все ок то вернуть true или кинуть редирект на другую страницу

Выглядит достаточно удобно. Но если мне не изменяет память в резолверах такого нет, вместо этого приходилось натягивать Router и рулить navigate или navigateByUrl и т.д.

В 19 же версии нам немного упростили жизнь и резолвер научили в RedirectCommand.

Пример с angular.dev

export const heroResolver: ResolveFn<Hero>= async (
  route: ActivatedRouteSnapshot,
  state: RouterStateSnapshot,
) => {
  const router = inject(Router);
  const heroService = inject(HeroService);
  
  try {
    return await heroService.getHero(route.paramMap.get('id')!);
  } catch {
    return new RedirectCommand(router.parseUrl('/404'));
  }
  
};

Ну просто сказка какая-то, а не только сигналы =)

Теги:
Всего голосов 2: ↑2 и ↓0+2
Комментарии0

Для тех, кто рассматривает TypeScript как инструмент (язык) для борьбы с ошибками: склонность к ошибкам и время их исправления в TypeScript оказались не значительно ниже, чем в JavaScript. Среднее отношение коммитов с исправлениями ошибок в проектах на TypeScript было более чем на 60% выше (0.126 против 0.206), и в среднем проектам на TypeScript требовалось более чем на один день больше для исправления ошибок (31,86 против 33,04 дней).

Теги:
Всего голосов 5: ↑5 и ↓0+7
Комментарии7

Полезные ресурсы для начинающего TypeScript-разработчика

Начнём с базы: Что такое TypeScript и чем он отличается от JavaScript

Бесплатные курсы

Learn TypeScript — курс для начинающих, который поможет освоить основы. Состоит из коротких уроков, общее время просмотра меньше 5 часов.

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

Egghead Typescript — курсы и статьи с разделением по уровням: будет полезно и джунам, и сеньорам.

Фулстек-разработчик — курс с бесплатной частью, в которой можно изучить основы HTML, CSS и JavaScript.

YouTube

Элементы TypeScript — двухчасовая лекция для продвинутых от разработчика Владимира Минина.

Frontend Channel — записи с конференций по разным направлениям и инструментам для создания веб-проектов.

Yandex for Frontend — лекции от экспертов Яндекса. Есть видео про инструменты программиста и IT-карьеру.

Плейлист по TypeScript — уроки из курса ItGid на канале «WebDev с нуля».

13 уроков TypeScript — основы и возможности TypeScript, курс видеолекций на канале WebDev.

Книги

«TypeScript быстро» — синтаксис, инструменты и связь с JavaScript. Теория с примерами: в книге разбирают реальные задачи разработчиков.

«Эффективный TypeScript: 62 способа улучшить код» — для разработчиков, которые знают JavaScript. Научит использовать TypeScript для повышения качества и уровня безопасности кода.

«Профессиональный TypeScript» — поможет в разработке масштабируемых JavaScript-приложений. Лучше совмещать чтение с практическими заданиями или курсами.

Теги:
Всего голосов 2: ↑1 и ↓10
Комментарии0

Привет друзья! Сделал максимально простые аналоговые часы на SVG. Можно ли их еще упростить или уменьшить? Или добавить немного улучшений без переусложнения? Буду рад вашим идеям!

Вот CodeSandbox

Fusor SVG Analog Clock
Fusor SVG Analog Clock

Сделано с помощью библиотеки Fusor

Теги:
Всего голосов 1: ↑1 и ↓0+3
Комментарии10
1