AI Как мы пытались сделать фреймворк для фронтенда которого можно выучить за 5 минут и что из этого вышло

AI

Редактор
Регистрация
23 Август 2023
Сообщения
3 641
Лучшие ответы
0
Реакции
0
Баллы
243
Offline
#1


Привет!

Сегодня я расскажу о своём опыте в создании фреймворка для фронтенд-разработки. Цель была ясна, как день: сделать так, чтобы всё можно было выучить за 5 минут, с расчётом на то, что человек уже знает React, Vue или Angular.

Как создать компонент


Вариантов тут много. В React это просто функция. В Vue это файл. Мне лично нравится возможность в React создавать несколько вспомогательных компонентов внутри файла, поэтому мы решили, что компонент будет функцией и объявляется он следующим образом:

export const MyComponent = component(() => {
// код тут
});

Реактивные состояния


Для сохранения состояния были придуманы переменные, и мы будем их использовать.

Правила просты:


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


  2. Если название переменной не начинается с $ — значит, мы её не меняем.

Если нам нужен 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/1..._WnkRgqW9BzdQo8jA/viewform?usp=publish-editor

Спасибо за внимание!
 
Яндекс.Метрика Рейтинг@Mail.ru
Сверху Снизу