React.js Просмотр руководства по управлению состоянием

Опубликовано: 2022-03-11

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

Из этого руководства по React.js вы узнаете больше об управлении состоянием представления.

Немного истории

Не так давно веб-приложения имели гораздо более простой поток данных. Браузер отправит запрос на сервер, вся логика приложения будет выполняться на сервере, а полное HTML-представление будет отправлено обратно в браузер для представления пользователю. Последующие действия пользователя (такие как клики, отправка форм и т. д.) снова вызовут тот же поток. Приложениям не нужно было беспокоиться о состоянии пользователя, и каждое представление можно было создать заново, отправив новый запрос на сервер.

Однако сложность веб-приложений росла, и требования пользователей к UI/UX также росли. Перезагрузка всей страницы, когда меняется только одна ее часть, была неэффективной и медленной. Нам нужно было быстрое, быстрое и отзывчивое взаимодействие с немедленным воздействием на пользовательский интерфейс.

На помощь пришел JavaScript. Разработчики начали писать значительный объем кода, который выполнялся в браузере до отправки запроса на сервер. jQuery также привнес значительный прогресс в веб-разработку переднего плана, поскольку он предоставил простые и эффективные готовые возможности, такие как проверка на стороне клиента, модальные окна, предупреждающие сообщения, анимация и даже частичные обновления страниц на основе Ajax.

Понимание сложности

Давайте рассмотрим простой пример оценки надежности пароля. Если пароль в порядке, поле ввода должно иметь зеленую рамку и отображать красивое сообщение. Если пароль слабый, поле ввода должно иметь красную рамку и отображать предупреждающее сообщение. Мы также можем показывать смайлик, когда пароль достаточно надежный.

Следующий код демонстрирует, как это можно сделать с помощью манипуляций с DOM. Здесь много «если», и код не очень легко читается.

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

Как показано выше, сначала нам нужно проверить, предоставил ли пользователь вообще какой-либо пароль, и обработать случай, когда поле пароля пусто. И во всех случаях нам нужно убедиться, что все связанные элементы DOM правильно обновлены. Это включает в себя сообщение, границу и смайлик.

Наше поле пароля может находиться в одном из трех состояний: пустое, слабое или сильное. И, как уже отмечалось, у нас есть три разных элемента DOM, на которые влияет состояние поля пароля. Обработка всех комбинаций и обеспечение правильного отображения нашего представления увеличивает цикломатическую сложность даже такого простого фрагмента кода, как этот.

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

Кодирование переходов вместо состояний может быть сложным. Количество ветвлений и проверок, которые нам нужно выполнить в нашем коде, растет экспоненциально с количеством состояний представления, которыми нужно управлять.

В нашем примере мы определили три состояния просмотра, что дало нам 3 * 2 = 6 переходов. В общем, для данных N состояний у нас есть N * (N - 1) = N^2 - N переходов, которые нам нужно было бы смоделировать. Просто подумайте о повышенной сложности, если мы добавим в наш пример четвертое состояние.

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

Снижение сложности

Предполагая, что мы могли бы объявить состояние представления на основе состояния модели, вместо того, чтобы явно кодировать переход из одного состояния в другое, мы могли бы получить что-то вроде этого:

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

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

Хотя этот подход значительно снижает сложность кода, он также предполагает, что есть кто-то или что-то еще, кто позаботится о реальных манипуляциях с DOM от нашего имени.

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

реакция

React — это библиотека JavaScript, созданная Facebook. Он предназначен для обработки части пользовательского интерфейса веб-приложений. Вы можете думать об этом как о V в архитектуре MVC. Это очень сосредоточено. Он не делает никаких предположений об остальном стеке ваших технологий и не обрабатывает ничего, кроме компонентов рендеринга. Он не предоставляет никаких механизмов маршрутизации, моделей или других функций, которые обычно входят в состав более крупных платформ. Таким образом, вы можете смешивать его и использовать с любой другой библиотекой или фреймворком.

React позволяет нам определять пользовательские интерфейсы как деревья составных компонентов. Разработчик React определяет эти компоненты, указывая функцию рендеринга , которая описывает компонент с учетом входного состояния. Эта функция должна быть чистой (т. е. она не должна иметь никаких побочных эффектов или зависеть ни от чего, кроме своего явного ввода).

Функция рендеринга возвращает описание представления, которое React называет Virtual DOM . Думайте об этом как об объекте JavaScript, соответствующем отображаемому элементу DOM.

Когда вы меняете состояние компонента, он просто перерисовывает себя и все свои дочерние элементы, возвращая новый виртуальный DOM .

Более того, React не будет делать простую замену HTML при переходе из одного состояния в другое. Он найдет разницу между предыдущим и новым состоянием и рассчитает наиболее эффективный набор операций DOM для выполнения перехода.

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

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

ПРИМЕЧАНИЕ. Следующий пример кода написан в препроцессоре JSX, что является распространенным способом написания пользовательского интерфейса на основе 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);

