In recent times, AngularJS has not criticized just lazy, and I, over the past couple of months, have read a lot of articles with criticism. One such article ended with the phrase “I, damn it, do not understand how Angular can be so popular, because it’s so bad!”
“And really,” I wondered, “if he's so bad, then why is he so popular?” And I think I found the answer. The fact is that Angular, like jQuery, has a low threshold of entry, it is simple and intuitive. Yes, of course, you can do complex things in Angular and jQuery, but most people do not use these libraries in this way. What I mean?
Suppose a developer has the following task: “Make a list of reviews, if there are no reviews, invite the user to add a review”
First, let's implement this on Angular:
<h1>Reviews</h1> <article ng-repeat="review in reviews"> {{review.user}}: {{review.text}} </article> <div ng-hide="reviews"> There are no reviews, wanna <a href="#">add</a> some? </div>
And now on React:
var Reviews = React.createClass({ getReviews (){ if(!this.props.reviews.length){ return ( <div> There are no reviews, wanna <a href="#">add</a> some? </div> ) } }, getReview (review){ return ( <article ng-repeat="review in reviews"> {review.user}: {review.text} </article> ) }, render (){ return ( <h1>Reviews</h1> {this.maybeGetAddNew()} {this.props.reviews.map(this.getReview} ) } });
')
As we can see, the code on React is not only longer, but also more confusing. After I finally enter the designers and layout designers, “Markap is always at the bottom of the file, write plain HTML, just use className instead of class,” the designer visits my file, sees
it and tells me, “What are you kidding? What are these braces? I need to add a class to one div, and what is it? I actually taught HTML, but not JS "
Another thing is with Angular, everything is clear to the designer, where he should be added, perhaps he even understands that when he disappears and what is repeated, you just need to scare him, that if he touches the attributes of ng-, then he will have a terrible judgment, hell and death and everything will be fine.
Well, it's the same designers, what to take from them, nubiyo, in one word, we are bearded, severe programmers, we like this code. Yah? And if someone from the team tells us, “Hey, {username}, what is your naked div? He breaks the whole layout for me! ”And we go into the component and see this:
render (){ return ( {this.getPostTitleIfPostHasTitle()} {this.getPostAuthorAvatarIfAuthorHasAvatar()} {this.getPostAuthorNameIfNotAnonymous()} {this.getPostTeaserImageIfPostHasTeaserImage()} {} ) }
and now we need to run through the methods and check the conditions of each in order to find that unfortunate div. Well, if some of the methods are also inherited from mixin ... then there will be unbridled fun for an hour, or even more.
Decision?
Obviously, we need to port a couple of the most used Angular directives to React, for example, ng-show, ng-hide, ng-class and ng-repeat. But how? You can, of course, make a mixin that will run along a hard-line list of “directives” and check them against the host's propy, and then follow the logic associated with this “directive”
var DirectiveMixin = { renderWithDirectives (target){ if(!!this.props.isShownWhen){ return target; } return null; } } var Component = { mixins: [DirectiveMixin], render (){ return this.renderWithDirectives( <div>Hi there!</div> ) } } var userIsLoggedIn = false; var React.renderComponent(<Component isShownWhen={userIsLoggedIn}/>, document.body);
Already not bad, but there are a couple of moments. First, we have to write a component for every small detail that conditionally appears / hides on our site, even if it is a small span. Well, secondly, if we want to use this for some old component, we will have to rewrite the render method using renderWithDirectives, and this is not always possible. And it's also difficult to debug if you have a component.
<AdminBar isShownWhen={userIsLoggedIn}/>
which should appear, but does not appear, is it because
userIsLoggedIn is false, or does AdminBar simply not use renderWithDirectives? These problems can be solved by creating one small component, which we, for readability, call it. It can look something like this:
var It = React.createClass({ mixins: [DirectiveComponents], render (){ return this.renderWithDirectives(this.props.children); } });
And you can use it like this:
<It isShownWhen={reviews.length}> <div>There are no reviews, wanna <a href="#">add</a> some?</div> </It>
And for even greater readability, you can fill this with a syntactic sugar, for example, like this:
<Show when={userIsLoggedIn}> <a href="#">Lougout</a> </Show> <Hide when={userIsLoggedIn}> <a href="#">Login</a> </Hide> <Show unless={userIsLoggedOut}> <a href="#">Lougout</a> </Show> <Hide unless={userIsLoggedOut}> <a href="#">Login</a> </Hide>
Well that's all? We have mix, component It and syntactic sugar in the form of Show / Hide when / unless, it remains to finish ng-class and ng-repeat and can it be on github and npm? Somehow, everything is too simple, let's make life difficult for us, and not just port Angulyar directives, but also make them better. For example, like this:
<Show when="user is logged in"> <a href="#">Logout</a> </Show> <Show unless="user is logged in"> <a href="#">Login</a> </Show> <Hide when="user is logged in"> No account? <a href="#">Sign up!</a> </Hide> <DisplayAll the="reviews for this product"> <div className="review-container"> <div isShownWhenThereAreNo="reviews for this product"> There are no reviews, wanna <a href="#">add</a> some? </div> <Review isTheTemplateForThe="review"/> </div> </DisplayAll>
There is nowhere more understandable, since it is almost English. Now the designer will understand exactly what conditionally appears / disappears, and the developer, who sees this code for the first time, will immediately understand what's going on, he will not have to control / command-click methods of {this.maybeGetThisStuff ()} to figure out . And to whom the situation is unknown, when an evil typewriter writes an evil tick to the bewildered developer “Why do you have a delimiter at new moon ?! I explained a hundred times to you, it should appear when the fast does not have a tambneil, Beijing has an air temperature above 20 degrees
or the fast has a tambneil, in London there is a clear sky
and full moon, but it must not be loved at new moon! "Now he can be explained , so that it describes the behavior of components in English on white already at the layout stage, for example like this:
<Show when="post has no thumbnail, it's 20 C in Beijing or the sky is clear in London and the moon is full"> <div className="delimiter"/> </Show>
The attentive reader is probably asking me "Oh, you demon! Did you come up with a natural language processor, or what? So why do you powder my head with your React! ”
Unfortunately, no, I didn’t invent the AI, and I’m probably not getting the Nobel Prize anymore. The principle of operation of these "string" conditions is similar to the work of function translators, that is, for example, in this case:
__('Please like and subscribe!')
The string
Please like and subscribe! is the key of the associative array, and the translation is performed by reading the value of this key (if any). The spherical "__" function in vacuum probably looks something like this:
function __(str){ return 'undefined' == typeof translations[str] ? str : translations[str]; }
Thus, the phrase
post has no thumbnail is the key to the object, the value of which is boolean. After the coder described the behavior of the components, the programmer needed to finish his coffee and write something like:
getDesigner2CoderTranslations (){ return { "post has no thumbnail, it's 20 C in Beijing or the sky is clear in London and the moon is full": (!this.postHasThumbnail() && this.getTemperature('Beijing') == 20) || (this.getSkyState('London') == this.SKY_CLEAR && this.getMoonPhase() == this.MOON_FULL) } }
Not bad, but where will this object with “translations” be stored, and how will we transmit it? Well, I thought about it and decided that it would be best to use the undocumented React function, called context. This is something like a sermon, which is transmitted not from the immediate parent to the immediate child, but from the higher component to all subordinate children, that is, children of children, children of children of children, etc. Here is
an unofficial introduction .
And, armed with new knowledge, let's end:
childContextTypes: { monstroLanguage: React.PropTypes.object }, getChildContext: function() { return { monstroLanguage: { "post has no thumbnail, it's 20 C in Beijing or the sky is clear in London and the moon is full": (!this.postHasThumbnail() && this.getTemperature('Beijing') == 20) || (this.getSkyState('London') == this.SKY_CLEAR && this.getMoonPhase() == this.MOON_FULL) } }; }
Mixin and directives declare that they expect such a context, and all the “string” conditions will be searched for in it.
contextTypes: { monstroLanguage: React.PropTypes.object },
Beautifully laid out, fraerok. And where can you look at your magic?
I put the code on the
githab . Now I finish the documentation (more precisely, I transfer from bitbucket markdown to github markdown), I test the examples and at the same time. By the evening, I think, I will finish, and lay out on npm. I would like to hear your opinion, advice, which directives would not hurt to add. Pull requests, of course, are very welcome.