React.js Zobacz samouczek zarządzania stanami
Opublikowany: 2022-03-11Jednym z największych i najczęstszych problemów we front-endowym tworzeniu stron internetowych jest zarządzanie stanem. Twórcy front-end freelance, tacy jak ja, nieustannie koncentrują się na utrzymywaniu synchronizacji obiektu stanu z jego widokiem i reprezentacją DOM. Użytkownicy mogą wchodzić w interakcję z aplikacją na wiele sposobów, a zapewnienie czystego przejścia z jednego stanu do drugiego jest dużym zadaniem.
Trochę historii
Nie tak dawno aplikacje internetowe miały znacznie prostszy przepływ danych. Przeglądarka wyśle żądanie do serwera, cała logika aplikacji zostanie wykonana na serwerze, a pełny widok HTML zostanie wysłany z powrotem do przeglądarki w celu prezentacji użytkownikowi. Kolejne działania użytkownika (takie jak kliknięcia, przesłanie formularza itp.) ponownie wywołałyby ten sam przepływ. Aplikacje nie musiały martwić się o stan użytkownika, a każdy widok można było odtworzyć, wysyłając nowe żądanie do serwera.
Jednak aplikacje internetowe rosły w swojej złożoności, a wymagania użytkowników dotyczące UI/UX również rosły. Ponowne ładowanie całej strony, gdy tylko jedna jej część się zmienia, było nieefektywne i powolne. Potrzebowaliśmy szybkiej, zgryźliwej i responsywnej interakcji z natychmiastowym wpływem na interfejs użytkownika.
Na ratunek przyszedł JavaScript. Deweloperzy zaczęli pisać znaczne ilości kodu, który był wykonywany w przeglądarce przed wysłaniem żądania na serwer. jQuery przyniosło również znaczne postępy w tworzeniu front-endowych stron internetowych, ponieważ zapewniło proste i skuteczne, gotowe do użycia możliwości, takie jak walidacja po stronie klienta, okna modalne, komunikaty alertów, animacje, a nawet częściowe aktualizacje stron oparte na Ajax.
Zrozumienie złożoności
Rzućmy okiem na prosty przykład oceny siły hasła. Jeśli hasło jest prawidłowe, pole wprowadzania powinno mieć zieloną ramkę i powinien wyświetlać ładną wiadomość. Jeśli hasło jest słabe, pole wprowadzania powinno mieć czerwone obramowanie i powinien wyświetlać komunikat ostrzegawczy. Możemy również pokazać uśmiechniętą buźkę, gdy hasło jest wystarczająco silne.
Poniższy kod pokazuje, jak można to zrobić za pomocą manipulacji DOM. Jest tu wiele „jeśli”, a kod nie jest łatwy do odczytania.
if (hasInputBorder()) { removeInputBorder(); } if (text.length === 0) { if (hasMessage()) { removeMessage(); } if (hasSmiley()) { removeSmiley(); } } else { var strength = getPasswordStrength(text); if (!hasInputBorder()) { addInputBorder(); } var color = (strength == 'weak' ? 'red' : 'green'); setInputBorderColor(color); var message = (strength == 'weak' ? "Password is weak" : "That's what I call a password!"); if (hasMessage()) { setMessageText(message); } else { addMessageWithText(message); } if (strength == 'weak') { if (hasSmiley()) { removeSmiley(); } } else { if (!hasSmiley()) { addSmiley(); } } }
Jak pokazano powyżej, najpierw musimy sprawdzić, czy użytkownik w ogóle podał hasło i zająć się przypadkiem, w którym pole hasła jest puste. I we wszystkich przypadkach musimy upewnić się, że wszystkie powiązane elementy DOM są odpowiednio aktualizowane. Obejmuje to wiadomość, ramkę i uśmiechniętą buźkę.
Nasze pole hasła może mieć jeden z trzech stanów: pusty, słaby lub silny. Jak już wspomniano, mamy trzy różne elementy DOM, na które wpływa stan pola hasła. Obsługa wszystkich kombinacji i upewnienie się, że nasz widok jest prawidłowo wyświetlany, zwiększa złożoność cyklomatyczną nawet dla tak prostego fragmentu kodu, jak ten.
DOM działa w trybie zachowanym , co oznacza, że zapamiętuje tylko aktualny stan. Aby zmodyfikować nasz widok, musimy podać instrukcje dla każdego elementu DOM i zaprogramować przejście.
Kodowanie przejść zamiast stanów może być złożone. Liczba gałęzi i sprawdzeń, które musimy wykonać w naszym kodzie, rośnie wykładniczo wraz z liczbą zarządzanych stanów widoku.
W naszym przykładzie zdefiniowaliśmy trzy stany widoku, co dało nam 3 * 2 = 6
przejść. Ogólnie rzecz biorąc, przy danych N stanach, mamy N * (N - 1) = N^2 - N
przejść, które musielibyśmy modelować. Pomyśl tylko o zwiększonej złożoności, jeśli dodamy do naszego przykładu czwarty stan.
Zazwyczaj jest za dużo kodu związanego z modelowaniem przejść . Byłoby znacznie lepiej, gdybyśmy mogli po prostu zdefiniować nasze stany widzenia i nie martwić się wszystkimi szczegółami przejścia z jednego stanu do drugiego.
Zmniejszenie złożoności
Zakładając, że moglibyśmy zadeklarować stan widoku na podstawie stanu modelu, zamiast jawnie kodować przejście z jednego stanu do drugiego, moglibyśmy mieć coś takiego:
var strength = getPasswordStrength(text); if (text.length == 0) { return div(input({type: 'password', value: text})); } else if (strength == 'weak') { return div( input({type: 'password', value: text, borderColor: 'red'}), span({}, "Weak") ); } else { return div( input({type: 'password', value: text, borderColor: 'green'}), span({}, "That's what I call a password!"), img({class: 'icon-smiley'}) ); }
Tutaj mamy trzy proste gałęzie kodu, reprezentujące trzy możliwe stany naszej aplikacji. Po prostu zwracamy specyfikację widoku w każdej gałęzi, w zależności od stanu modelu. Cały kod manipulacji DOM jest usuwany; podajemy tylko informacje o tym, czego chcemy, a nie o tym, jak się tam dostać.
Chociaż to podejście znacznie zmniejsza złożoność kodu, zakłada również, że ktoś lub coś innego zajmie się faktyczną manipulacją DOM w naszym imieniu.
W tym miejscu wkracza React. React upewni się, że stan widoku jest natychmiast zarządzany i aktualizowany w oparciu o stan bazowego modelu danych.
Reakcja
React to biblioteka JavaScript stworzona przez Facebooka. Jest przeznaczony do obsługi części interfejsu użytkownika aplikacji internetowych. Możesz myśleć o tym jako o V w architekturze MVC. Jest bardzo skoncentrowany. Nie przyjmuje żadnych założeń dotyczących reszty stosu technologicznego i nie obsługuje niczego poza renderowaniem komponentów. Nie zapewnia żadnych mechanizmów routingu, modeli ani innych funkcji, które zwykle są zawarte w większych platformach. W ten sposób możesz go mieszać i używać z dowolną inną biblioteką lub frameworkiem.
React pozwala nam zdefiniować UI jako drzewa komponentów kompozytowych. Programista React definiuje te komponenty, określając funkcję render, która opisuje komponent, biorąc pod uwagę stan wejściowy. Ta funkcja powinna być czysta (tj. nie powinna mieć żadnych skutków ubocznych ani zależeć od czegokolwiek innego niż jej jawne dane wejściowe).
Funkcja render zwraca opis widoku, który React wywołuje wirtualny DOM . Pomyśl o tym jako o obiekcie JavaScript odpowiadającym wyrenderowanemu elementowi DOM.
Kiedy zmienisz stan komponentu, po prostu ponownie renderuje siebie i wszystkie jego elementy podrzędne, zwracając nowy wirtualny DOM .
Co więcej, React nie wykona prostego zastąpienia HTML podczas przechodzenia z jednego stanu do drugiego. Znajdzie różnicę między poprzednim a nowym stanem i obliczy najbardziej efektywny zestaw operacji DOM do wykonania przejścia.
Nawet nie mając na uwadze wydajności, redukcja złożoności kodu jest sama w sobie znacząca i pozwala nam skoncentrować nasze wysiłki na bardziej unikalnych i złożonych częściach naszej aplikacji.
Aby uzyskać nieco więcej konkretów, tak wygląda nasz przykład z samouczka, używając Reacta do zarządzania stanami widoku.
UWAGA: Poniższy przykładowy kod został napisany w preprocesorze JSX, co jest powszechnym sposobem pisania interfejsu użytkownika opartego na React.
function getPasswordStrength(text) { // Some code that calculates the strength given the password text. } var PasswordWithStrength = React.createClass({ getInitialState: function() { return {value: ''}; }, render: function() { var strength = getPasswordStrength(this.state.value); if (this.state.value.length == 0) { return <div> <input type="password" value={this.state.value} onChange={this.handleInputChange} /> </div>; } else if (strength == 'weak') { return <div> <input type="password" value={this.state.value} onChange={this.handleInputChange} style={ {border: '1px solid red'} } /> <span style={{color: 'red'}}>Weak!</span> </div>; } else { return <div> <input type="password" value={this.state.value} onChange={this.handleInputChange} style={ {border: '1px solid green'} } /> <span style={{color: 'green'}}>That's what I call a password!</span> <Emoji value="smiley" /> </div>; } }, handleInputChange: function(ev) { this.setState({value: ev.target.value}); } }); React.render(<PasswordWithStrength />, document.body);
Komponent Emoji
, który jest renderowany, gdy siła hasła jest OK z <Emoji value="smiley" />
jest po prostu kolejnym składnikiem niestandardowym (tak jak PasswordWithStrength
). Definiuje się to tak:

var Emoji = React.createClass({ render: function() { var emojiSrc = this.props.value + '.png'; return <img src={emojiSrc}></img>; } });
React.js kontra inne
Szczerze mówiąc, istnieją inne frameworki JavaScript po stronie klienta (takie jak Ember, Angular, Knockout i inne), które również rozwiązały problem zarządzania stanem widoku, a nawet dodały do niego więcej funkcji. Dlaczego więc miałbyś chcieć używać Reacta zamiast jakiegokolwiek innego frameworka?
React ma dwie kluczowe zalety w porównaniu z większością innych bibliotek.
Brak powiązania danych
Niektóre inne alternatywne struktury używają wiązania danych do mapowania elementów DOM na właściwości stanu i utrzymywania ich synchronizacji, obserwując zmiany właściwości. Takie podejście umożliwia jednorazowe renderowanie widoku, przy czym każda zmiana powoduje wyzwolenie tylko modyfikacji elementów DOM, których to dotyczy. Inne alternatywy używają sprawdzania brudnego ; to znaczy, zamiast obserwować indywidualne zmiany właściwości, po prostu dokonują porównania między poprzednim a nowym stanem. React jest bardziej podobny do tego drugiego podejścia, ale zamiast porównywać stany, porównuje reprezentacje widoku.
React nie ma wiązania danych. Deweloper powinien wywołać metodę setState
lub ponownie zrenderować górny składnik, gdy stan zostanie zmieniony. Obejmuje przepływ jednokierunkowy, od stanu do widoku.
Ta koncepcja jest łatwa do przyjęcia, ponieważ programiści na ogół nie myślą o wiązaniu danych. Nacisk kładziony jest na wizualną reprezentację danych. Dlatego nie musisz myśleć o zależnych właściwościach, formatowaniu, wiązaniu specjalnych znaczników HTML itp. W React po prostu ponownie renderujesz komponent, gdy model się zmieni.
Aby zrozumieć różnicę w zarządzaniu stanem widoku, porównajmy Ember i React . Stworzymy obiekt person
, który wypisze pełne imię i nazwisko pisane wielkimi literami. Po dwóch sekundach przeprowadzimy symulację zmiany i zaktualizujemy widok.
// EXAMPLE USING EMBER App = Ember.Application.create(); App.Person = Ember.Object.extend({ firstName: null, lastName: null, fullName: function() { return this.get('firstName') + ' ' + this.get('lastName'); }.property('firstName', 'lastName') }); var person = App.Person.create({ firstName: "John", lastName: "Doe" }); Ember.Handlebars.helper('upcase', function(value) { return value.toUpperCase(); }); App.IndexRoute = Ember.Route.extend({ model: function () { return person; } }); setTimeout(function() { person.set('firstName', 'Harry'); }, 2000); // Templates: <script type="text/x-handlebars"> <h2>Welcome to Ember.js</h2> {{outlet}} </script> <script type="text/x-handlebars" data-template-name="index"> The current user is: {{upcase model.fullName}} </script>
Stworzyliśmy obiekt z właściwościami firstName
, lastName
i fullName
. Ponieważ Ember obserwuje zmiany właściwości, musieliśmy określić, że fullName
zależy od firstName
i lastName
. W tym celu dodaliśmy .property('firstName', 'lastName')
podczas definiowania fullName
.
Po dwóch sekundach person.set('firstName', 'Harry');
jest wykonywany. To wywołało aktualizację widoku i jego powiązanie.
Teraz zróbmy to samo w React.
// EXAMPLE USING REACT var CurrentUser = React.createClass({ render: function() { return <div>The current user is: {this.props.user.fullName().toUpperCase()}</div>; } }); var person = { firstName: 'John', lastName: 'Doe', fullName: function() { return this.firstName + ' ' + this.lastName; } }; var currentUser = React.render(<CurrentUser user={person}/>, document.body); setTimeout(function() { person.firstName = 'Harry'; currentUser.setProps({user: person}); }, 2000);
Mimo że kod Ember jest prosty i łatwy do odczytania, oczywiste jest, że React wygrywa w prostocie. person
jest zwykłym obiektem JavaScript, a fullName
jest po prostu funkcją.
Brak szablonów
Każda alternatywna platforma ma inny sposób obsługi szablonów. Niektóre z nich używają stringów, które są kompilowane do JavaScript, podczas gdy inne używają bezpośrednio elementów DOM. Większość z nich używa niestandardowych atrybutów i tagów HTML, które są następnie „kompilowane” w HTML.
Szablony nie są częścią kodu JavaScript. Z tego powodu każda alternatywa wymaga niestandardowego sposobu reprezentowania typowych operacji, warunków warunkowych, iteracji, wywoływania funkcji itp. Wszystkie kończą się tworzeniem nowego pseudo-języka, którego programiści muszą się nauczyć.
W React nie ma szablonów, wszystko jest zwykłym, starym JavaScriptem.
React wykorzystuje pełną moc JavaScript do generowania widoku. Metodą render komponentu jest funkcja JavaScript.
JSX jest dostępny jako preprocesor, który zamienia „składnię podobną do HTML” w normalny JavaScript, ale JSX jest opcjonalny i możesz używać standardowego JavaScript bez żadnych preprocesorów. Możesz także skorzystać z istniejących narzędzi JavaScript. Linters, preprocesory, adnotacje typów, minifikacja, eliminacja martwego kodu itp.
Ponownie użyjmy konkretnego przykładu, aby porównać React z jednym z alternatywnych frameworków do zarządzania stanem widoku.
Poniższy samouczek jest przykładem użycia AngularJS do wyświetlenia listy hashtagów i liczby tweetów dla każdego z nich. Lista jest sortowana według liczby, a w przypadku braku hashtagów wyświetlany jest komunikat.
<!-- EXAMPLE USING ANGULAR --> <div ng-controller="MyCtrl"> <ul ng-show="hashTags.length > 0"> <li ng-repeat="hashTag in hashTags | orderBy:'tweetCount':true"> {{hashTag.name}} - {{hashTag.tweetCount}} tweets </li> </ul> <span ng-show="hashTags.length == 0">No hashtags found!</span> </div>
Aby móc stworzyć tę listę, programista musi zapoznać się z AngularJS directives
, ng-show
i ng-repeat
. Następnie musi poznać AngularJS filters
, aby zrozumieć orderBy
. Dużo pracy przy prostych rzeczach, takich jak tworzenie listy!
Rozważmy teraz przykład React, który robi to samo:
// EXAMPLE USING REACT function byTweetCountDesc(h1, h2) { return h2.tweetCount - h1.tweetCount; } //... render: function() { if (this.state.hashTags.length > 0) { var comps = this.state.hashTags.sort(byTweetCountDesc).map(function(hashTag, index) { return <li key={index}> {hashTag.name} - {hashTag.tweetCount} tweets </li>; }); return <ul>{comps}</ul>; } else { return <span>No hashtags found!</span> } }
Nawet jeśli użyliśmy „bardziej zaawansowanego” podejścia i JSX, każdy programista z podstawową znajomością JavaScriptu może z łatwością przeczytać powyższy kod i zrozumieć, co robi. Standardowe sprawdzenie warunkowe przy użyciu if
, iteracji przy użyciu map()
i standardowego 'sort()' przychodzi naturalnie każdemu programiście, więc nie trzeba się uczyć dodatkowej składni ani innych koncepcji.
Wniosek
Głównym wnioskiem z tego samouczka React.js jest fakt, że React pozwala skupić się na faktycznym zarządzaniu stanem widoku, a nie na przejściach, co upraszcza pracę i aplikację.
Krzywa uczenia się do przyjęcia Reacta jest dość trywialna. Bez niestandardowego języka szablonów do opanowania, bez wiązania danych, o które trzeba się martwić, a wszystko sprowadza się do funkcji JavaScript, które opisują elementy interfejsu użytkownika.
Aby dowiedzieć się więcej o uproszczeniu kodu aplikacji za pomocą React, spójrz na wykład Stevena Luschera, Decomplexifying Code with React.
Oto dodatkowa lektura dla każdego, kto chce zrobić kolejny krok i zacząć korzystać z Reacta:
- http://jlongster.com/Removing-User-Interface-Complexity,-lub-Dlaczego-React-is-Awesome