Сегодня мы публикуем перевод материала, автор которого, проанализировав особенности работы с объектами в JavaScript, предлагает React-разработчикам методику ускорения приложений. В частности, речь идёт о том, что переменная, которой, как принято говорить, «присвоен объект», и которую часто называют просто «объектом», на самом деле, хранит не сам объект, а ссылку на него. Функции в JavaScript тоже являются объектами, поэтому вышесказанное справедливо и для них. Если помнить об этом, проектируя React-компоненты и критически анализируя их код, можно усовершенствовать их внутренние механизмы и улучшить производительность приложений.
const functionOne = function() { alert('Hello world!'); };
const functionTwo = function() { alert('Hello world!'); };
functionOne === functionTwo; // false
const functionThree = function() { alert('Hello world!'); };
const functionFour = functionThree;
functionThree === functionFour; // true
true
.const object1 = {};
const object2 = {};
const object3 = object1;
object1 === object2; // false
object1 === object3; // true
object1 = {}
, это приводит к заполнению некими данными участка памяти, выделенного специально для объекта object1
. object1
в виде адреса, по которому в памяти расположены структуры данных, относящиеся к объекту. Выполнение команды object2 = {}
приводит к выделению ещё одной области памяти, предназначенной специально для object2
. Расположены ли объекты obect1
и object2
в одной и той же области памяти? Нет, каждому из них выделен собственный участок. Именно поэтому при попытке сравнения object1
и object2
мы получаем false
. Эти объекты могут иметь идентичную структуру, но адреса в памяти, где они расположены, различаются, а при сравнении проверяются именно адреса.object3 = object1
, мы записываем в константу object3
адрес объекта object1
. Это — не новый объект. Данной константе назначается адрес уже существующего объекта. Проверить это можно так:const object1 = { x: true };
const object3 = object1;
object3.x = false;
object1.x; // false
object1
. Затем в константу object3
записывается тот же адрес. Изменение object3
приводит к изменению объекта в памяти. Это означает, что при обращении к объекту с использованием любой другой ссылки на него, например, той, что хранится в object1
, мы будем работать уже с его изменённой версией.render
. Очевидно то, что если компонент остался прежним, его не нужно рендерить повторно. Если ничего не меняется, функция render
возвратит то же, что и прежде, поэтому нет нужды её выполнять. Этот механизм делает React быстрым. Что-либо выводится на экран только тогда, когда это необходимо.==
. React не выполняет «мелкого» или «глубокого» сравнения объектов для того, чтобы определить их равенство. «Мелкое» сравнение (shallow comparison) — это понятие, используемое для описания сравнения каждой пары ключ-значение объекта — в противовес сравнению, при котором сопоставляются лишь адреса объектов в памяти (ссылки на них). При «глубоком» сравнении (deep comparison) объектов идут ещё дальше, и, если значением сравниваемых свойств объектов также являются объекты, выполняют и сравнение пар ключ-значение этих объектов. Этот процесс повторяется для всех объектов, вложенных в другие объекты. React ничего подобного не делает, выполняя лишь проверку на равенство ссылок.{ x: 1 }
на другой объект, который выглядит точно так же, React выполнит повторный рендеринг компонента, так как эти объекты находятся в разных областях памяти. Если вспомнить вышеприведённый пример, то, при изменении свойства компонента с object1
на object3
, React не будет повторно рендерить такой компонент, так как константы object1
и object3
ссылаются на один и тот же объект.class SomeComponent extends React.PureComponent {
get instructions() {
if (this.props.do) {
return 'Click the button: ';
}
return 'Do NOT click the button: ';
}
render() {
return (
<div>
{this.instructions}
<Button onClick={() => alert('!')} />
</div>
);
}
}
do
(do={true}
или do={false}
) компонента SomeComponent
.SomeComponent
(при изменении значения свойства do
с true
на false
и наоборот), повторно рендерится и элемент Button
. Обработчик onClick
, несмотря на то, что он всегда один и тот же, создаётся заново при каждом вызове функции render
. В результате оказывается, что при каждом выводе компонента в памяти создаётся новая функция, так как её создание выполняется в функции render
, ссылка на новый адрес в памяти передаётся в <Button />
, и компонент Button
также рендерится повторно, несмотря на то, что в нём совершенно ничего не изменилось.this
), то вы можете определить её за пределами компонента. Все экземпляры компонента будут использовать одну и ту же ссылку на функцию, так как во всех случаях это будет одна и та же функция. Вот как это выглядит:const createAlertBox = () => alert('!');
class SomeComponent extends React.PureComponent {
get instructions() {
if (this.props.do) {
return 'Click the button: ';
}
return 'Do NOT click the button: ';
}
render() {
return (
<div>
{this.instructions}
<Button onClick={createAlertBox} />
</div>
);
}
}
createAlertBox
, при каждом вызове render
, будет содержать одну и ту же ссылку на одну и ту же область в памяти. В результате повторный вывод Button
выполняться не будет.Button
имеет маленькие размеры и быстро рендерится, вышеописанную проблему, связанную с внутренним объявлением функций, можно встретить и в больших, сложных компонентах, на вывод которых требуется немало времени. Это может ощутимо замедлить React-приложение. В связи с этим имеет смысл следовать рекомендации, в соответствии с которой такие функции никогда не следует объявлять внутри метода render
.class SomeComponent extends React.PureComponent {
createAlertBox = () => {
alert(this.props.message);
};
get instructions() {
if (this.props.do) {
return 'Click the button: ';
}
return 'Do NOT click the button: ';
}
render() {
return (
<div>
{this.instructions}
<Button onClick={this.createAlertBox} />
</div>
);
}
}
SomeComponent
при нажатии на кнопку будут выводиться различные сообщения. Обработчик события элемента Button
должен быть уникальным для SomeComponent
. При передаче метода cteateAlertBox
неважно, будет ли выполняться повторный рендеринг SomeComponent
. Неважно и то, изменилось ли свойство message
. Адрес функции createAlertBox
не меняется, а это значит, что элемент Button
повторно рендериться не должен. Благодаря этому можно сэкономить системные ресурсы и улучшить скорость рендеринга приложения.render
применяется метод массива map
:class SomeComponent extends React.PureComponent {
render() {
return (
<ul>
{this.props.list.map(listItem =>
<li key={listItem.text}>
<Button onClick={() => alert(listItem.text)} />
</li>
)}
</ul>
);
}
}
SomeComponent
, неизвестно, что это будут за функции. Как решить эту головоломку?class SomeComponent extends React.PureComponent {
// У каждого экземпляра SomeComponent есть кэш обработчиков события нажатия на кнопку
// реализующих уникальный функционал.
clickHandlers = {};
// Создание обработчика или возврат существующего обработчика
// на основании уникального идентификатора.
getClickHandler(key) {
// Если для заданного уникального идентификатора нет обработчика, создадим его.
if (!Object.prototype.hasOwnProperty.call(this.clickHandlers, key)) {
this.clickHandlers[key] = () => alert(key);
}
return this.clickHandlers[key];
}
render() {
return (
<ul>
{this.props.list.map(listItem =>
<li key={listItem.text}>
<Button onClick={this.getClickHandler(listItem.text)} />
</li>
)}
</ul>
);
}
}
getClickHandler
. Этот метод, при первом вызове с неким значением, создаст функцию, уникальную для этого значения, поместит её в кэш и возвратит её. Все последующие вызовы этого метода с передачей ему того же значения приведут к тому, что он будет просто возвращать ссылку на функцию из кэша.SomeComponent
не приведёт к повторному рендерингу Button
. Аналогично, добавление элементов в свойство list
приведёт к динамическому созданию обработчиков событий для каждой кнопки.key
для каждого JSX-объекта, получаемого в результате работы метода map
.[ 'soda', 'pizza' ]
, а потом превратился в [ 'pizza' ]
, и вы при этом кэшировали обработчики событий с помощью команды вида listeners[0] = () => alert('soda')
, вы обнаружите, что когда пользователь щелкнет по кнопке, которой назначен обработчик с идентификатором 0, и которая, в соответствии с содержимым массива [ 'pizza' ]
, должна выводить сообщение pizza
, будет выведено сообщение soda
. По той же причине не рекомендуется использовать индексы массивов в качестве свойств, являющихся ключами.К сожалению, не доступен сервер mySQL