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

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

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

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

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

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

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

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

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

class SomePlugin extends React.Component {
  componentDidMount() {
    this.$el = $(this.el);
    this.$el.somePlugin();
  }

  componentWillUnmount() {
    this.$el.somePlugin('destroy');
  }

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

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

Интеграция с JQuery Chosen Plugin

Для более конкретного примера этих понятий давайте напишем минимальную оболочку для плагина Chosen , которая увеличивает <select> входы.

Заметка:

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

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

Если вы вызываете его на <select> узле DOM, он считывает атрибуты с исходного узла DOM, скрывает его встроенным стилем, а затем добавляет отдельный узел DOM со своим собственным визуальным представлением сразу после <select>. Затем он запускает события jQuery, чтобы уведомить нас об изменениях.

Предположим, что это API, к которому мы стремимся, с нашей <Chosen> оболочкой React:

function Example() {
  return (
    <Chosen onChange={value => console.log(value)}>
      <option>vanilla</option>
      <option>chocolate</option>
      <option>strawberry</option>
    </Chosen>
  );
}

Мы будем использовать его как неконтролируемый компонент для простоты. Сначала мы создадим пустой компонент с render() методом, в который мы вернемся, <select> завернутый в <div>:

class Chosen extends React.Component {
  render() {
    return (
      <div>
        <select className="Chosen-select" ref={el => this.el = el}>
          {this.props.children}
        </select>
      </div>
    );
  }
}

Обратите внимание, как мы завернули <select> в дополнительную <div>. Это необходимо, потому что Chosen добавит еще один элемент DOM сразу после <select> узла, который мы ему передали. Однако, что касается Реагента, <div> всегда есть только один ребенок. Таким образом мы гарантируем, что обновления React не будут конфликтовать с дополнительным узлом DOM, добавленным Chosen. Важно, что если вы измените DOM вне потока React, вы должны убедиться, что React не имеет причины касаться этих узлов DOM.

Затем мы реализуем крючки жизненного цикла. Нам нужно инициализировать Chosen с помощью ссылки ref на <select> узел componentDidMount и снести его в componentWillUnmount:

componentDidMount() {
  this.$el = $(this.el);
  this.$el.chosen();
}

componentWillUnmount() {
  this.$el.chosen('destroy');
}

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

<select className="Chosen-select" ref={el => this.el = el}>

Этого достаточно, чтобы получить наш компонент для рендеринга, но мы также хотим получать уведомления об изменениях стоимости. Для этого мы будем подписываться на change событие jQuery на <select> управляемом пользователем Chosen.

Мы не перейдем this.props.onChange непосредственно к Chosen, потому что реквизит компонента может со временем меняться, и это включает обработчики событий. Вместо этого мы объявим handleChange() метод, который вызывает this.props.onChange и подписывается на changeсобытие jQuery:

componentDidMount() {
  this.$el = $(this.el);
  this.$el.chosen();

  this.handleChange = this.handleChange.bind(this);
  this.$el.on('change', this.handleChange);
}

componentWillUnmount() {
  this.$el.off('change', this.handleChange);
  this.$el.chosen('destroy');
}

handleChange(e) {
  this.props.onChange(e.target.value);
}

Наконец, осталось еще одно. В React реквизит может меняться со временем. Например, <Chosen> компонент может получить разные дочерние элементы, если изменяется состояние родительского компонента. Это означает, что в точках интеграции важно, чтобы мы вручную обновляли DOM в ответ на обновления prop, поскольку мы больше не позволяем React управлять DOM для нас.

Документация Chosen показывает, что мы можем использовать jQuery trigger() API для уведомления об изменениях в исходном элементе DOM. Мы дадим React позаботиться об обновлении this.props.children внутри <select>, но мы также добавим componentDidUpdate() крючок жизненного цикла, который уведомляет о выбранных изменениях в списке детей:

componentDidUpdate(prevProps) {
  if (prevProps.children !== this.props.children) {
    this.$el.trigger("chosen:updated");
  }
}

Таким образом, Chosen будет знать, что обновляет свой элемент DOM, когда <select> дети, которым управляет React, меняются. Полная реализация Chosenкомпонента выглядит следующим образом:

class Chosen extends React.Component {
  componentDidMount() {
    this.$el = $(this.el);
    this.$el.chosen();

    this.handleChange = this.handleChange.bind(this);
    this.$el.on('change', this.handleChange);
  }
  
  componentDidUpdate(prevProps) {
    if (prevProps.children !== this.props.children) {
      this.$el.trigger("chosen:updated");
    }
  }

  componentWillUnmount() {
    this.$el.off('change', this.handleChange);
    this.$el.chosen('destroy');
  }
  
  handleChange(e) {
    this.props.onChange(e.target.value);
  }

