Good afternoon, dear readers.

Recently, such a wonderful framework as
React.js is increasingly mentioned on Habré. I have been working with him for 4 months, so I decided to share the experience of use. It was decided to make a small series of articles that should be the most concise full guide to the framework. This is my first post on Habré, so please do not judge too harshly. My main task is to talk about approaches and practices, the secondary one is to find out from people who used React, how they work with it and how they solved certain cases that arose in their work. And of course to expand the framework community. Beginning, I designed in the form of a small abstract-crib. And then just practice.
* in a nutshell famous series of books from the publisher o'Reilly, the expression itself means: to speak without further explanation (author's note)In the first article I will show how to make the simplest page, which will contain the very minimum of dynamics. The main approaches, techniques and some small tricks. If possible, the entire code will be reviewed to the smallest detail. This will help me a small repository:
https://github.com/Aetet/react-article .
Each change has a corresponding tag. Descriptions for each of them will be announced along the way.
')
Abstract
What is it and what does it eat?
React is supposedly the only View framework from Facebook. Yes, the developers are a little heart-stopping, which you can't do for marketing. In fact, React is a view-oriented MVC framework, although it does not look like it at first glance. By the end of the article I will demonstrate the presence of all three elements. For convenience of writing to React, an XML-like syntax is used. Since the default constructs are similar:
<div>Hello {this.props.name}</div>
would obviously be invalid for JS, then the JSX format was coined. Visibility and a quick start for the coder, the main advantages of the approach. It is processed using React-tools and turns into a regular JS-construction of the form:
React.DOM.div(null, "Hello ", this.props.name);
About React has already been told:
Basic terms:
Helper is an object with many different methods that perform conversion operations. For example, they format the date, create textual representations of the classes for the view, etc.
Manager - an object containing methods for interacting with the server part of the application. Saving data from the server is always an important part, so it has a special place.
Props - the data that is passed to the widget during rendering.
State - stores the internal state of the widget (for example, it is open, active, hidden, etc.)
Component - A block of functionality that performs a specific function.
The main features of the framework:
- Declarative approach
- Stateless components.
- Normalize DOMEvent
- Own DOM implementation
- Unix way following.
- Ability to use the composition component'ov and reuse them.
Practice
Well, with marketing and introductory part sorted out, go to the point: creating a widget. HelloWorld can be viewed on the React page. Therefore, let's write down something more complicated - a booking page, for example.
Formulation of the problem
What do we post on it? What does a booking business require? Let's first make it simple:
- Form with the name, surname, gender of the person for whom we will reserve a place.
- Submit button.
The task is very simple, but on it you can run in basic cases and concepts well. Time to uncover tools and remember the github
https://github.com/Aetet/react-articleThe project build is built using gulp, modular system for webpack, stylus, bootstrap. Everything is set up and ready to use. Therefore, you need to make a minimum of preparations:
- download dependencies via npm install.
- run gulp
In this article I will not discuss the configuration and use of gulp, webpack. I am sure that an inquisitive Habra reader will find information on this issue.
Organization structure.
Chaos always conquers order, for it is better organized.
Terry Pratchett
Initial state: start tag.
End state: first-static tag.
First, let's outline the usual static content, and then go deeper:
We start from the base:
var React = require('react'); var Index = React.createClass({ render: function () { return ( <div> <div>Booking</div> </div> ); } }); React.renderComponent( <Index /> , document.querySelector('.appl'));
Creating a new widget consists of several parts:
- Create a class for the index component using React.createClass.
- Create a render function that contains a template.
- We render the class, passing it the necessary initial data props and the selector where the element will be rendered.
So we got our first Index.
We start to go from top to bottom and decompose our application. The root element is the component Booking. Plus, we have two main blocks on the page:
- Information about the passenger.
- Submit button.
Therefore, we create the components PassengerInfo and Submit. Inside we just put static HTML.
var React = require('react'); var PassengerInfo = React.createClass({ render: function () { return ( <div> <span > <label></label> <input type="text" name="firstName" /> </span> <span > <label></label> <input type="text" name="lastName" /> </span> <div className="controls"> <span className="btn-group"> <span className="btn">M</span> <span className="btn">F</span> </span> </div> </div> ); } }); module.exports = PassengerInfo;
Creating a state.
I have repeatedly noticed that God, showing a passion for cheap literary cliches, often sends us weather that reflects our internal state.
Stephen Fry.
Initial state: first-staticEnd state: initialState-propTypesNow that we’ve created the basic structure, we’ll add a state to our booking widget.
Add a getInitialState method to the Booking.
getInitialState: function () { return { firstName: '', lastName: '', gender: '' }; }
When the booking widget is first rendered, the function fills this.state with the returned object. Pass values from state to component PassengerInfo.
<PassengerInfo firstName={this.state.firstName} lastName={this.state.lastName} gender={this.state.gender} />
firstName, lastName, gender are now props for the component PassengerInfo.
To make sure that the correct props types were passed to us, we add validation:
propTypes: { firstName: React.PropTypes.string, lastName: React.PropTypes.string, gender: React.PropTypes.string }
Now, if the type of one of the properties is not string, in the console we will get a wonderful warning:
Warning: Invalid prop `firstName` of type` object` supplied to `PassengerInfo`, expected` string`.The getInitialState description in the documentation is here:
facebook.imtqy.com/react/docs/component-specs.html#getinitialstatepropTypes:
facebook.imtqy.com/react/docs/component-specs.html#proptypesChange state.
If my daughter gave me a badge with the word "idiot", I would put it on.
Hugh Laurie
Initial state: initialState-propTypes tagEnd state: handleChange tag
Perhaps it is worth learning a little about tracking what is going on in the application and how to manage handlers correctly.
To keep track of input changes, add an onChange handler:
var PassengerInfo = React.createClass({ handleChange: function (e) { var target = e.target, name = target.name, value = target.value; this.props.onChange(name, value); }, render: function () { return ( ... <input onChange={this.handleChange} type="text" name="firstName" value={this.props.firstName} /> ... ); } });
In React, it is slightly different from the usual onChange behavior.
Read moreArgument
(e)
in this case is SynteticReactEvent, not DOMEvent, as it may seem at first glance. The object was specially prepared and grown under laboratory conditions for cross-browser work. With some reservations.
Hm Apparently, a very important moment of data transfer to the parent widget has arrived. There is nothing easier. We just need to knock the neighbor on top of the mop on the ceiling. To do this, we add the onChange function to Booking in props PassengerInfo.
var Booking = React.createClass({ handleChange: function (name, value) { var state = {}; state[name] = value; this.setState(s tate); }, render: function () { return ( <div> <PassengerInfo firstName={this.state.firstName} lastName={this.state.lastName} gender={this.state.gender} onChange={this.handleChange}/> <Submit /> </div> ); } });
Thus, the state of the top widget Booking is always up to date. And the state reflects the state of the child widgets.
You can read more about event handlers that elements have here:
facebook.imtqy.com/react/docs/events.htmlfacebook.imtqy.com/react/docs/dom-differences.htmlAbout support for older browsers:
facebook.imtqy.com/react/docs/working-with-the-browser.html#browser-support-and-polyfillsReusable widgets.
We do not know what is inside the puppy and what he feels. Maybe this is also a pretense.
Isaac Asimov
Initial state: handleChange tag.
Final state: Gender-Switcher tag.
Personally, I like small views with very simple interaction inside. In PassengerInfo, for my taste, a bit too much layout. Therefore, I will select the gender selection widget into a separate GenderSwitcher and make it reusable. Reusable widgets are usually stored in Common / Widgets.
For a widget to be truly reusable, it needs to have clear input and output data. Obviously, for this widget, the default floor will be input, the selected floor will be output. Well, it's simple. We act by analogy with the previous example and get:
var GenderSwitcher = React.createClass({ propTypes: { gender: React.PropTypes.string }, handleClick: function (e) { var target = e.target, type = target.dataset.type; this.props.handleGender(type); }, render: function () { return ( <span className="btn-group"> <span data-type="m" className="btn" onClick={this.handleClick} ></span> <span data-type="f" className="btn" onClick={this.handleClick} ></span> </span> ); } });
Use the data attribute to get information about which item we clicked on. To access these attributes, simply access the dataset property of the element. Unfortunately, I do not remember the exact place in the official documentation where this is said. Therefore, I will be glad to add an article with a link about this moment.
ATTENTION!!! This approach may become obsolete with the new version:
github.com/facebook/react/issues/1259Of course, you can do all the processing through the same handleChange handler, but I prefer to stick with the
Unix way . One function - one appointment. Therefore, add the handleGender handler.
var PassengerInfo = React.createClass({ ... handleGender: function (type) { this.props.onChange('gender', type); }, ... });
Now, to make sure that when the floor changes, it is stored in the state, we put console.log in the top widget Booking.jsx. Open the page, click on M, and indeed - the floor has changed in the console.
Controlled-component time
People are too receptive. If someone tries to control you, you submit. Sometimes I think you like it.
Doctor Who
Baseline : GenderSwitcher Tag
End state: tag
controlled .
Let's make the input display the changes and be controlled.
To do this, add value to input, in which we will write the value of incoming props.
<input onChange={this.handleChange} type="text" name="firstName" value={this.props.firstName} />
Attention!!! If we just do
<input type="text" name="firstName" value="Vasya" />
then the input will not change.
More about this behavior:
facebook.imtqy.com/react/tips/controlled-input-null-value.htmlI recommend doing everything input as controlled as possible in order to have full control over each input. This will help to change the behavior of the element if new requirements come.
Utility features.
Just because they serve you.
Clerks
Baseline: controlled tag
End state: viewHelper tag
The logic for the GenderSwitcher works, but there is no visual display of the change. Well, add it.
Essentially, a GenderSwitcher change is just a change to this.props.gender
Therefore, we will do the following: depending on the incoming this.props.gender, we will add or delete the active class of the element. Since I'm still sticking with Unix-way, I will do it with the external helper function.
It will be very simple:
var GenderViewHelper = { generateMaleClass: function (gender, defaultClasses) { var className = defaultClasses.join(' '), activeClass = (gender === 'm') ? ' btn-primary' : ''; return className + activeClass; } };
Similarly, we do for female.
Yes, of course, you can refactor, highlight the global helper function for classes, but, in my opinion, this is a premature optimization.
Plus there is another trick:
facebook.imtqy.com/react/docs/class-name-manipulation.htmlThe render function will change a little:
render: function () { var maleClass = GenderViewHelper.generateMaleClass(this.props.gender, ['btn']); var femaleClass = GenderViewHelper.generateFemaleClass(this.props.gender, ['btn']); return ( <span className="btn-group"> <span data-type="m" className={maleClass} onClick={this.handleClick} ></span> <span data-type="f" className={femaleClass} onClick={this.handleClick} ></span> </span> ); }
Challenge server data spirits
- I can call spirits out of the abyss!
- And I can, and everyone can.
The only question is whether they will come to the call.
Shakespeare. Henry IV.
Initial state: viewHelper tag
Final state: save-server tag
The article goes to the finish line, so it's time to fix and save. Send our data to the server.
To do this, add a handler to the button by clicking:
var Submit = React.createClass({ handleClick: function () { this.props.onSubmit(); }, render: function () { return ( <div className="form-actions"> <button className="btn btn-primary" onClick={this.handleClick}>Submit</button> </div> ); } });
And we will catch this handler in the root widget.
var Booking = React.createClass({ ... handleSubmit: function () { var dataForServer = clone(this.state); BookingManager.saveData(dataForServer) .then(function (successMsg) { alert(successMsg); }) .fail(function (err) { console.log('err when save', err); }); }, render: function () { return ( ... <Submit onSubmit={this.handleSubmit}/> ... ); } });
Passing objects into methods without prior copying is a bad practice for my taste. Few people change the object inside the function, some hack impose, and then look, pray, knock on the tambourine. Therefore, it is good practice to work with the immutable data structure. Of course, my implementation of cloning creepiness is imperfect, but no one bothers to connect normal library functions.
But what about us then the Manager, which saves data, will look like.
var Vow = require('vow'); var BookingManager = { saveData: function (data) { var dfd = Vow.defer(); setTimeout(function () { dfd.resolve('Hello, Habra!' + JSON.stringify(data)); }, 1000); return dfd.promise(); } }; module.exports = BookingManager;
As you can see, saveData is a simple emulation of the response from the server. Defer is needed mainly for convenient operation, if you suddenly need to wait for several events, and to fully support asynchrony.
We begin to emerge a clear life cycle component'a:
- Render Views
- Views respond to handlers
- Handlers pass control callbacks to the root widget - Controller.
- We save data in state storage.
- State generates a change event.
- Views begin to re-render.
- And the situation went in a new circle.
Instead of an afterword.
Still, nothing MVC-shnoe React is not alien. M - state. Yes, we store our data in the state, what is not the usual model? Moreover, it radiates events and causes changes in the component's life cycle. V - the whole function of render, is one large View, which was crossed together with the template. C - root widget, an ideal candidate for the controller role, in it
Business meets money, data begins its journey through the props component - V and lies in demand in M - state.
Thus, the creators of React very gracefully approached the implementation of a long-known pattern. Instead of dividing MVC into individual letters, they divided the lifecycle between them. And it turned out, frankly, very cool. Now I can say with confidence that React is my favorite tool, working with which is a great joy. Of course, he has his flaws and his sores. However, for each of them, you can find a solution or hack. After all, people who have a hackathon is part of the corporate culture are working on it now.
If the habrasoobshchestvu like the article, I will write another article about React.
In it, I plan to cover the following topics:
- Scale refactoring.
- Scaling from a simple form to a complex form with complex dynamic behavior.
- The second coming of the Manager or the requirements have changed again.
- React Reefs
- How to escape from Evil jQuery.
- UPD: Stateless components
- Routing and general application organization
- Flux
In my work I used the following tools:
- React.JS library ( https://github.com/facebook/react )
- Gulp build system ( https://github.com/gulpjs/gulp )
- Webpack modular system ( https://github.com/webpack/webpack )
- Connect framework ( https://github.com/senchalabs/connect )
- Library for promise and defer vow ( https://github.com/dfilatov/vow )
PS
I dedicate this article to my mother - my first teacher.