Компонент Emoji , который отображается, когда надежность пароля в порядке с <Emoji value="smiley" /> , является просто еще одним настраиваемым компонентом (точно так же, как PasswordWithStrength ). Он определяется следующим образом:

 var Emoji = React.createClass({ render: function() { var emojiSrc = this.props.value + '.png'; return <img src={emojiSrc}></img>; } });

React.js против других

Справедливости ради, однако, есть и другие JavaScript-фреймворки на стороне клиента (такие как Ember, Angular, Knockout и другие), которые также решили проблему управления состоянием представления и даже добавили к нему дополнительные функции. Итак, почему вы хотите использовать React вместо любого другого фреймворка?

У React есть два ключевых преимущества по сравнению с большинством других библиотек.

Нет привязки данных

Некоторые другие альтернативные платформы используют привязку данных для сопоставления элементов DOM со свойствами состояния и поддерживают их синхронизацию, наблюдая за изменениями свойств. Этот подход позволяет отображать представление один раз, при этом каждое изменение вызывает только модификации затронутых элементов DOM. Другие альтернативы используют грязную проверку ; то есть вместо того, чтобы наблюдать за изменениями отдельных свойств, они просто выполняют сравнение между предыдущим состоянием и новым. React больше похож на последний подход, но вместо сравнения состояний он сравнивает представления представления.

React не имеет привязки данных. Ожидается, что разработчик вызовет метод setState или повторно отобразит верхний компонент при изменении состояния. Он охватывает однонаправленный поток от состояния к представлению.

Эту концепцию легко принять, поскольку разработчики обычно не думают о привязке данных. Основное внимание уделяется визуальному представлению данных. Поэтому вам не нужно думать о зависимых свойствах, форматировании, привязке специальных тегов HTML и т. д. С React вы просто повторно визуализируете компонент при изменении модели.

Чтобы понять разницу в управлении состоянием представления, давайте сравним Ember и React . Мы создадим объект person , который будет выводить полное имя в верхнем регистре. Через две секунды мы смоделируем изменение и обновим представление.

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

Мы создали объект со свойствами firstName , lastName и fullName . Поскольку Ember наблюдает за изменениями свойств, нам пришлось указать, что fullName зависит от firstName и lastName . Для этого мы добавили .property('firstName', 'lastName') при определении fullName .

Через две секунды person.set('firstName', 'Harry'); выполняется. Это вызвало обновление представления и его привязки.

Теперь давайте сделаем то же самое в 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);

Несмотря на то, что код Ember прост и удобен для чтения, очевидно, что React выигрывает в простоте. person — это обычный объект JavaScript, а fullName имя — просто функция.

Нет шаблонов

Каждый альтернативный фреймворк имеет свой способ обработки шаблонов. Некоторые из них используют строки, скомпилированные в JavaScript, а другие напрямую используют элементы DOM. Большинство из них используют пользовательские атрибуты HTML и теги, которые затем «компилируются» в HTML.

Шаблоны не являются частью кода JavaScript. Из-за этого каждая альтернатива нуждается в собственном способе представления общих операций, условий, итераций, вызовов функций и т. д. Все они в конечном итоге создают новый псевдоязык, который разработчики должны изучить.

В React нет шаблонов, все это старый добрый JavaScript.

React использует всю мощь JavaScript для создания представления. Метод рендеринга компонента — это функция JavaScript.

JSX доступен как препроцессор, который превращает «HTML-подобный синтаксис» в обычный JavaScript, но JSX является необязательным, и вы можете использовать стандартный JavaScript без каких-либо препроцессоров. Вы также можете воспользоваться преимуществами существующих инструментов JavaScript. Линтеры, препроцессоры, аннотации типов, минификация, удаление мертвого кода и т. д.

Давайте снова используем конкретный пример для сравнения React с одной из альтернативных платформ для управления состоянием представления.

Следующее руководство является примером использования AngularJS для перечисления хэштегов и количества твитов для каждого из них. Список отсортирован по количеству, и если нет хэштегов, отображается сообщение.

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

Чтобы иметь возможность составить этот список, разработчик должен узнать о AngularJS directives , ng-show и ng-repeat . Затем ему нужно узнать о AngularJS filters , чтобы понять orderBy . Много работы для такой простой вещи, как вывод списка!

Теперь давайте рассмотрим пример React, который делает то же самое:

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

Даже если бы мы использовали «более продвинутый» подход и JSX, каждый веб-разработчик с базовым пониманием JavaScript может легко прочитать приведенный выше код и понять, что он делает. Стандартная условная проверка с использованием if , итерация с использованием map() и стандартная sort() естественны для любого разработчика, поэтому нет необходимости изучать дополнительный синтаксис или другие концепции.

Заключение

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

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

Чтобы узнать больше об упрощении кода приложения с помощью React, ознакомьтесь с докладом Стивена Люшера «Декомплексирование кода с помощью React».

Вот дополнительное чтение для тех, кто хочет сделать следующий шаг и начать использовать React:

  • http://jlongster.com/Removing-User-Interface-Complexity,-or-Why-React-is-Awesome