3.17 Интеграция со сторонними библиотеками

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


3.17.1 Интеграция с плагинами манипуляции DOM

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

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

Самый простой способ избежать конфликтов - предотвратить обновление компонента React. Вы можете сделать это, предоставив элементы, для которых у React нет никаких причин их обновлять, например, пустой <div />.


3.17.1.1 Как подойти к проблеме

Чтобы это продемонстрировать, давайте построим обёртку для известного плагина jQuery.

Мы добавим атрибут ref к корневому элементу DOM. Внутри componentDidMount мы получим на него ссылку, чтобы можно было передать его в плагин jQuery.

Чтобы предотвратить взаимодействие React с DOM после монтирования, мы вернем пустой тег <div /> из метода render(). Элемент <div /> не имеет свойств или дочерних элементов, поэтому у React нет причин обновлять его, оставляя плагину jQuery свободу для управления этой частью DOM:


Код
    
  // применим плагин "modal"
  class Modal extends React.Component {
    componentDidMount() {
      this.$element = $(this.element);
    // установить плагин
      this.$element.modal();
    }

    componentWillUnmount() {
    // удалить плагин
      this.$element.modal('destroy');
    }

    render() {
      return <div ref={element => this.element = element} />;
    }
  }
  

Обратите внимание, что мы определили методы жизненного цикла componentDidMount и componentWillUnmount. Многие плагины jQuery присоединяют слушателей событий к DOM, поэтому важно удалить их в componentWillUnmount. Если плагин не предоставляет метод очистки, вам, вероятно, придется предоставить свой собственный, чтобы удалить слушатели событий, зарегистрированных плагином, предотвращая тем самым утечку памяти.


3.17.1.2 Интеграция с JQuery UI Dialog плагином

Для более конкретного примера, давайте напишем минимальную оболочку для плагина JQuery UI Dialog .

Внимание! Просто потому, что это возможно, это совсем не означает, что такой подход - лучший для приложений React. Рекомендуется использовать именно компоненты React насколько это возможно. React-компоненты более просты для повторного использования в приложениях и обеспечивают больший контроль над своим поведением и внешним видом.

Во-первых, давайте посмотрим, что Dialog делает с DOM.

Если вы вызываете Dialog на узле DOM <div>, он автоматически внесет следующие изменения


Код
    
  <div class="ui-dialog ui-widget ui-widget-content ui-corner-all ui-draggable ui-resizable">
     <div class="ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix">
        <span id="ui-dialog-title-dialog" class="ui-dialog-title">Заголовок</span>
        <a class="ui-dialog-titlebar-close ui-corner-all" href="#"><span class="ui-icon ui-icon-closethick">close</span></a>
     </div>
     <div style="height: 200px; min-height: 109px; width: auto;" class="ui-dialog-content ui-widget-content" id="dialog">
        <p>Содержимое диалогового окна.</p>
     </div>
  </div>
  

Предположим, что это API, к которому мы стремимся с помощью нашего React-компонента-оболочки <Dialog>:


Код
    
  class App extends React.Component {
    constructor(props){
      super(props);
      this.showDialog = this.showDialog.bind(this);
      this.state = {isDialogShowed: false};
    }

    showDialog(){
  this.setState({isDialogShowed: true});
    }

    render(){
      return (
      <div>
          <button onClick={this.showDialog}>Показать диалог</button>
          {this.state.isDialogShowed ? <Dialog title="Диалог">Привет, Мир!</Dialog> : null}
      </div>
      );
    }
  }
  

Во-первых, мы создадим компонент с методом render(), где будем возвращать <div>:


Код
    
  class Dialog extends React.Component {
    constructor(props){
      super(props);
    }

    render() {
      return <div ref={element => this.element = element}>{this.props.children}</div>;
    }
  }
  

Важно знать, что если вы измените DOM вне потока React, вы должны убедиться, что у React нет оснований касаться этих узлов DOM.



Далее мы реализуем метод жизненного цикла. Нам нужно инициализировать Dialog с помощью ссылки ref на узел <div> в componentDidMount:


Код
    
  componentDidMount() {
    this.$element = $(this.element);
    this.$element.dialog({title: this.props.title});
  }
  

Посмотреть в CodePen


Обратите внимание на одну очень важную деталь: как мы вызываем диалог из приложения.


Код
    
  {this.state.isDialogShowed ? <Dialog title="Диалог">Привет, Мир!</Dialog> : null}
  

