
Одна методика изучения позволила мне понять устройство многих сложных программных систем и сводится к тому, что нужно переписать изучаемый проект так, чтобы получился какой нибудь неведомый зверь, который хотя бы чуть-чуть шевелился и был в чем-то схож со своим прообразом.
Мне нужно признаться, что я бэкендер и даже не знаю что меня соподвигло к написанию микрофреймворка на JavaScript. Но если говорить откровенно, это желание изучить JS.
На самом деле есть хорошая мотивация создавать проекты в виде модулей/компонентов. В представлении бэкенд-программистов данные выглядят в лучшем случае как JSON объекты, они должны быть сформированы в нужную структуру и отправлены куда надо, а там делайте с ними, что хотите. На фронтенде в самом примитивном варианте приходится выбирать по ID нужные HTML-элементы и обновлять их атрибуты, а также изменять текстовые узлы. Облегчают жизнь JavaScript-фреймворки.
Однажды я написал свой PHP-Slim-framework, который далек от оригинального, но мне он реально помогает в PHP-проектах. Сегодня мне хочется рассказать о том как я представил истоки разработки ReactJS. Я написал один файл в 135 строчек кода назвал его bots.js и если его подключить и написать компонент подобно как в React, то можно даже что-то увидеть в браузере. Назвал я его ReactKids.
Идея в том, чтобы разбить на компоненты разрабатываемый проект, добавлять компоненты посредством javascript и следить, чтобы между компонентами не возникало зависимостей.
Структура HTML стандартная:
<!doctype html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <title>HelloReactKids</title> <link href="style.css" rel="stylesheet"> </head> <body> <div id="root"></div> <script src="js/bots.js"></script> <script src="js/pict.js"></script> <script src="js/navbar.js"></script> <script src="js/label.js"></script> <script src="js/button.js"></script> <script src="js/app.js"></script> </body> </html>
Для приложения указываем id=root и подключаем bots.js, затем подключаем компоненты (или сами их пишем) и в app.js все это запускаем.
Компонент в ReactKids выглядит так:
function Button(attr) { // attribute components default values if(!attr.labelButton) attr.labelButton = "Click Me" return elem( "button", { padding: "0.65rem", marginTop: "0.4rem", color: "gray", border: "1px solid gray", borderRadius: "0.5rem", background: "#fff", fontSize: "large", cursor: "pointer", }, { id: "btn1", click: btn1Click, }, attr.labelButton ) } function btn1Click(e) { console.log("Clicked!") setAttr(Label({labelContent: "i-i-i!!!"}), 0.6) }
Компонентом в нашем случае может быть только функция в которой params называется attr.
Вот здесь следует уделить внимание для чего этот attr может пригодиться. Ну во-первых, те, кто знаком с реактом знают, что по ним можно «спускать» данные дочерним компонентам. То есть компонент возвращает компонент, который возвращает компонент, и так до компонента у которого нет дочерних. Однако их также используют и в качестве упаковок для данных приходящих от сервера. Запросы на бэкенд по большей части отправляются из функций обрабатывающих события, связанные с взаимодействием с пользовательским интерфейсом.
Когда сервер в ответ присылает JSON (обычно текстом) его нужно превратить в JS-объект и куда-то деть. Вот для этого и требуется params в React и attr в нашей детской реализации.
В attr можно запихивать весь JSON объект, полученный от сервера, а можно только лучшие нужные данные и, возможно, дополненные другими необходимыми.
Дальше следуем логике взрослого React — в начале функции обрабатываем объект attr и выполняем другие хозяйственные дела. После чего нужно возвратить результат вызова функции elem(), реализация которой находится в bots.js. В параметры вызова передается:
- Имя тега.
- Объект со стилями (в формате JS)
- Атрибуты для тега.
- Текст, другой тег или компонент (дочерний) или ничего не передается.
Посмотрим на app.js:
var attr = { labelContent: "Hello React Kids", labelButton: "This button", } rend(document.getElementById("root"), App(attr)) function App(attr) { return elem( "div", { fontFamily: "segoe ui", color: "gray", textAlign: "center", }, { id: "app", }, [ Navbar(attr), Pict(attr), Label(attr), Button(attr), ] ) }
Тут тоже ничего необычного. Вот то же самое посложнее:
function App(attr) { var cpic1 = CirclePict({id: "img1", src: "./img/img1.jpg", height: "200px"}) var cpic2 = CirclePict({id: "img1", src: "./img/img2.jpg", height: "200px"}) var cpic3 = CirclePict({id: "img1", src: "./img/img3.jpg", height: "200px"}) var txt1 = "Кто придумал детское шампанское. Бывает ли от него похмелье и чем отличается от газировки."; var txt2 = "Кто такой Дед Мороз, зачем ему рассказывают стихи и какова максимальная стоимость его подарка."; return elem( "div", { fontFamily: "segoe ui", color: "gray", }, { id: "app", }, [ Pict({id: "logo", src: "./img/logo.png", height: "90%"}), Text({id: "info", text: "you number", direction: "right"}), Label(attr), Outer({id: "outer1", content: [cpic1, cpic2, cpic3]}), Text({id: "txt1", text: txt1, width: "450px"}), Button(attr), Label({id: "lbl2", labelContent: "Дед Мороз по вызову"}), Text({id: "txt2", text: txt2, width: "650px", direction: "center"}), RoundPict({id: "well", src: "./img/well.jpg", height: "280px", width: "550"}) ] ) }
Как видно мы вложили в компонент Outer 3 компонента CirclePict.
Дети, конечно, заметили отсутствие JSX. На самом деле он придуман ленивыми программистами и просто облегчает то, что мы пишем. В итоге теги JSX все равно превращаются в JavaScript.
Теперь нужно посмотреть как вот это вот реализовано в bots.js. Фреймворк состоит из целых 3-х функций, собственно elem() и setAttr() первая для создания, вторая для обновления состояния компонента и rend() для отображения в app.js.
function elem(elt, style, attr, item) { /*element */ if(elt) { // создается узел, как и теги в браузере var el = document.createElement(elt); } else { console.log("elt fail") return } /* style */ if(style) { if(typeof(style) == "object") { for(var itm in style) { el.style[itm] = style[itm] } } else { console.log("style is not object type") } } else { console.log("style fail") } /* attr */ if(attr) { if(typeof(attr) == "object") { for(var itm in attr) { if(typeof(attr[itm]) == "function") { el.addEventListener(itm, attr[itm]) } else { // standart el[itm] = attr[itm] } } } else { console.log("attr is not object type") } } else { console.log("attr fail (add ID for element)") } /* item */ if(item) { if(typeof(item) == "string") { var text = document.createTextNode(item) el.appendChild(text) } else if(typeof(item) == "object") { if(Array.isArray(item)) { if(item.length < 1) { console.log("not items in array") return } item.map(function(itm) { el.appendChild(itm) }) } else { el.appendChild(item) } } else { console.log("text is not string or object type") } } else { console.log("text fail") } return el }
Функция обрабатывает переданные ей параметры в той же последовательности:
- Создание компонента в дереве документа.
- Добавление ему стилей.
- Атрибутов.
- Добавление дочернего элемента в виде текстового либо другого элемента.
При обработке атрибутов также проверяем их тип, если в качестве значения получена функция, то предполагается что это событие и вешаем на него прослушку. Поэтому остается только объявить и реализовать указанную в качестве события функцию.
Вот в этой функции обработки события мы и вызываем setAttr(), передавая ей сам объект с обновленным attr. Тут одно но — для каждого создаваемого элемента в attr необходимо указывать id иначе он не будет обновляться через setAttr. Она по id его находит в DOM.
Что касается setAttr() — тут все хуже чем в React, хотя для понимания принципов ее достаточно (ну или почти достаточно).
function setAttr(update, slow) { if(slow) { var replace = document.getElementById(update.id) var opamax = 0.99 var opaint = 0.01 var outslow = setInterval(function() { opamax = opamax - opaint if(opamax <= 0) { clearInterval(outslow) update.style.opacity = opamax replace.parentNode.replaceChild(update, replace) var inslow = setInterval(function() { opamax = opamax + opaint update.style.opacity = opamax if(opamax >= 1) { clearInterval(inslow) } }, slow) } replace.style.opacity = opamax }, slow) } else { var replace = document.getElementById(update.id) replace.parentNode.replaceChild(update, replace) } }
Как видно здесь только манипуляции с деревом документа и еще эффект затухания, чтобы хотя бы смотрелось и код был похож на функцию, а не helloworld.
Самая крутая в нашем детском фреймворке это функция рендеринга:
function rend(root, elem) { root.appendChild(elem) }
Мною было замечено, что начинающим программистам трудно начать изучение таких штук как React чисто психологически. Удивив сотни мегабайт библиотек и миллионы строк кода приходится впадать в депрессию и отыскивать себе нечто другое. В частности переходят на Vue. Конечно это тоже хороший фреймворк, но еще лучше понимать оба подхода к разработке фронтенда.
Выходит, что сложные программные среды возникают из маленьких, но эффективных решений. Поэтому желаю удачи всем кто стремится к познанию React. Да прибудет с нами сила!
ссылка на оригинал статьи https://habr.com/ru/post/486122/
Добавить комментарий