Child ReactJS with 135 lines of code

There are already a lot of articles about ReactJS. But in order to start studying a novice programmer, you need to find the very beginning where the foundations of its creation are. I wanted to show that there is nothing complicated in understanding the principles of developing a frontend on this framework.

JavaScript for Babies

One method of study allowed me to understand the structure of many complex software systems and it comes down to the fact that you need to rewrite the project you are studying so that you get some kind of unknown beast that at least moves a little and is somewhat similar to its prototype.

I need to admit that I am a back-end and I don’t even know what motivated me to write a microframework in JavaScript. But frankly, this is a desire to learn JS.

In fact, there is a good motivation to create projects in the form of modules / components. In the view of backend programmers, data at best looks like JSON objects, they should be formed into the desired structure and sent where necessary, and then do whatever you want with them. At the front end, in the most primitive version, you have to select the necessary HTML elements by ID and update their attributes, as well as change the text nodes. Make life easier for JavaScript frameworks.

Once I wrote my PHP-Slim-framework, which is far from the original, but it really helps me in PHP projects. Today I want to talk about how I presented the origins of ReactJS development. I wrote one file in 135 lines of code called it bots.js and if you connect it and write a component like in React, you can even see something in the browser. I called him ReactKids.

The idea is to break the project under development into components, add components using javascript and make sure that there are no dependencies between the components.

HTML standard structure:

<!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>

For the application, specify id = root and connect bots.js, then connect the components (or write them yourself) and run it in app.js.

The component in ReactKids looks like this:

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)
}

The component in our case can only be a function in which params is called attr.
Here you should pay attention to why this attr may come in handy. Well, firstly, those who are familiar with the reaction know that it is possible to "lower" the data to the daughter components. That is, a component returns a component that returns a component, and so on until a component that has no children. However, they are also used as packages for data coming from the server. Backend requests are for the most part sent from functions that handle events related to interaction with the user interface.

When the server sends back JSON (usually in text), it needs to be turned into a JS object and done somewhere. This is what params in React and attr in our child implementation are for.
In attr, you can cram the entire JSON object received from the server, or you can only get the best data you need and, possibly, supplemented with other necessary data.

Next, we follow the logic of the adult React - at the beginning of the function, we process the attr object and perform other business affairs. After that you need to return the result of calling the elem () function, the implementation of which is in bots.js. The call parameters are passed:

  1. The name of the tag.
  2. Object with styles (in JS format)
  3. Attributes for the tag.
  4. Text, another tag or component (child) or nothing is passed.

Take a look at 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),
		]
	)
}

Nothing unusual here either. Here is the same more complicated:

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"})
		]
	)
}

As you can see, we have embedded the CirclePict component in the Outer 3 component.

Children, of course, noticed the lack of JSX. In fact, it was invented by lazy programmers and simply facilitates what we write. As a result, JSX tags still turn into JavaScript.

Now you need to see how this is now implemented in bots.js. The framework consists of 3 whole functions, in fact elem () and setAttr (), the first to create, the second to update the state of the component and rend () to display in 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
}

The function processes the parameters passed to it in the same sequence:

  1. Create a component in the document tree.
  2. Adding styles to it.
  3. Attributes.
  4. Adding a child element as a text or other element.

When processing attributes, we also check their type, if a function is received as a value, then it is assumed that this is an event and we hang a wiretap on it. Therefore, it remains only to declare and implement the function indicated as an event.

It is in this event handling function that we call setAttr (), passing the object itself with the updated attr to it. There is one thing but - for each element to be created in attr, you must specify id; otherwise, it will not be updated via setAttr. She by id finds it in the DOM.

As for setAttr (), everything is worse than in React, although it is enough (or almost enough) to understand the principles.

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)
	}
}

As you can see here, only manipulations with the document tree and another fading effect, so that at least it looks and the code looks like a function, not helloworld.

The coolest in our children's framework is the rendering function:

function rend(root, elem) {
	root.appendChild(elem)
}

I noticed that it is difficult for novice programmers to start learning things like React purely psychologically. Seeing hundreds of megabytes of libraries and millions of lines of code, you have to get depressed and look for something else. In particular, they are switching to Vue. Of course, this is also a good framework, but it is even better to understand both approaches to frontend development.

It turns out that complex software environments arise from small but effective solutions. Therefore, I wish good luck to all who seek knowledge of React. May the force be with us!

Source: https://habr.com/ru/post/undefined/


All Articles