Это перевод поста 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 за то что они делают будущее доступным для всех нас, уже сегодня.

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


  1. xamd
    09.07.2015 21:24
    +6

    Так приятно, когда ты читаешь о том, что уже крутится у тебя в продакшене :)


  1. FractalizeR
    10.07.2015 10:21

    Спасибо за статью. Подскажите, как вы решили вопрос с отсутствием возможности использовать миксины в определении классов React компонентов? Или просто отказались от миксинов?


    1. ElianL
      10.07.2015 11:52

      Один из способов заменить миксины — использовать декораторы. Декораторами так же можно подмешивать в класс какие-нибудь методы.

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


      1. ElianL
        10.07.2015 11:57

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


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


        1. ElianL
          10.07.2015 12:34

          Верное замечание, этого пока не в стандарте)

          Но допустим static методы так же не утверждены, и не вошли в ES6. Но в этой статье описаны. Так что, думаю, и декораторы заслуживают внимания уже сегодня.


        1. hell0w0rd
          10.07.2015 14:11

          А не получится отключить babel никогда. У нас всегда будет как минимум JSX.


          1. FractalizeR
            10.07.2015 14:51

            Я к этому, в общем-то, и не стремился :) Проблема в том, что когда вы используете возможности из разряда нестандартизированных и экспериментальных, а кодовая база проекта велика, при внесенении в стандарт существенных изменений, вы гарантировано получите геморрой. Babel будет изменен так, чтобы соответствовать новым изменениям. А вам придется поменять код, который от них зависит.


            1. hell0w0rd
              10.07.2015 19:55

              Такое неизбежно будет происходить с различными инструментами. Другое дело, что есть понятие BC. И если фича существовала год, ее скорее всего оставят с флагом «не поддерживается», или еще что-то подобное. В конце концов всегда есть возможность написания плагинов, или фиксирования версии babel.
              Ну и про JSX я упомянул не случайно, он скорее всего никогда не станет стандратом.


              1. FractalizeR
                11.07.2015 11:09

                Такое неизбежно будет происходить с различными инструментами

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


    1. nd0ut
      10.07.2015 14:22
      +1

      1. olegshilov Автор
        11.07.2015 03:29

        этот вариант мне больше нравится github.com/felixgirault/pure-render-decorator


        1. olegshilov Автор
          11.07.2015 04:16

          пора спать.


  1. VBauer
    20.07.2015 17:50

    react-playground стартап площадка для простого react приложения в связке jspm + babel.