Привет!
Сегодня я расскажу о своём опыте в создании фреймворка для фронтенд-разработки. Цель была ясна, как день: сделать так, чтобы всё можно было выучить за 5 минут, с расчётом на то, что человек уже знает React, Vue или Angular.
Как создать компонент
Вариантов тут много. В React это просто функция. В Vue это файл. Мне лично нравится возможность в React создавать несколько вспомогательных компонентов внутри файла, поэтому мы решили, что компонент будет функцией и объявляется он следующим образом:
export const MyComponent = component(() => {
// код тут
});
Реактивные состояния
Для сохранения состояния были придуманы переменные, и мы будем их использовать.
Правила просты:
Если название переменной начинается с
$— значит, она будет реактивной.Если название переменной не начинается с
$— значит, мы её не меняем.
Если нам нужен derived/computed state, то мы описываем константу с нужным значением. Даже если использовать let, то некоторые линтеры автоматически будут менять его на const, поэтому константа — это канон.
Пример кода:
export const MyComponent = component(() => {
let $a = 2;
let $b = 3;
const $sum = $a + $b;
const $sum2 = sum($a, $b);
});
Эффекты
Сама функция компонента выполняется всего 1 раз, поэтому для того чтобы управлять эффектами, есть следующие функции:
watchвыполняет функцию каждый раз, когда меняются реактивные данные внутри.beforeMountвыполняет функцию после инициализации данных и перед тем, как начать обновлять DOM.afterMountвыполняет код после того, как DOM был обновлён.beforeDestroyвыполняет код до того, как удалить ноды из DOM.
Следующий код
export const MyComponent = component(() => {
let $state = 'init';
watch(() => { console.log($state) });
beforeMount(() => { $state = "before" });
afterMount(() => { $state = "after" });
});
будет выводить в консоль:
init
before
after
DOM
Для описания узлов DOM используется HTML-код, прописанный напрямую в функцию, исключение только для событий: onclick, onpress и т.д., они получают функцию в качестве значения. Всё это работает через JSX.
Описание узлов
Пример кнопки со счётчиком:
export const MyComponent = component(() => {
let $count = 0;
function inc() {
$count++;
}
<button class="btn" onclick={inc}>You clicked {$count} times</button>;
});
Для class есть возможность передать массив строк для удобства, а для style — объект свойств. Но это уже плюшки.
Обратная связь
Для того чтобы вручную что-то менять/создавать, подключать сторонние библиотеки, используется обратная связь — функция, которая вызывается, когда узел и все его дочерние элементы добавлены в DOM.
Пример использования обратной связи:
export const MyComponent = component(() => {
function sideEffect(input: HTMLInputElement) {
input.showPicker();
}
<input type="date" callback={sideEffect}/>;
});
Передача данных между компонентами
Данные можно передать между компонентами следующими путями:
от родителя к дочернему компоненту через свойства;
от дочернего к родителю через обратную связь;
от дочернего к родителю и обратно через слоты.
Передача данных через свойства
Свойства — это объект, к названию полей применяются такие же правила, как к названию переменных: то есть если поле начинается с $, то оно передаёт реактивные данные, иначе это обычное поле.
Пример передачи данных через свойства:
interface Props {
userId: string;
$userName: string;
}
const Child = component(({userId, $userName}: Props) => {
<div>{userId} is named {$userName}</div>;
});
const Parent = component(() => {
const id = 1;
let $name = "First";
// Когда мы здесь обновляем имя,
// оно будет автоматически обновлено в дочернем элементе
<Child userId={id} $userName={$name}/>;
});
Передача данных через обратную связь
Компонент, как функция, может что-то возвращать, это значение передаётся родительскому компоненту через обратную связь.
Пример использования в качестве альтернативы forwardRef из React:
const Child = component(() => {
let input: HTMLInputElement | null = null;
<input callback={element => input = element}/>;
return input;
});
const Parent = component(() => {
<Child callback={input => { console.log(input) }}/>;
});
Передача данных через слоты
Слоты от дочернего элемента к родителю передают свойства, а от родителя к дочернему — DOM-представление:
interface Props {
$title: string;
slot?(props: { $name: string }): void;
}
const Child = component(({$title, slot}: Props) => {
<div>
<Slot model={slot} $name={`${$title} is amazing`}/>
</div>;
});
const Parent = component(() => {
let $title = "MyApp";
<Child $title={$title} slot={(($name) => {
<span>{$name}</span>;
})}/>;
});
В случаях когда дочерний компонент ничего не передаёт родителю, содержимое можно написать внутри тега: <Child><span>Text</span></Child>.
Также внутри тега Slot можно добавить содержимое, которое будет отображаться, если родитель не заполнил слот.
Слот — это не просто функция, а полноценный маленький компонент, то внутри него можно добавить derived/computed состояния, эффекты через watch, beforeMount, afterMount и даже beforeDestroy.
Логика и циклы
Это всё работает через специальные встроенные компоненты If, Else, ElseIf и For.
Пример условного текста:
const MyComponent = component(() => {
let $count = 0;
<If $condition={$count > 2}>
Count is too big!
</If>;
});
Пример цикла:
const MyComponent = component(() => {
const arr = [1, 2, 3];
<For model={arr} slot={number => {
Number is {number}
}}/>;
});
Правила про названия свойств относятся и к встроенным компонентам. То есть If будет реагировать на изменения в $condition, а For не будет реагировать на изменения модели — это значит, что модель надо обновлять через push, pull и т.д.
Выводы
Тут есть необходимый минимум, чтобы создать SPA. Это всё уже работает, и дальше — больше: есть стили для компонентов, скрипты для сборки как приложения, так и библиотек под фреймворк. Последнее, что добавили, — это SSG и условия в стиле React как альтернатива тегам If/Else. Проблема в том, что TypeScript иногда ругается.
Проект с открытым исходным кодом, но не знаю, будет ли публикация его названия считаться рекламой. Если хотите помочь, можете заполнить опрос под CustDev: https://docs.google.com/forms/d/e/1FAIpQLSej4oupzzzN1Iy2Yk9gMe4lJyhdAkUJS_WnkRgqW9BzdQo8jA/viewform?usp=publish-editor
Спасибо за внимание!
Комментарии (6)

m6atom
07.11.2025 22:23Как мы пытались сделать фреймворк для фронтенда которого можно выучить за 5 минут и что из этого вышло
Что-то с падежом напутали

vasille Автор
07.11.2025 22:23Русский не мой родной язык, так что может быть ошибки, я пока что не понимаю какой падеж напутал.

goldexer
07.11.2025 22:23Кстати, если вам сложно, используйте профридеры - приложения, которые подсвечивают ошибки в тексте и исправляют по нажатию кнопки. В основном они умеют объяснять причины исправлений. Я вот, например, английские тексты так правлю. А то знаний мало и пишу как селянин, меня очень выручает.

grammidin4eg
07.11.2025 22:23Очередной Фреймворк, который ничего нового не делает. Можно было взять существующий и не тратить зря деньги бизнеса.
artptr86
Кажется, вы изобрели Solid.js, только со слотами
vasille Автор
Интересное наблюдение, я лично думал что он сильно похож на Svelte 4.