  render() {
    return (
      <div>
        <select className="Chosen-select" ref={el => this.el = el}>
          {this.props.children}
        </select>
      </div>
    );
  }
}

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

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

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

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

Замена строковой визуализации с реакцией

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

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

$('#container').html('<button id="btn">Say Hello</button>');
$('#btn').click(function() {
  alert('Hello!');
});

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

function Button() {
  return <button id="btn">Say Hello</button>;
}

ReactDOM.render(
  <Button />,
  document.getElementById('container'),
  function() {
    $('#btn').click(function() {
      alert('Hello!');
    });
  }
);

Отсюда вы можете начать больше логики в компоненте и начать применять более распространенные практики React. Например, в компонентах лучше не полагаться на идентификаторы, потому что один и тот же компонент может отображаться несколько раз. Вместо этого мы будем использовать систему событий React и зарегистрируем обработчик кликов непосредственно в <button> элементе React :

function Button(props) {
  return <button onClick={props.onClick}>Say Hello</button>;
}

function HelloButton() {
  function handleClick() {
    alert('Hello!');
  }
  return <Button onClick={handleClick} />;
}

ReactDOM.render(
  <HelloButton />,
  document.getElementById('container')
);

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

Вложение Реагирует в представлении Backbone

Представления базовой линии обычно используют HTML-строки или строковые функции шаблонов для создания содержимого для своих элементов DOM. Этот процесс также можно заменить на рендеринг компонента React.

Ниже мы создадим обратный вызов Backbone ParagraphView. Он будет переопределять render() функцию Backbone для рендеринга <Paragraph> компонента React в элемент DOM, предоставляемый Backbone(this.el). Здесь мы также используем ReactDOM.render():

function Paragraph(props) {
  return <p>{props.text}</p>;
}

const ParagraphView = Backbone.View.extend({
  render() {
    const text = this.model.get('text');
    ReactDOM.render(<Paragraph text={text} />, this.el);
    return this;
  },
  remove() {
    ReactDOM.unmountComponentAtNode(this.el);
    Backbone.View.prototype.remove.call(this);
  }
});

Важно , что мы называем ReactDOM.unmountComponentAtNode() в removeметоде , так что React Разрегистрирует обработчик событий и другие ресурсы , связанные с деревом компонентов , когда он отсоединен.

Когда компонент удаляется из дерева React, очистка выполняется автоматически, но поскольку мы удаляем все дерево вручную, мы должны вызвать этот метод.

Интеграция с слоями моделей

Хотя обычно рекомендуется использовать однонаправленный поток данных, такой как React state , Flux или Redux , компоненты React могут использовать слой модели из других фреймворков и библиотек.

Использование базовых моделей в реакционных компонентах

Самый простой способ потребления моделей и коллекций Backbone из компонента React - прослушивать различные события изменений и вручную принудительно обновлять.

Компоненты, отвечающие за модели рендеринга, будут прослушивать 'change'события, тогда как компоненты, отвечающие за рендеринг коллекций, будут слушать 'add'и 'remove' события. В обоих случаях позвоните, this.forceUpdate() чтобы повторно перенести компонент с новыми данными.

В приведенном ниже примере Listкомпонент отображает коллекцию Backbone, используя Item компонент для визуализации отдельных элементов.

class Item extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange() {
    this.forceUpdate();
  }

  componentDidMount() {
    this.props.model.on('change', this.handleChange);
  }

  componentWillUnmount() {
    this.props.model.off('change', this.handleChange);
  }

  render() {
    return <li>{this.props.model.get('text')}</li>;
  }
}

class List extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange() {
    this.forceUpdate();
  }

  componentDidMount() {
    this.props.collection.on('add', 'remove', this.handleChange);
  }

  componentWillUnmount() {
    this.props.collection.off('add', 'remove', this.handleChange);
  }

  render() {
    return (
      <ul>
        {this.props.collection.map(model => (
          <Item key={model.cid} model={model} />
        ))}
      </ul>
    );
  }
}

Извлечение данных из базовых моделей

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

Одним из решений этого является извлечение атрибутов модели как простых данных всякий раз, когда они меняются, и сохраняйте эту логику в одном месте. Ниже представлен компонент более высокого порядка, который извлекает все атрибуты модели Backbone в состояние, передавая данные обернутому компоненту.

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

В приведенном ниже примере мы создадим копию атрибутов модели, чтобы сформировать начальное состояние. Мы подписываемся на changeсобытие (и отменим подписку на размонтирование), и когда это произойдет, мы обновим состояние с текущими атрибутами модели. Наконец, мы следим за тем, чтобы, если сам modelprop изменился, мы не забываем отписаться от старой модели и подписываться на новую.

Обратите внимание, что этот пример не должен быть исчерпывающим в отношении работы с Backbone, но он должен дать вам представление о том, как подойти к этому в общем виде:

function connectToBackboneModel(WrappedComponent) {
  return class BackboneComponent extends React.Component {
    constructor(props) {
      super(props);
      this.state = Object.assign({}, props.model.attributes);
      this.handleChange = this.handleChange.bind(this);
    }

    componentDidMount() {
      this.props.model.on('change', this.handleChange);
    }

    componentWillReceiveProps(nextProps) {
      this.setState(Object.assign({}, nextProps.model.attributes));
      if (nextProps.model !== this.props.model) {
        this.props.model.off('change', this.handleChange);
        nextProps.model.on('change', this.handleChange);
      }
    }

    componentWillUnmount() {
      this.props.model.off('change', this.handleChange);
    }

    handleChange(model) {
      this.setState(model.changedAttributes());
    }

    render() {
      const propsExceptModel = Object.assign({}, this.props);
      delete propsExceptModel.model;
      return <WrappedComponent {...propsExceptModel} {...this.state} />;
    }
  }
}

Чтобы продемонстрировать, как его использовать, мы подключим NameInput компонент React к модели Backbone и обновим его firstName атрибут каждый раз, когда изменяется вход:

function NameInput(props) {
  return (
    <p>
      <input value={props.firstName} onChange={props.handleChange} />
      <br />
      My name is {props.firstName}.
    </p>
  );
}

const BackboneNameInput = connectToBackboneModel(NameInput);

function Example(props) {
  function handleChange(e) {
    props.model.set('firstName', e.target.value);
  }

  return (
    <BackboneNameInput
      model={props.model}
      handleChange={handleChange}
    />

  );
}

const model = new Backbone.Model({ firstName: 'Frodo' });
ReactDOM.render(
  <Example model={model} />,
  document.getElementById('root')
);

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