Мы либо отрисовываем его, либо возвращаем null. Это сделано для того, чтобы диалог при открытии и закрытии монтировался и демонтировался каждый раз. Это обеспечит вызов методов жизненного цикла компонента диалога, в которых происходит установка и уничтожение плагина. Если бы мы показывали диалог таким образом:


Код
    
  render(){
    const isShowed = this.state.isDialogShowed;
    return <Dialog isShowed={isShowed} title="Диалог">Привет, Мир!</Dialog>
  }
  

То его методы жизненного цикла сработали бы лишь один раз при первой отрисовке. А далее вызывался бы только его метод render(), так как диалог не монтируется и не демонтируется.

Также обратите внимание, что React не присваивает какое-то особенное значение полю this.element. Оно работает только потому, что мы ранее назначили это поле в атрибуте ref в методе render ():


Код
    
  <div ref={element => this.element = element}>
  

Данный компонент пока не может демонтироваться. Если вы щелкните на кнопке «закрыть», то диалог больше не откроется. Это происходит потому, что мы не демонтируем компонент. Чтобы это исправить, добавим в компонент диалога метод жизненного цикла componentWillUnmount, в котором будем уничтожать диалог:


Код
    
  componentWillUnmount() {
    this.$element.dialog('destroy');
  }
  

Но чтобы данный метод вызвался, нам необходимо демонтировать наш диалог. Для этого нам понадобится атрибут onClose для диалога, которому мы присвоим метод-коллбэк, выставляющий isDialogShowed в false:


Код
    
  class App extends React.Component {
    constructor(props){
      super(props);
      this.showDialog = this.showDialog.bind(this);
      this.hideDialog = this.hideDialog.bind(this);
      this.state = {isDialogShowed: false};
    }

    showDialog(){
      this.setState({isDialogShowed: true});
    }

    hideDialog(){
      this.setState({isDialogShowed: false});
    }

    render(){
      return (
      <div>
        <button onClick={this.showDialog}>Показать диалог</button>
          {this.state.isDialogShowed ? <Dialog isShowed={true} title="Диалог"
      onClose={this.hideDialog}>Привет, Мир!</Dialog> : null}
      </div>
      );
    }
  }
  

У самого компонента диалога будем использовать коллбэк onClose в componentDidMount:


Код
    
  componentDidMount() {
    this.$element = $(this.element);
    this.$element.dialog({title: this.props.title, close: this.props.onClose});
  }
  

Посмотреть в CodePen



3.17.2 Интеграция с другими библиотеками

React может быть встроен в другие приложения благодаря гибкости ReactDOM.render().

Хотя React обычно используется при запуске для загрузки единственного корневого React компонента в DOM, ReactDOM.render() может быть вызван множество раз для независимых частей UI, которые могут быть маленькими как одиночные кнопки, так и большими, как целое приложение .

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


3.17.2.1 Замена отрисовки, основанной на строках, на React

Общим паттерном в старых веб-приложениях является описание фрагментов DOM в виде строки и вставка их в DOM, например: $element.html(htmlString). Эти точки в кодовой базе идеально подходят для внедрения React. Просто перепишите строковое представление как компонент React.

Итак, следующая реализация jQuery ...


Код
    
  $('#root').html('<button id="okButton" class="btn btn-default">OK</button>');
  $('#okButton').on('click',() => {
     alert('Нажата кнопка "ОК"');
  });
  

... может быть переписана с использованием компонента React:


Код
    
  class Button extends React.Component {
    componentDidMount(){
      $('#okButton').on('click', this.onClick);
    }

    componentWillUnmount(){
      $('#okButton').off('click', this.onClick);
    }

    onClick(){
      alert('Hello!');
    }

    render(){
      return <button id="okButton" className="btn btn-success">ОК</button>;
    }
  }

  ReactDOM.render(<Button />, document.getElementById('root'));
  

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

Вместо этого мы будем использовать систему событий React и зарегистрируем обработчик кликов непосредственно в элементе React <button>:


Код
    
  class Button extends React.Component {
    onClick(){
      alert('Hello!');
    }

    render(){
      return <button id="okButton" onClick={this.onClick} className="btn btn-success">ОК</button>;
    }
  }

  ReactDOM.render(<Button />, document.getElementById('root'));
  

Вы можете иметь столько изолированных компонентов, сколько захотите, и использовать ReactDOM.render() для рендеринга их в разные контейнеры DOM. Постепенно при преобразовании всё большего количества вашего приложения в React вы сможете объединить их в более крупные компоненты и переместить некоторые из ReactDOM.render() вызовов выше по иерархии.