[Из песочницы] DOM-а хватит на всех, или как помирить ReactJS с тем фактом, что сторонние библиотеки меняют его DOM
Современные JavaScript фреймворки, и ReactJS не исключение, обычно требуют эксклюзивного доступа к DOM и им очень не нравится, когда кто-то без их ведома этот DOM меняет. Проблема в том, что существует огромное количество сторонних библиотек (например, плагины jQuery), которым необходимо в их подконтрольном поддереве что-нибудь да вропнуть, анвропнуть, перенести в другое место и т.д. Обычно в таких случаях мы видим в консольке нечто подобное:
К счастью, эта проблема довольно легко и быстро решается. В этом посте я попробую изложить решение пошагово, но, если вам неинтересно, или вы спешите, просто поскрольте вниз к ссылке на гист с готовым решением. Итак, начнем.
Кто виноват? Допустим, мы хотим использовать в нашем ReactJS проекте какой-нибудь мега-крутой редактор текста, по типу AceEditor или TinyMCE. Этот плагин берет элемент и превращает его в , с тулбаром и хайлатом, и он может выглядеть, например, так: function textarea2editor (parent){ var $parent= jQuery (parent); var $editor = jQuery ('
'); $editor.css ('background',»#333»); $editor.css («color»,»#efefef»); $parent.find ('textarea').replaceWith ($editor); /*…*/ return { setText: function (text){ $editor.html (text); }, /*…*/ } } Допустим, у нас есть ReactJS приложение, которое выводит Unix команды с заданным интервалом:var App = React.createClass ({ render: function () { return (
var Component = React.render (
componentDidMount: function (){ this.editor = textarea2editor (this.getDOMNode ()); this.editor.setText (this.props.contents); } А также метод componendDidUpdate, который будет обновлять содержимое
при каждом обновлении компоненты:componentDidUpdate: function (){ this.editor.setText (this.props.contents); } И вот у нас получился следующий фиддл.Когда же мы его запустим, мы увидим, что в «терминале» появляется только первая команда, но не остальные. И если мы откроем консольку, то увидим, почему это происходит:
Дело, конечно же, в том, что наш «редактор» заменил на
без ведома React, и теперь React в замешательстве, у него в виртуальном доме есть , а в реальном доме нет, и непонятно, как в таких условиях делать дифф и обновлять страницу.И что делать? К счастью, решение очень простое, но в голову оно мне пришло не сразу. Вдохновил меня на это решение опыт общения с AngularJS, где есть директива ngNonBindable, которая как бы говорит Ангуляру:
Я задумался, а нет ли в React-е чего-нибудь подобного. В документации об этом (прямо) не сказано, но зато сказано про метод shouldComponentUpdate, который возвращает булево значение, и если оно ложно, то React не станет обновлять не только компоненту, но и все её поддерево. То есть, он просто не станет вызывать методы componentWillUpdate, componentWillReceiveProps, render и т.д. Этот метод предлагается в качестве средства оптимизации, но подождите-ка, а если он не вызовет render, то поддерево компоненты в виртуальном DOM не изменится, значит дифф для этого виртуального поддерева и соответствующему ему реальному DOM в принципе не нужен, означает ли это, что при помощи этого «оптимизационного» метода, можно заставить Реакт игнорировать определенные часть подвластному ему DOM-a? Оказывается, можно, но если мы добавим в нашу компоненту:
shouldComponentUpdate: function (){ return false; } то ошибки-то исчезнут, но наш «терминал» все равно не будет обновляться. На самом деле, нам нужно заставить React игнорировать только , а не всю компоненту, неужели нам для этого придется писать RenderOnceTextarea и так всякий раз, когда мы хотим использовать компоненту из React.DOM? На самом деле, есть решение получше — написать компоненту ReactIgnore, которая всегда возвращает своих детей, и всегда возвращает ложное в shouldComponentUpdate:
var ReactIgnore = React.createClass ({ displayName: 'ReactIgnore', shouldComponentUpdate: function (){ return false; }, render: function (){ return React.Children.only (this.props.children); } }); Вот работающий фиддл.
tl; dr aka ссылка на гист Я скопипастил ReactIgnore из своего проекта и выложил в качества гиста (ахтунг, ES6 Harmony), пользуйтесь на здоровье.