Детский ReactJS из 135 строк кода

от автора

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

JavaScript for Babies

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

Мне нужно признаться, что я бэкендер и даже не знаю что меня соподвигло к написанию микрофреймворка на 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. В параметры вызова передается:

  1. Имя тега.
  2. Объект со стилями (в формате JS)
  3. Атрибуты для тега.
  4. Текст, другой тег или компонент (дочерний) или ничего не передается.

Посмотрим на 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 }

Функция обрабатывает переданные ей параметры в той же последовательности:

  1. Создание компонента в дереве документа.
  2. Добавление ему стилей.
  3. Атрибутов.
  4. Добавление дочернего элемента в виде текстового либо другого элемента.

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

Вот в этой функции обработки события мы и вызываем 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *