9 июля 2015 в 16:30

React на ES6+ перевод

Это перевод поста Steven Luscher опубликованного в блоге Babel. Steven работает в Facebook над Relay – JavaScript фрэймворком для создания приложений с использованием React и GraphQL.
За этот год, в процессе реорганизации 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 за то что они делают будущее доступным для всех нас, уже сегодня.
Автор оригинала: Steven Luscher
@olegshilov
карма
4,0
рейтинг 0,0
Front-end
Самое читаемое Разработка

Комментарии (15)

  • +6
    Так приятно, когда ты читаешь о том, что уже крутится у тебя в продакшене :)
  • 0
    Спасибо за статью. Подскажите, как вы решили вопрос с отсутствием возможности использовать миксины в определении классов React компонентов? Или просто отказались от миксинов?
    • 0
      Один из способов заменить миксины — использовать декораторы. Декораторами так же можно подмешивать в класс какие-нибудь методы.

      Правда чтобы подмешивать несколько так называемых «lifecycle methods» уже придется повозиться. Сам решения не видел, но слышал что на просторах интернета уже есть много реализаций.

      • 0
        Я представляю себе реализацию примерно следующей.
        Декоратор проверяет если в прототипе (например) метод componentWillMount. Если нет, то просто добавляет. Если есть то добавляет свой внутри которого уже вызывается существующий
      • 0
        Проблема в том, что декораторы не являются утвержденным стандартном… Это ведь ES7, если не ошибаюсь. Хотя внедрение поддержки уже начато многими библиотеками. Babel поддерживает, например. Но в экспериментальном порядке:

        These proposals are subject to change so use with extreme caution. Babel may update without warning in order to track spec changes.
        • 0
          Верное замечание, этого пока не в стандарте)

          Но допустим static методы так же не утверждены, и не вошли в ES6. Но в этой статье описаны. Так что, думаю, и декораторы заслуживают внимания уже сегодня.
          • 0
            Статик есть в TypeScript в примерно таком виде, так что думаю и в стандарт рано или поздно проберется.
        • 0
          А не получится отключить babel никогда. У нас всегда будет как минимум JSX.
          • 0
            Я к этому, в общем-то, и не стремился :) Проблема в том, что когда вы используете возможности из разряда нестандартизированных и экспериментальных, а кодовая база проекта велика, при внесенении в стандарт существенных изменений, вы гарантировано получите геморрой. Babel будет изменен так, чтобы соответствовать новым изменениям. А вам придется поменять код, который от них зависит.
            • 0
              Такое неизбежно будет происходить с различными инструментами. Другое дело, что есть понятие BC. И если фича существовала год, ее скорее всего оставят с флагом «не поддерживается», или еще что-то подобное. В конце концов всегда есть возможность написания плагинов, или фиксирования версии babel.
              Ну и про JSX я упомянул не случайно, он скорее всего никогда не станет стандратом.
              • 0
                Такое неизбежно будет происходить с различными инструментами

                Конечно, но только в том случае, когда фича является экспериментальной.
    • +1
  • 0
    react-playground стартап площадка для простого react приложения в связке jspm + babel.

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