Статьи

Реакция для разработчиков Angular

Эта статья для разработчиков, которые знакомы с Angular 1.x и хотели бы узнать больше о React. Мы рассмотрим различные подходы, которые они используют для создания многофункциональных веб-приложений, перекрывающуюся функциональность и пробелы, которые React не пытается заполнить.

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

Фреймворки против библиотек

Angular — это фреймворк , тогда как React — это библиотека, ориентированная только на слой представления. Есть затраты и выгоды, связанные как с использованием каркасов, так и с набором слабосвязанных библиотек.

Фреймворки пытаются предложить законченное решение, и они могут помочь организовать код с помощью шаблонов и соглашений, если вы являетесь частью большой команды. Однако наличие большого API добавляет когнитивную нагрузку, когда вы пишете, и вы будете тратить гораздо больше времени на чтение документации и запоминание шаблонов — особенно в первые дни, когда вы все еще учитесь.

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

Рекомендуемые курсы

Тодд девиз
Конечный ресурс для изучения Angular и его экосистемы. Используйте код купона «SITEPOINT» при оформлении заказа, чтобы получить скидку 25% .

Из коробки

Angular предоставляет вам богатый набор функций для создания веб-приложений. Среди его особенностей:

  • HTML-шаблоны с динамическими выражениями в двойных фигурных скобках {{ }}
  • встроенные директивы, такие как ng-model , ng-repeat и ng-class для расширения возможностей HTML
  • контроллеры для группировки логики и передачи данных в представление
  • двусторонняя привязка как простой способ синхронизировать вид и контроллер
  • большая коллекция модулей, таких как $http для связи с сервером и ngRoute для маршрутизации
  • пользовательские директивы для создания собственного синтаксиса HTML
  • внедрение зависимостей для ограничения воздействия объектов на определенные части приложения
  • сервисы для общей бизнес-логики
  • фильтры для помощников форматирования представления.

React, с другой стороны, дает вам:

  • Синтаксис JSX для шаблонов с выражениями JavaScript в виде отдельных фигур { }
  • компоненты, которые больше всего похожи на директивы элемента Angular.

React остается незамеченным, когда речь идет об остальной части структуры вашего приложения, и он поощряет использование стандартных API-интерфейсов JavaScript поверх абстракций инфраструктуры. Вместо того, чтобы предоставлять оболочку типа $ http для связи с сервером, вы можете использовать fetch () . Вы можете свободно использовать такие конструкции, как сервисы и фильтры, но React не предоставит им абстракцию. Вы можете поместить их в модули JavaScript и требовать их по мере необходимости в ваших компонентах.

Таким образом, в то время как Angular дает вам гораздо больше абстракций для обычных задач, React сознательно избегает этого, чтобы вы чаще писали стандартный JavaScript и использовали внешние зависимости для всего остального.

Бутстрапирование

Инициализация приложений Angular требует наличия модуля, списка зависимостей и корневого элемента.

 let app = angular.module('app', []) let root = document.querySelector('#root'); angular.element(root).ready(function() { angular.bootstrap(root, ['app']); }); 

Точкой входа для React является рендеринг компонента в корневой узел. Также возможно иметь несколько корневых компонентов:

 let root = document.querySelector('#root'); ReactDOM.render(<App />, root) 

Шаблоны

Анатомия углового обзора сложна и имеет много обязанностей. Ваши HTML-шаблоны содержат набор директив и выражений, которые связывают представление и связанные контроллеры вместе. Данные передаются в разных контекстах через $scope .

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

 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) :

 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 компонента. Например:

 class MyComponent extends React.Component { render() { return ( // JSX lives here ) } } 

нг-повтор

 <ul> <li ng-repeat="word in words">{ word }</li> </ul> 

Мы можем использовать стандартные циклические механизмы JavaScript, такие как map чтобы получить массив элементов в JSX.

 <ul> { words.map((word)=> <li>{ word }</li> )} </ul> 

нг-класс

 <form ng-class="{ active: active, error: error }"> </form> 

В React мы оставляем наши собственные устройства, чтобы создать разделенный пробелами список классов для свойства className . Для этой цели обычно используют существующую функцию, такую ​​как classNames Джеда Уотсона.

 <form className={ classNames({active: active, error: error}) }> </form> 

Способ думать об этих атрибутах в JSX таков, как будто вы устанавливаете свойства для этих узлов напрямую. Вот почему это имя className а не имя атрибута class .

 formNode.className = "active error"; 

нг-если

 <div> <p ng-if="enabled">Yep</p> </div> 

операторы ifelse не работают внутри JSX, потому что JSX — это просто синтаксический сахар для вызовов функций и построения объектов. Для этого типично использовать троичные операторы или перемещать условную логику в начало метода рендеринга, вне JSX.

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

нг-шоу / нг-скрыть

 <p ng-show="alive">Living</p> <p ng-hide="alive">Ghost</p> 

В React вы можете установить свойства стиля напрямую или добавить служебный класс, такой как .hidden { display: none } , в свой CSS с целью скрытия ваших элементов (как это обрабатывает Angular).

 <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 чтобы выделить активный слайд.

 <div ng-controller="SlideShowController"> <slide-show slides="slides"></slide-show> </div> 
 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; }; } }; }); 

Компонент слайд-шоу в угловых

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

 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.state , которую вы можете изменить, вызвав this.setState({ key: value }) . Любые изменения состояния приводят к повторной визуализации компонента.

 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 выглядят как встроенные обработчики событий старой школы, такие как onClick . Однако не расстраивайтесь: под капотом все происходит правильно и создает высокопроизводительных делегированных слушателей событий.

Компонент слайд-шоу в действии

Двухстороннее связывание

Надежная ng-model Angular и $scope образуют связь, по которой данные перемещаются между элементом формы и свойствами объекта JavaScript в контроллере.

 app.controller("TwoWayController", function($scope) { $scope.person = { name: 'Bruce' }; }); 
 <div ng-controller="TwoWayController"> <input ng-model="person.name" /> <p>Hello {{ person.name }}!</p> </div> 

React отказывается от этой схемы в пользу одностороннего потока данных. Одни и те же типы представлений могут быть построены с обоими образцами.

 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 (при каждом нажатии клавиши в приведенном выше примере). Сам компонент называется «с состоянием», потому что он управляет своими собственными данными. Это не рекомендуется для большинства компонентов. Идеально, чтобы компоненты оставались «без состояния» и передавали им данные через props .

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

Позвони своим родителям

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

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

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

Звучит здорово. Могу ли я использовать оба !?

Да! Можно рендерить компоненты React внутри существующего приложения Angular. Бен Надель подготовил хороший пост со скринкастом о том, как визуализировать компоненты React в директиве Angular . Существует также ngReact , который предоставляет директиву react-component для react-component между React и Angular.

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

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

Как насчет Angular 2?

Компоненты в Angular 2 во многом напоминают компоненты React. Компоненты примера в документах имеют класс и шаблон в непосредственной близости. События выглядят похоже. В нем объясняется, как создавать представления с использованием иерархии компонентов , так же, как если бы вы строили ее в React, и она включает модули ES6 для внедрения зависимостей.

 // 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, на которые вы можете посмотреть:

Учебные ресурсы

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

Эта статья была рецензирована Крейгом Билнером . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!