React з використанням ряду особливостей ES6+

За цей рік, в процесі реорганізації 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>

Переклад статті “React on ES6+