За этот год, в процессе реорганизации Instagram Web, мы насладились использованием ряда особенностей ES6+, при написании нашх React компонентов. Позвольте мне остановиться на тех моментах, когда новые возможности языка могут повлиять на то как вы пишите React приложения, и сделают этот процесс легче и веселее, чем когда-либо.
Классы
До сих пор наиболее заметным из видимых изменений в том, как мы пишем наши React компоненты, используя ES6+ является то, что мы решили использовать синтаксис определения класса. Вместо того чтобы использовать метод React.createClass для определения компонента, мы можем определить настоящий ES6 класс, который расширяет класс React.Component:
class Photo extends React.Component {
render() {
return <img alt={this.props.caption} src={this.props.src} />;
}
}
Вы сразу же вы заметите небольшое различие — вам становится доступным более лаконичный синтаксис определения класса:
// The ES5 way
var Photo = React.createClass({
handleDoubleTap: function(e) { … },
render: function() { … },
});
// The ES6+ way
class Photo extends React.Component {
handleDoubleTap(e) { … }
render() { … }
}
Стоит отметить, что мы отбросили две скобки и завершающую точку с запятой, а для каждого объявленного метода мы опускаем двоеточие, ключевое слово function и запятую.
Все методы жизненного цикла компонента, кроме одного могут быть определены, как можно было бы ожидать, с использованием нового синтаксиса определения классов. Конструктор класса в настоящее время выступает в роли ранее используемого метода componentWillMount:
// The ES5 way
var EmbedModal = React.createClass({
componentWillMount: function() { … },
});
// The ES6+ way
class EmbedModal extends React.Component {
constructor(props) {
super(props);
// Operations usually carried out in componentWillMount go here
}
}
Инициализаторы свойств
В мире классов ES6+, типы свойств и значения по умолчанию могут существовать как статические свойства этого класса. Эти переменные, а также начальное состояние компонента, могут быть определены с использованием ES7 инициализаторов свойств:
// The ES5 way
var Video = React.createClass({
getDefaultProps: function() {
return {
autoPlay: false,
maxLoops: 10,
};
},
getInitialState: function() {
return {
loopsRemaining: this.props.maxLoops,
};
},
propTypes: {
autoPlay: React.PropTypes.bool.isRequired,
maxLoops: React.PropTypes.number.isRequired,
posterFrameSrc: React.PropTypes.string.isRequired,
videoSrc: React.PropTypes.string.isRequired,
},
});
// The ES6+ way
class Video extends React.Component {
static defaultProps = {
autoPlay: false,
maxLoops: 10,
}
static propTypes = {
autoPlay: React.PropTypes.bool.isRequired,
maxLoops: React.PropTypes.number.isRequired,
posterFrameSrc: React.PropTypes.string.isRequired,
videoSrc: React.PropTypes.string.isRequired,
}
state = {
loopsRemaining: this.props.maxLoops,
}
}
Инициализаторы свойств ES7 работают внутри конструктора класса, где this относится к текущему экземпляру класса перед его созданием. Благодаря этому, начальное состояние компонента может зависить от this.props. Примечательно, что мы больше не должны определять значения props по умолчанию и начальное состояние объекта в терминах getter функции.
Arrow функции
Метод React.createClass используется для выполнения некоторых дополнительных работ по привязке к методам экземпляра компонента, чтобы убедиться, что внутри них, ключевое слово this будет относиться к экземпляру компонента.
// Autobinding, brought to you by React.createClass
var PostInfo = React.createClass({
handleOptionsButtonClick: function(e) {
// Here, 'this' refers to the component instance.
this.setState({showOptionsModal: true});
},
});
Так как мы не связаны использованием метода React.createClass, при определении компонентов синтаксисом классов ES6+, казалось бы, что нам нужно вручную привязать методы экземпляра, туда где мы хотим их использовать:
// Manually bind, wherever you need to
class PostInfo extends React.Component {
constructor(props) {
super(props);
// Manually bind this method to the component instance...
this.handleOptionsButtonClick = this.handleOptionsButtonClick.bind(this);
}
handleOptionsButtonClick(e) {
// ...to ensure that 'this' refers to the component instance here.
this.setState({showOptionsModal: true});
}
}
К счастью, путем объединения двух возможностей ES6+ – arrow функции и инициализаторы свойств – отказ от привязки к экземпляру компонента становится очень легким:
class PostInfo extends React.Component {
handleOptionsButtonClick = (e) => {
this.setState({showOptionsModal: true});
}
}
Тело ES6 arrow функций использует то же лексическое this как и код, который её окружает. Это позволяет нам достичь желаемого результата из-за того, что ES7 инициализаторы свойств находятся в области видимости. Загляните под капот, чтобы понять почему это работает.
Динамические имена свойств и шаблоные строки
Одним из усовершенствований литералов объектов включает в себя возможность назначать производные имена свойствам. Изначально мы могли бы сделать что-то подобное для установки некоторой части состояния компонента:
var Form = React.createClass({
onChange: function(inputName, e) {
var stateToSet = {};
stateToSet[inputName + 'Value'] = e.target.value;
this.setState(stateToSet);
},
});
Теперь у нас есть возможность создавать объекты, в которых имена свойств определяются выражением JavaScript во время выполнения. Здесь мы используем шаблонные строки, для того что-бы определить, какое свойство установить в состоянии компонента:
class Form extends React.Component {
onChange(inputName, e) {
this.setState({
[`${inputName}Value`]: e.target.value,
});
}
}
Деструктуризация и распространение атрибутов
Часто при создании компонентов, мы могли бы передать большинство свойств родительского компонента к дочернему компоненту, но не все из них. В сочетании ES6+ деструктурирования и распространения атрибутов JSX, это становится возможным без плясок с бубном:
class AutoloadingPostsGrid extends React.Component {
render() {
var {
className,
...others, // contains all properties of this.props except for className
} = this.props;
return (
<div className={className}>
<PostsGrid {...others} />
<button onClick={this.handleLoadMoreClick}>Load more</button>
</div>
);
}
}
Так же мы можем сочетать JSX распространение атрибутов с регулярными атрибутами, пользуясь простым правилом приоритета для реализации переопределения значений и установки значений атрибута по умолчанию. Этот элемент получит переопределеное значение атрибута className, даже если свойство className существует в this.props:
<div {...this.props} className="override">
…
</div>
Атрибут className этого элемента по умолчанию имеет значение «base», если не существует свойства className в this.props чтобы переопределить его:
<div className="base" {...this.props}>
…
</div>
Спасибо за прочтение
Я надеюсь что вам, так же как нам, понравится использовать возможности языка ES6+ для написания React кода. Спасибо моим коллегам за их вклад в эту статью, и особая благодарность команде Babel за то что они делают будущее доступным для всех нас, уже сегодня.
Комментарии (14)
FractalizeR
10.07.2015 10:21Спасибо за статью. Подскажите, как вы решили вопрос с отсутствием возможности использовать миксины в определении классов React компонентов? Или просто отказались от миксинов?
ElianL
10.07.2015 11:52Один из способов заменить миксины — использовать декораторы. Декораторами так же можно подмешивать в класс какие-нибудь методы.
Правда чтобы подмешивать несколько так называемых «lifecycle methods» уже придется повозиться. Сам решения не видел, но слышал что на просторах интернета уже есть много реализаций.
ElianL
10.07.2015 11:57Я представляю себе реализацию примерно следующей.
Декоратор проверяет если в прототипе (например) метод componentWillMount. Если нет, то просто добавляет. Если есть то добавляет свой внутри которого уже вызывается существующий
FractalizeR
10.07.2015 12:29Проблема в том, что декораторы не являются утвержденным стандартном… Это ведь ES7, если не ошибаюсь. Хотя внедрение поддержки уже начато многими библиотеками. Babel поддерживает, например. Но в экспериментальном порядке:
These proposals are subject to change so use with extreme caution. Babel may update without warning in order to track spec changes.
ElianL
10.07.2015 12:34Верное замечание, этого пока не в стандарте)
Но допустим static методы так же не утверждены, и не вошли в ES6. Но в этой статье описаны. Так что, думаю, и декораторы заслуживают внимания уже сегодня.
hell0w0rd
10.07.2015 14:11А не получится отключить babel никогда. У нас всегда будет как минимум JSX.
FractalizeR
10.07.2015 14:51Я к этому, в общем-то, и не стремился :) Проблема в том, что когда вы используете возможности из разряда нестандартизированных и экспериментальных, а кодовая база проекта велика, при внесенении в стандарт существенных изменений, вы гарантировано получите геморрой. Babel будет изменен так, чтобы соответствовать новым изменениям. А вам придется поменять код, который от них зависит.
hell0w0rd
10.07.2015 19:55Такое неизбежно будет происходить с различными инструментами. Другое дело, что есть понятие BC. И если фича существовала год, ее скорее всего оставят с флагом «не поддерживается», или еще что-то подобное. В конце концов всегда есть возможность написания плагинов, или фиксирования версии babel.
Ну и про JSX я упомянул не случайно, он скорее всего никогда не станет стандратом.FractalizeR
11.07.2015 11:09Такое неизбежно будет происходить с различными инструментами
Конечно, но только в том случае, когда фича является экспериментальной.
nd0ut
10.07.2015 14:22+1olegshilov Автор
11.07.2015 03:29этот вариант мне больше нравится github.com/felixgirault/pure-render-decorator
VBauer
20.07.2015 17:50react-playground стартап площадка для простого react приложения в связке jspm + babel.
xamd
Так приятно, когда ты читаешь о том, что уже крутится у тебя в продакшене :)