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 .
Во-первых, давайте посмотрим, что 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});
}
Обратите внимание на одну очень важную деталь: как мы вызываем диалог из приложения.
{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});
}
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()
вызовов выше по иерархии.