От автора: эта статья предназначена для разработчиков, которые знакомы с Angular 1.x и хотели бы узнать больше о React. Мы рассмотрим различные подходы к созданию насыщенных веб-приложений, перекрытий функциональных возможностей и недостатков, которые React не может заполнить. После ознакомления вы узнаете о проблемах, которые React намеревается решить, и о том, как вы можете использовать знания, которые вы уже начали использовать в React в собственных проектах.
Фрэймворки против Библиотек
Angular это фрэймворк, тогда как React — это библиотека, ориентированная только на слое представления. Существуют плюсы и минусы, связанные с использованием как фреймворков, так и коллекций слабо связанных библиотек.
Фрэймворки пытаются предложить полное решение, и могут помочь организовать код через шаблоны и соглашения, если вы являетесь частью большой команды. Однако когда пишете, наличие крупного API добавляет когнитивную нагрузку, будет потрачено гораздо больше времени на чтение документации и запоминание шаблонов — особенно в первые дни, когда только идёт обучение.
Слабо связанные библиотеки с маленькими API легче изучать, однако когда у вас возникнут проблемы, вы будете решать их большим количеством кода, или же вам придется подключать внешние библиотеки. Обычно это приводит к написанию своего фреймворка для уменьшения размеров шаблона.
Из коробки
Angular дает вам богатый набор функций для создания веб-приложений. Среди его возможностей:
HTML-шаблоны с динамическими выражениями в двойных скобках {{ }}
встроенные директивы, например, для расширения возможностей HTML ng-modelng-repeatng-class
контроллеры для группировки логики и передачи данных в представление
двусторонняя привязка как простой способ синхронизировать представление и контроллер
большой набор модулей, например $http для связи с сервером и ngRoute для маршрутизации
настраиваемые директивы для создания собственного синтаксиса HTML
инъекции зависимостей для ограничения воздействия объектов на определенные части приложения
услуги для общей бизнес-логики
фильтры для помощи при форматировании.
С другой стороны, React даст:
Синтаксис JSX для шаблонов с выражениями JavaScript в одиночных скобках {}
компоненты, которые больше всего похожи на директивы элемента Angular.
React не упрямится, когда дело доходит до остальной части структуры приложения, и поощряет использование стандартных JavaScript API поверх абстракций фреймворка. Вместо того чтобы предоставлять оболочку, такую как связь $http для сервера, вы можете использовать fetch(). Вы можете использовать различные конструкции, такие как службы и фильтры, но React не будет предоставлять абстракцию для них. Вы можете поместить их в модули JavaScript и потребовать их в своих компонентах по мере необходимости.
То есть, в то время как Angular дает вам больше абстракций для обычных задач, React намеренно избегает этого, чтобы вы чаще писали стандартный JavaScript и использовали внешние зависимости для всего остального.
Предварительная загрузка
Инициализация приложений Angular требуют наличия модуля, списка зависимостей и корневого элемента.
1 2 3 4 5 |
let app = angular.module('app', []) let root = document.querySelector('#root'); angular.element(root).ready(function() { angular.bootstrap(root, ['app']); }); |
Точкой входа для React является рендеринг компонента в корневой узел. Также возможны несколько корневых компонентов:
1 2 |
let root = document.querySelector('#root'); ReactDOM.render(<App />, root) |
Шаблоны
Строение представления Angular сложное и имеет множество задач. HTML-шаблоны содержат сочетание директив и выражений, которые связывают представление и связанные с ним контроллеры между собой. Данные в разных контекстах проходят через $scope.
В React все компоненты идут вниз, данные поступают в одном направлении от вершины дерева компонентов до узловых листьев. JSX является наиболее распространенным синтаксисом для написания компонентов, превращая знаковую структуру XML в JavaScript. Хотя это больше похоже на синтаксис шаблона, он компилируется во вложенные вызовы функций.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
const App = React.createClass({ render: function() { return ( <Component> <div>{ 2 + 1 }</div> <Component prop="value" /> <Component time={ new Date().getTime() }> <Component /> </Component> </Component> ) } }) |
Скомпилированный код, приведенный ниже, должен пояснить, как JSX выражения выше ссылаются на вызов функций: createElement(component, props,children)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var App = React.createClass({ render: function render() { return React.createElement( Component, null, React.createElement("div", null, 2 + 1), React.createElement(Component, { prop: "value" }), React.createElement( Component, { time: new Date().getTime() }, React.createElement(Component, null) ) ); } }); |
Директивы шаблонов
Давайте рассмотрим, как некоторые из наиболее используемых шаблонов в Angular будут написаны в компонентах React. Теперь у React нет шаблонов, так что эти примеры — это код JSX, который будет находиться внутри компонента render функции. Например:
1 2 3 4 5 6 7 |
class MyComponent extends React.Component { render() { return ( // JSX lives here ) } } |
ng-repeat
1 2 3 |
<ul> <li ng-repeat="word in words">{ word }</li> </ul> |
Мы можем использовать стандартные механизмы циклизации JavaScript, например, map для получения массива элементов в JSX.
1 2 3 |
<ul> { words.map((word)=> <li>{ word }</li> )} </ul> |
ng-class
1 2 |
<form ng-class="{ active: active, error: error }"> </form> |
В React мы создаём список классов, разделенных пробелами для свойства className, на собственных устройствах. Для этой цели обычно используется существующая функция, такая как className Джеда Уотсона.
1 2 |
<form className={ classNames({active: active, error: error}) }> </form> |
Можно думать об этих атрибутах в JSX так, будто вы устанавливаете свойства напрямую на узлах. Вот почему это className, а не имя атрибута класса.
1 |
formNode.className = "active error"; |
ng-if
1 2 3 |
<div> <p ng-if="enabled">Yep</p> </div> |
if … else операторы, не работающие внутри JSX, потому что JSX — это просто синтаксический сахар для вызовова функций и построения объектов. Для этого принято использовать тернарные операторы или перемещать условную логику в начало метода визуализации вне JSX.
1 2 3 4 5 6 7 8 9 10 11 |
// ternary <div> { enabled ? <p>Enabled</p> : null } </div> // if/else outside of JSX let node = null; if (enabled) { node = <p>Enabled</p>; } <div>{ node }</div> |
ng-show / ng-hide
1 2 |
<p ng-show="alive">Living</p> <p ng-hide="alive">Ghost</p> |
В React вы можете напрямую установить свойства стиля или добавить в свой CSS класс утилит, чтобы скрыть элементы типа .hidden {display: none } (как это делает Angular).
1 2 3 4 5 |
<p style={ display: alive ? 'block' : 'none' }>Living</p> <p style={ display: alive ? 'none' : 'block' }>Ghost</p> <p className={ classNames({ hidden: !alive }) }>Living</p> <p className={ classNames({ hidden: alive }) }>Ghost</p> |
Теперь вместо специального синтаксиса шаблона и атрибутов нужно будет использовать JavaScript для достижения желаемого результата.
Пример компонента
Компоненты React больше похожи на директивы Angular . Они используются, прежде всего, для абстрагирования сложных структур DOM и поведения внутри многоразовых фрагментов. Ниже приведен пример компонента слайд-шоу, который принимает массив слайдов, отображает список изображений с навигационными элементами и отслеживает его собственное activeIndex состояние, чтобы выделить активный слайд.
1 2 3 |
<div ng-controller="SlideShowController"> <slide-show slides="slides"></slide-show> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
app.controller("SlideShowController", function($scope) { $scope.slides = [{ imageUrl: "allan-beaver.jpg", caption: "Allan Allan Al Al Allan" }, { imageUrl: "steve-beaver.jpg", caption: "Steve Steve Steve" }]; }); app.directive("slideShow", function() { return { restrict: 'E', scope: { slides: '=' }, template: ` <div class="slideshow"> <ul class="slideshow-slides"> <li ng-repeat="slide in slides" ng-class="{ active: $index == activeIndex }"> <figure> <img ng-src="{{ slide.imageUrl }}" /> <figcaption ng-show="slide.caption">{{ slide.caption }}</figcaption> </figure> </li> </ul> <ul class="slideshow-dots"> <li ng-repeat="slide in slides" ng-class="{ active: $index == activeIndex }"> <a ng-click="jumpToSlide($index)">{{ $index + 1 }}</a> </li> </ul> </div> `, link: function($scope, element, attrs) { $scope.activeIndex = 0; $scope.jumpToSlide = function(index) { $scope.activeIndex = index; }; } }; }); |
Компонент слайд-шоу в Angular
Этот компонент в React будет отображаться внутри другого компонента и передавать данные слайдов через реквизиты.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
let _slides = [{ imageUrl: "allan-beaver.jpg", caption: "Allan Allan Al Al Allan" }, { imageUrl: "steve-beaver.jpg", caption: "Steve Steve Steve" }]; class App extends React.Component { render() { return <SlideShow slides={ _slides } /> } } |
У компонентов React есть локальная область действия, которую, вызвав, можно изменить. Любые изменения состояния заставляют компонент повторно отображать себя .this.statethis.setState({ key: value })
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
class SlideShow extends React.Component { constructor() { super() this.state = { activeIndex: 0 }; } jumpToSlide(index) { this.setState({ activeIndex: index }); } render() { return ( <div className="slideshow"> <ul className="slideshow-slides"> { this.props.slides.map((slide, index) => ( <li className={ classNames({ active: index == this.state.activeIndex }) }> <figure> <img src={ slide.imageUrl } /> { slide.caption ? <figcaption>{ slide.caption }</figcaption> : null } </figure> </li> )) } </ul> <ul className="slideshow-dots"> { this.props.slides.map((slide, index) => ( <li className={ (index == this.state.activeIndex) ? 'active': '' }> <a onClick={ (event)=> this.jumpToSlide(index) }>{ index + 1 }</a> </li> )) } </ul> </div> ); } } |
События в React выглядят как обработчики событий in-line старой школы, такие как onClick. Не расстраивайтесь: внутри всё делается как надо, создаются высокопроизводительные делегированные обработчики событий.
Компонент слайд-шоу в React
Двусторонняя привязка
Angular доверенная директива ng-model и переменная $scope формируют связь, где данные могут ходить в обе стороны от элемента формы к свойствам JS-объекта в контроллере.
1 2 3 4 5 |
app.controller("TwoWayController", function($scope) { $scope.person = { name: 'Bruce' }; }); |
1 2 3 4 |
<div ng-controller="TwoWayController"> <input ng-model="person.name" /> <p>Hello {{ person.name }}!</p> </div> |
React избегает этого шаблона в пользу одностороннего потока данных. Тем не менее, те же типы представлений могут быть построены с обоими шаблонами.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class OneWayComponent extends React.Component { constructor() { super() this.state = { name: 'Bruce' } } change(event) { this.setState({ name: event.target.value }); } render() { return ( <div> <input value={ this.state.name } onChange={ (event)=> this.change(event) } /> <p>Hello { this.state.name }!</p> </div> ); } } |
Здесь input это “управляемый вход”. Это означает, что его значение изменяется только когда вызывается функция «render» (по каждому штриху в примере выше). Сам компонент называется «stateful», потому что он управляет своими собственными данными. Это не рекомендуется для большинства компонентов. Идеальным является сохранение компонентов “stateless” и передача данных props через них.
Как правило, “stateful” Container Component или Controller View находится в верхней части дерева с множеством дочерних компонентов “satetless”. Для получения дополнительной информации об этом прочтите, какие компоненты должны иметь состояние?
Вызов родителей
В то время как данные стекают в одном направлении, однако вы можете вызывать методы на родительских элементах через callback-функции. Обычно это делается в ответ на некоторые пользовательские вводы. Эта гибкость дает вам большой контроль при рефакторинге компонентов в их простейших презентационных формах. Если реорганизованные компоненты вообще не имеют состояния, они могут быть записаны как чистые функции.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
// A presentational component written as a pure function const OneWayComponent = (props)=> ( <div> <input value={ props.name } onChange={ (event)=> props.onChange(event.target.value) } /> <p>Hello { props.name }!</p> </div> ); class ParentComponent extends React.Component { constructor() { super() this.state = { name: 'Bruce' }; } change(value) { this.setState({name: value}); } render() { return ( <div> <OneWayComponent name={ this.state.name } onChange={ this.change.bind(this) } /> <p>Hello { this.state.name }!</p> </div> ) } } |
На первый взгляд это может показаться движением по кругу, если вы знакомы с двусторонней привязкой данных. Преимущество наличия множества небольших презентационных «немых» компонентов, которые просто принимают данные props и их рендеринг, заключается в том, что они по умолчанию проще, а простые компоненты имеют гораздо меньше ошибок. Это также не позволяет пользовательскому интерфейсу находиться в несогласованном состоянии, что часто происходит, если данные находятся в нескольких местах и должны поддерживаться отдельно.
Инъекция зависимостей, сервисы, фильтры
Модули JavaScript — один из лучших способов обработки зависимостей. Их можно использовать с помощью такого инструмента, как Webpack , SystemJS или Browserify.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// An Angular directive with dependencies app.directive('myComponent', ['Notifier', '$filter', function(Notifier, $filter) { const formatName = $filter('formatName'); // use Notifier / formatName }] // ES6 Modules used by a React component import Notifier from "services/notifier"; import { formatName } from "filters"; class MyComponent extends React.Component { // use Notifier / formatName } |
Могу ли я использовать и то и другое сразу!?
Да! В существующем Anglar приложении можно отображать компоненты React. Бен Надэл собрал хороший пост со скринкастами о том, как визуализировать компоненты React внутри директивы Angular. Существует также ngReact , который предоставляет директиву react-component для работы в качестве склейки между React и Angular.
Если вы столкнулись с проблемами производительности рендеринга в некоторых частях Angular, возможно, можно повысить производительность, делегируя часть этого рендеринга для React. При этом не стоит включать две большие библиотеки JavaScript, которые решают много одинаковых проблем. Несмотря на то, что React это только слой представления, он примерно такого же размера, как и Angular, так что, основываясь на таком использовании, вес может быть чрезмерно большим.
В то время как React и Angular решают одни и те же проблемы, они обходятся по-разному. React способствует функциональному декларативному подходу, когда компоненты являются чистыми функциями, свободными от побочных эффектов. Этот функциональный стиль программирования приводит к меньшему количеству ошибок и более простому рассуждению.
Что насчёт Angular 2?
Компоненты в Angular 2 во многом напоминают компоненты React. Примеры компонентов в документации имеют класс и шаблон в непосредственной близости. События выглядят одинаково. В нем объясняется, как создавать представления с использованием иерархии компонентов точно так же, как если бы вы строили ее в React, также в него включены модули ES6 для инъекций зависимостей.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
// Angular 2 @Component({ selector: 'hello-component', template: ` <h4>Give me some keys!</h4> <input (keyup)="onKeyUp($event)" /> <div>{{ values }}</div> ` }) class HelloComponent { values=''; onKeyUp(event) { this.values += event.target.value + ' | '; } } // React class HelloComponent extends React.Component { constructor(props) { super() this.state = { values: '' }; } onKeyUp(event) { const values = `${this.state.values + event.target.value} | `; this.setState({ values: values }); } render() { return ( <div> <h4>Give me some keys!</h4> <div><input onKeyUp={ this.onKeyUp.bind(this) } /></div> <div>{ this.state.values }</div> </div> ); } } |
Большая часть работы над Angular 2 заключалась в том, чтобы DOM-обновления выполнялись эффективнее. Предыдущий синтаксис шаблона и сложности вокруг областей привели к большим проблемам производительности в крупных приложениях.
Полное приложение
В этой статье я сосредоточился на шаблонах, директивах и формах, но если вы создаете полное приложение, потребуются другие функции, которые помогут управлять моделью данных, связью на сервере и маршрутизацией. Когда я впервые научился работать с Angular и React, прежде чем начать использовать их в реальных приложениях, я создал пример приложения Gmail, чтобы понять, как они работают, и посмотреть, что такое опыт разработчика.
Возможно, вам будет интересно взглянуть на эти примеры приложений, чтобы сравнить различия React и Angular. Пример React написан на CoffeeScript с CJSX, хотя сообщество React собралось вокруг ES6 с Babel и Webpack , так что это инструмент, который я бы предложил принять, если вы только начинаете.
Также есть приложения TodoMVC, которые вы можете сравнить:
Автор: Mark Brown
Источник: //www.sitepoint.com/
Редакция: Команда webformyself.