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

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


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

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

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


Чтобы это продемонстрировать, давайте построим обёртку для известного плагина 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. Если плагин не предоставляет метод очистки, вам, вероятно, придется предоставить свой собственный, чтобы удалить слушатели событий, зарегистрированных плагином, предотвращая тем самым утечку памяти.


Для более конкретного примера, давайте напишем минимальную оболочку для плагина 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



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() вызовов выше по иерархии.