Книга: React.js. Быстрый старт
Назад: 1. Hello World
Дальше: 3. Excel: необычный табличный компонент

2. Жизнь компонента

После того как вы узнали о порядке использования готовых DOM-компонентов, пора приступить к изучению способов создания собственных компонентов.

Самый минимум

API-интерфейс для создания нового компонента имеет следу­ющий вид:

var MyComponent = React.createClass({

  /* спецификации */

});

В качестве «спецификаций» используется объект JavaScript, имеющий один обязательный метод render() и несколько необязательных методов и свойств. Самый простой пример может выглядеть следующим образом:

var Component = React.createClass({

  render: function() {

    return React.DOM.span(null, "I'm so custom");

  }

});

Как видите, единственное, что нужно сделать, — это реализовать метод render(). Он должен возвратить React-компонент, именно поэтому вы видите в фрагменте кода компонент span; просто текст возвратить нельзя.

Использование вашего компонента в приложении похоже на использование DOM-компонентов:

ReactDOM.render(

  React.createElement(Component),

  document.getElementById("app")

);

Результат отображения вашего пользовательского компонента показан на рис. 2.1.

Один из способов создания экземпляра вашего компонента — применение метода React.createElement(). При создании сразу нескольких экземпляров применяется фабрика:

var ComponentFactory = React.createFactory(Component);

 

ReactDOM.render(

  ComponentFactory(),

  document.getElementById("app")

);

Учтите, что уже известные вам методы семейства React.DOM.* фактически являются всего лишь удобными оболочками вокруг React.createElement(). Иными словами, этот код также работает с DOM-компонентами:

ReactDOM.render(

  React.createElement("span", null, "Hello"),

  document.getElementById("app")

);

02_01.tif 

Рис. 2.1. Ваш первый пользовательский компонент

Как видите, DOM-элементы, в отличие от функций JavaScript, определяются в виде строк, как и в случае с пользовательскими компонентами.

Свойства

Ваши компоненты получают свойства и в зависимости от их значений по-разному выводятся на экран или ведут себя в приложении. Все свойства доступны через объект this.props. Рассмотрим пример:

var Component = React.createClass({

  render: function() {

    return React.DOM.span(null, "My name is " +

      this.props.name);

  }

});

Передача свойства при отображении компонента выглядит следующим образом:

ReactDOM.render(

  React.createElement(Component, {

    name: "Bob",

  }),

  document.getElementById("app")

);

Результат показан на рис. 2.2.

161186.png

Свойство this.props следует считать пригодным только для чтения. Свойства можно с успехом применять для переноса настроек из родительских компонентов в дочерние (и, как будет показано далее, из дочерних компонентов — в родительские). Если вас все же что-то подстегивает назначить свойство с применением this.props, просто воспользуйтесь вместо этого дополнительными переменными или свойствами спецификации объекта вашего компонента (как в образце this.thing в противоположность образцу this.props.thing). На деле в браузерах, отвечающих спецификации ECMAScript5, вам не захочется видоизменять свойство this.props, поскольку:

> Object.isFrozen(this.props) === true; // истина

02_02.tif 

Рис. 2.2. Использование свойств компонента

propTypes

Для объявления списка свойств, принимаемых вашим компонентом, и их типов вы можете добавить в свои компоненты свойство propTypes (типы свойств). Рассмотрим пример:

var Component = React.createClass({

  propTypes: {

    name: React.PropTypes.string.isRequired,

  },

  render: function() {

    return React.DOM.span(null, "My name is " + this.props.name);

  }

});

Свойство propTypes не обязательно использовать, но оно предоставляет два преимущества:

вы заранее объявляете, какие свойства ожидает ваш компонент. Пользователям вашего компонента не придется тщательно изучать исходный (потенциально весьма длинный) код функции render(), чтобы определить, какими свойствами можно воспользоваться для настройки компонента;

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

Рассмотрим проверку в действии. Выражение name: React.PropTy­pes.string.isRequired явно просит для свойства name обязательное строковое значение. Если вы забудете передать значение, в консоли появится предупреждение (рис. 2.3):

ReactDOM.render(

  React.createElement(Component, {

    // name: "Bob",

  }),

  document.getElementById("app")

);

Предупреждение также будет получено при предоставлении значения неверного типа, скажем целого числа (рис. 2.4):

React.createElement(Component, {

  name: 123,

})

02_03.tif 

Рис. 2.3. Предупреждение, появляющееся в случае, когда обязательное свойство не предоставлено

02_04.tif 

Рис. 2.4. Предупреждение, которое появляется при предоставлении значения неверного типа

Рисунок 2.5 даст представление обо всех доступных свойствах PropTypes, которыми можно воспользоваться для объявления ваших ожиданий.

02_05.tif 

Рис. 2.5. Список всех типов, используемых в React.PropTypes

166513.png

Объявление propTypes в ваших компонентах не является обязательным (это означает, что здесь можно перечислить лишь некоторые, а не абсолютно все свойства). Можно назвать идею объявления не всех свойств порочной, но имейте в виду, что можете столкнуться с этим при отладке чужого кода.

Значения свойств, используемые по умолчанию. Когда ваши компоненты получают необязательные свойства, следует уделить особое внимание их работоспособности в том случае, когда свойства не объявляются. Это неизбежно приводит к применению защитного шаблона, например:

var text = 'text' in this.props ? this.props.text : '';

Избавиться от необходимости написания такого кода (сконцентрировавшись на более важных аспектах программы) можно, реализовав метод getDefaultProps():

var Component = React.createClass({

  propTypes: {

    firstName: React.PropTypes.string.isRequired,

    middleName: React.PropTypes.string,

    familyName: React.PropTypes.string.isRequired,

    address: React.PropTypes.string,

  },

 

  getDefaultProps: function() {

    return {

      middleName: '',

      address: 'n/a',

    };

  },

 

  render: function() {/* ... */}

});

Как видите, getDefaultProps() возвращает объект, предоставляя допустимые значения для каждого необязательного свойства (из числа тех, для которых не указывается .isRequired).

Состояние

До сих пор примеры были довольно статичными (или «не име­ющими состояния»). Они преследовали простую цель: дать вам представление о создании блоков при составлении вашего пользовательского интерфейса. Но истинный блеск React (на фоне усложнения ситуации при работе с традиционной DOM-моделью браузера) проявляется, когда изменяются данные в вашем приложении. В React используется понятие состояния, которое представляет собой данные, используемые вашими компонентами для их самостоятельного отображения на экране. При изменении состояния React перестраивает пользовательский интерфейс без какого-либо вашего участия. Таким образом, после начального создания пользовательского интерфейса (в функции render()) вам останется лишь обновлять данные, совершенно не заботясь насчет изменений пользовательского интерфейса. По сути, ваш метод render() уже предоставил план внешнего вида компонента.

161198.png

Обновление пользовательского интерфейса после вызова метода setState() выполняется с использованием механизма выстраивания очереди, который рационально группирует изменения, поэтому непосредственное обновление this.state (чего не следует делать) может привести к непредсказуемому поведению. Считайте, что объект this.state, так же как и this.props, предназначен лишь для чтения — не только потому, что его применение в другом качестве считается неприемлемым, но и потому, что такое применение может привести к неожиданным результатам. Аналогично никогда не вызывайте this.render() самостоятельно, лучше предоставьте такое право библиотеке React, чтобы дать ей возможность сгруппировать изменения, выявить их наименьшее количество и вызвать render() в самый подходящий момент.

Точно так же, как свойства доступны путем обращения к объекту this.props, к состоянию можно обратиться через объект this.state. Для обновления состояния предназначен метод this.setState(). При вызове this.setState() React вызывает ваш метод render() и обновляет пользовательский интерфейс.

162135.png

React обновляет пользовательский интерфейс при вызове setState(). Это наиболее распространенный вариант, но чуть позже вы увидите, что есть еще и запасной вариант. Предотвратить обновление пользовательского интерфейса можно, вернув false в специальном методе жизненного цикла — shouldComponentUpdate().

Компонент textarea, отслеживающий свое состояние

Создадим новый компонент — textarea (текстовая область), сохраняющий количество набранных в нем символов (рис. 2.6).

02_06.tif 

Рис. 2.6. Конечный результат работы пользовательского компонента textarea

Вы (как и все другие потребители этого многократно используемого компонента) можете воспользоваться новым компонентом следующим образом:

ReactDOM.render(

  React.createElement(TextAreaCounter, {

    text: "Bob",

  }),

  document.getElementById("app")

);

Теперь реализуем компонент. Начнем с создания версии «без состояния», не обрабатывающей обновления, поскольку она не слишком отличается от всех предыдущих примеров:

var TextAreaCounter = React.createClass({

  propTypes: {

    text: React.PropTypes.string,

  },

 

  getDefaultProps: function() {

    return {

      text: '',

    };

  },

 

  render: function() {

    return React.DOM.div(null,

      React.DOM.textarea({

        defaultValue: this.props.text,

      }),

      React.DOM.h3(null, this.props.text.length)

    );

  }

});

161208.png

Возможно, вы заметили, что textarea в предыдущем фрагменте кода получает свойство defaultValue, в отличие от привычного вам дочернего элемента text в HTML. Все дело в небольшой разнице между React и традиционным HTML, которая проявляется при работе с формами. Этот вопрос рассматривается в главе 4, и спешу заверить, что различий не так уж и много. Кроме того, вы поймете, что эти различия вполне резонны и призваны упростить вашу жизнь разработчика.

Как видите, компонент получает необязательное строковое свойство text и выводит текстовую область с заданным значением, а также получает элемент <h3>, который показывает длину строки (рис. 2.7).

Следующий шаг заключается в превращении этого лишенного состояния компонента в компонент с поддержкой состояния. Иными словами, получим компонент, обрабатывающий некие данные (состояние) и использующий их для исходного вывода самого себя на экран с последующим самостоятельным обновлением (повторным выводом) при их изменении.

02_07.tif 

Рис. 2.7. Компонент TextAreaCounter в работе

Реализуйте в вашем компоненте метод getInitialState(), чтобы быть уверенными, что работа всегда будет вестись с допустимыми данными:

getInitialState: function() {

  return {

    text: this.props.text,

  };

},

Данные, обрабатываемые этим компонентом, являются простым текстом в текстовой области, поэтому у состояния есть только одно свойство text, доступ к которому можно получить через выражение this.state.text. Изначально (в getInitialState()) вы просто копируете свойство text. Позже при изменении данных (в ходе набора пользователем текста в текстовой области) компонент обновляет свое состояние, используя вспомогательный метод:

_textChange: function(ev) {

  this.setState({

    text: ev.target.value,

  });

},

Состояние всегда обновляется с помощью метода this.setState(), который получает объект и объединяет его с уже существующими в this.state данными. Нетрудно догадаться, что _textChange() является отслеживателем событий, который получает объект события ev и извлекает из него текст, введенный в текстовую область.

Остается лишь обновить метод render() для использования вместо this.props свойства this.state и установить отслеживатель событий:

render: function() {

  return React.DOM.div(null,

    React.DOM.textarea({

      value: this.state.text,

      onChange: this._textChange,

    }),

    React.DOM.h3(null, this.state.text.length)

  );

}

Теперь, как только пользователь что-нибудь набирает в текстовой области, значение счетчика обновляется, чтобы соответствовать содержимому этой области (рис. 2.8).

02_08.tif 

Рис. 2.8. Набор текста в текстовой области

Немного о DOM-событиях

Во избежание путаницы насчет следующей строки необходимо кое-что пояснить:

onChange: this._textChange

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

Обработка событий в прежние времена

Чтобы выполнять какие-либо действия, очень удобно применять встроенные обработчики событий:

<button onclick="doStuff">

При всем удобстве и легкой узнаваемости (отслеживатель событий находится там же, где и пользовательский интерфейс) пользоваться слишком большим количеством разбросанных подобным образом отслеживателей событий крайне неэффективно. Также трудно пользоваться на одной и той же кнопке более чем одним отслеживателем, особенно если эта кнопка является не вашим, а чьим-то «компонентом» или входит в другую библиотеку и вам не хочется туда внедряться и «править» или разветвлять код. Именно поэтому в мире DOM-модели для установки отслеживателей используются метод element.addEventListener (что приводит к наличию кода в двух и более местах) и делегирование событий (для решения проблем производительности). Делегирование событий означает, что отслеживание событий осуществляется в родительском узле, скажем в <div>, содержащем множество кнопок, и для всех кнопок устанавливается один отслеживатель.

С использованием делегирования событий выполняется следующее:

<div id="parent">

  <button id="ok">OK</button>

  <button id="cancel">Cancel</button>

</div>

 

<script>

document.getElementById('parent').addEventListener('click',

  function(event) {

  var button = event.target;

 

  // Выполнение разных действий на основе того,

  // какая из кнопок была нажата

  switch (button.id) {

    case 'ok':

      console.log('OK!');

      break;

    case 'cancel':

      console.log('Cancel');

      break;

    default:

      new Error('Непредвиденный идентификатор кнопки');

  };

});

</script>

Со своей работой этот код справляется, но у него имеются недостатки:

объявление отслеживателя находится далеко от компонента пользовательского интерфейса, что затрудняет поиск и отладку кода;

делегирование с неизменным использованием инструкции switch создает ненужный шаблонный код непосредственно перед переходом к реальным действиям (в данном случае к реакции на нажатие кнопки);

браузерная несовместимость (которая здесь не рассматривается) требует от этого кода более пространного решения.

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

в дополнение к методу addEventListener требуется применение метода attachEvent;

в самом начале кода отслеживателя требуется применение выражения var event = event || window.event;;

требуется применение выражения var button = event.tar­get || event.srcElement;.

Все эти необходимые и весьма неприятные нюансы в конечном итоге наводят на мысль о применении какой-нибудь библиотеки, связанной с обработкой событий. Но зачем добавлять еще одну библиотеку (и изучать дополнительные API-интерфейсы), когда React поставляется в комплекте с решениями, избавляющими от всех неприятностей, связанных с обработкой событий?

Обработка событий в React

Чтобы охватить и привести к единому формату все браузерные события, в React используются искусственные события, ликвидирующие проблему несовместимости браузеров. Это позволяет вам быть уверенными, что свойство event.target доступно во всех браузерах. Именно поэтому в фрагменте кода TextAreaCounter нужно лишь воспользоваться свойством ev.target.value — и все заработает. Это также означает, что API-интерфейс для отмены событий един для всех браузеров; иными словами, методы event.stopPropagation() и event.preventDefault() работают даже на старых версиях IE.

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

Для обработчиков событий в React применяется синтаксис с использованием «верблюжьего» регистра букв (camelCase), поэтому вместо onclick используется форма записи onClick.

Если по каким-то причинам вам понадобится исходное браузерное событие, оно доступно в виде свойства event.nativeEvent, но вряд ли вам это когда-либо пригодится.

И еще: событие onChange (в том же виде, в каком оно использовалось в примере с текстовой областью) ведет себя согласно вашим ожиданиям: оно инициируется, когда пользователь выполняет набор текста, а не после того, как набор будет завершен и произойдет переход за пределы данного поля, как это происходит в обычной DOM-модели.

Сравнение свойств и состояния

Вы уже знаете, что при решении задачи отображения вашего компонента в методе render() у вас есть доступ к свойствам через выражение this.props и к состоянию — через this.state. Может возникнуть вопрос, когда следует использовать одно из этих выражений, а когда — другое.

Свойства — это механизм, предназначенный для внешнего мира (для пользователей компонента) и служащий для настройки вашего компонента. А состояние относится к работе с внутренними данными. Поэтому, если искать аналогии в объектно-ориентированном программировании, this.props является подобием аргументов, переданных конструктору класса, а this.state можно представить в виде набора ваших закрытых свойств.

Свойства в исходном состоянии: антишаблон

Ранее уже был показан пример использования выражения this.props внутри метода getInitialState():

getInitialState: function() {

  return {

    text: this.props.text,

  };

},

Фактически этот пример относится к антишаблонам. В идеале используется любая комбинация выражений this.state и this.props, подходящая для создания пользовательского интерфейса в методе render().

Однако иногда приходится брать значение, переданное вашему компоненту, и использовать его для построения исходного состояния. Здесь нет ничего крамольного, за исключением того, что код, вызывающий ваш компонент, может предполагать, что свойство (text в предыдущем примере) всегда будет иметь самое последнее значение, а пример противоречит этому предположению. Чтобы предположения не вызывали никаких сомнений, достаточно просто воспользоваться другим именем, например вместо text назвать свойство как-нибудь вроде defaultText или initialValue:

propTypes: {

  defaultValue: React.PropTypes.string

},

 

getInitialState: function() {

  return {

    text: this.props.defaultValue,

  };

},

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

Доступ к компоненту извне

Позволить себе роскошь запускать совершенно новое React-приложение можно не всегда. Порой приходится внедряться в существующее приложение или в сайт и постепенно переходить к React. К счастью, библиотека React была сконструирована для работы с любым ранее созданным и имеющимся в вашем распоряжении базовым кодом. Ведь те, кто начинал создавать React, не могли переписать целиком огромное приложение (Facebook).

Один из способов, позволяющих вашему React-приложению общаться с внешним миром, заключается в получении ссылки на выводимый вами компонент с помощью метода ReactDOM.render() и ее использовании за пределами компонента:

var myTextAreaCounter = ReactDOM.render(

  React.createElement(TextAreaCounter, {

    defaultValue: "Bob",

  }),

  document.getElementById("app")

);

Теперь можно воспользоваться компонентом myTextAreaCounter для доступа к тем же методам и свойствам, к которым обычно обращаются с помощью выражения this внутри компонента. Можно даже манипулировать компонентами, используя вашу консоль JavaScript (рис. 2.9).

02_09.tif 

Рис. 2.9. Обращение к выведенному на экран компоненту с помощью ссылки

В этой строке кода устанавливается новое состояние:

myTextAreaCounter.setState({text: "Hello outside

  world!"});

В этой строке кода берется ссылка на основной родительский DOM-узел, создаваемый React:

var reactAppNode = ReactDOM.findDOMNode(myTextAreaCounter);

Это первый дочерний элемент родительского <div id="app">, где библиотеке React предписывается творить чудеса:

reactAppNode.parentNode === document.getElementById('app'); // true

А вот как можно получить доступ к свойствам и состоянию:

myTextAreaCounter.props; // Object { defaultValue: "Bob"}

myTextAreaCounter.state;

// Object { text: "Hello outside world!"}

166836.png

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

// Антипример

myTextAreaCounter.setState({text: 'NOOOO'});

Изменение  свойств на лету

Как известно, настройка компонента осуществляется с помощью его свойств.

Соответственно, изменение свойств извне после того, как компонент был создан, должно быть обоснованным. Но ваш компонент должен быть готов к обработке подобного развития событий.

Если посмотреть на метод render() из предыдущих примеров, в нем используется лишь выражение this.state:

render: function() {

  return React.DOM.div(null,

    React.DOM.textarea({

      value: this.state.text,

      nChange: this._textChange,

    }),

    React.DOM.h3(null, this.state.text.length)

  );

}

Если свойства изменятся за пределами компонента, это не возымеет никакого эффекта на экране. Иными словами, содержимое текстовой области после того, как вы сделаете следующее, не изменится:

myTextAreaCounter = ReactDOM.render(

  React.createElement(TextAreaCounter, {

    defaultValue: "Hello", // ранее известное как "Bob"

  }),

  document.getElementById("app")

);

 

161218.png

Даже притом что компонент myTextAreaCounter переписан с использованием нового вызова ReactDOM.render(), состояние приложения останется прежним. React ничего не стирает, а сопоставляет состояние приложения до и после. При этом он вносит минимальные изменения.

Теперь содержимое this.props изменилось (но содержимое пользовательского интерфейса осталось прежним):

myTextAreaCounter.props; // Object { defaultValue="Hello"}

161225.png

Обновление пользовательского интерфейса выполняется путем установки состояния:

// Антипример

myTextAreaCounter.setState({text: 'Hello'});

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

Если нужно корректно справиться с внешним вторжением (изменением свойств), к нему можно приготовиться, реализовав метод componentWillReceiveProps():

componentWillReceiveProps: function(newProps) {

  this.setState({

    text: newProps.defaultValue,

  });

},

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

Методы управления  жизненным циклом

Метод componentWillReceiveProps() из предыдущего фрагмента кода относится к так называемым методам управления жизненным циклом, предлагаемым React. Эти методы можно использовать для отслеживания изменений в ваших компонентах. К числу других методов управления жизненным циклом, которые могут быть вами реализованы, относятся следующие.

componentWillUpdate(). Выполняется до того, как метод render() вашего компонента будет вызван еще раз (в результате изменений свойств или состояния).

componentDidUpdate(). Выполняется после завершения работы метода render() и применения новых изменений в отношении исходной DOM-модели.

componentWillMount(). Выполняется перед вставкой узла в DOM-модель.

componentDidMount(). Выполняется после вставки узла в DOM-модель.

componentWillUnmount(). Выполняется непосредственно перед тем, как компонент удаляется из DOM-модели.

shouldComponentUpdate(newProps, newState). Вызывается перед вызовом метода componentWillUpdate() и дает возможность возвратить false и отменить обновление, что означает отказ от вызова метода render(). Пригодится в тех местах приложения, для которых критична производительность, когда вы полагаете, что ничего важного не изменилось и повторный вывод на экран необязателен. Это решение принимается на основе сравнения аргумента newState с существующим значением выражения this.state и сравнения newProps с this.props или просто на основании сведений о том, что компонент статичен и не подвергается изменениям. (Соответствующий пример будет вскоре рассмотрен.)

Примеры управления жизненным циклом

Тотальная регистрация

Чтобы лучше разобраться в жизни компонента, добавим к компоненту TextAreaCounter регистрацию. Просто реализуем все методы управления жизненным циклом для регистрации их вызова наряду с показом всех их аргументов в консоли:

var TextAreaCounter = React.createClass({

 

  _log: function(methodName, args) {

    console.log(methodName, args);

  },

  componentWillUpdate: function() {

    this._log('componentWillUpdate', arguments);

  },

  componentDidUpdate: function() {

    this._log('componentDidUpdate', arguments);

  },

  componentWillMount: function() {

    this._log('componentWillMount', arguments);

  },

  componentDidMount: function() {

    this._log('componentDidMount', arguments);

  },

  componentWillUnmount: function() {

    this._log('componentWillUnmount', arguments);

  },

 

  // ...

  // продолжение реализации, render() и т.д.

 

};

Все происходящее после загрузки страницы показано на рис. 2.10.

02_10.tif 

Рис. 2.10. Установка компонента

Как видите, были вызваны два метода без аргументов. Обычно из этих двух методов наиболее интересен componentDidMount(). Если нужно, доступ к только что установленному DOM-узлу можно получить путем вызова метода ReactDOM.findDOMNode(this) (например, для получения габаритных размеров компонента). Теперь, когда ваш компонент ожил, можно выполнять с ним любые действия по инициализации.

Интересно, что произойдет, если набрать s, превращая текст в Bobs? (См. рис. 2.11.)

02_11.tif 

Рис. 2.11. Обновление компонента

Будет вызван метод componentWillUpdate(nextProps, nextState) с новыми данными, которые будут использованы для повторного отображения компонента. Первый аргумент представляет собой будущее значение свойства this.props (которое в данном примере не изменяется), а второй — будущее значение нового свойства this.state. Третьим по значимости является контекст context, который на данном этапе особого интереса для нас не представляет. Вы можете сравнить аргументы (например, newProps) с текущим значением this.props и решить, нужно ли совершать с ними какие-либо действия.

Как видите, после метода componentWillUpdate() вызван метод componentDidUpdate(oldProps, oldState) с передачей ему значений свойств и состояния до изменения. Он позволяет работать с DOM, когда компонент уже обновлен. В нем можно указать выражение this.setState(), чего нельзя сделать в componentWillUpdate().

Предположим, что вам требуется ограничить количество символов, набираемых в текстовой области. Это нужно делать в обработчике события _textChange(), вызываемом при наборе пользователем текста. Но что, если кто-нибудь (кто наивнее и моложе вас) вызовет setState() за пределами компонента? (Как уже ранее упоминалось, такой вызов — порочная практика.) Сможете ли вы защитить согласованность и нормальное состояние своего компонента? Разумеется. Вы можете провести проверку в методе componentDidUpdate() и, если количество символов превысит разрешенное, вернуть состояние к его прежнему значению. То есть нужно сделать нечто подобное:

componentDidUpdate: function(oldProps, oldState) {

  if (this.state.text.length > 3) {

    this.replaceState(oldState);

  }

},

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

 

161230.png

Обратите внимание на использование метода replaceState() вместо setState(). Метод setState(obj) объединяет свойства obj с теми, которые имелись в this.state, а метод replaceState() полностью все перезаписывает.

Использование примеси

В предыдущем примере были показаны регистрируемые вызовы четырех из пяти методов управления жизненным циклом. Пятый метод, componentWillUnmount(), лучше всего продемонстрировать при наличии дочерних компонентов, удаляемых их родителем. В этом примере вам нужно зарегистрировать все изменения в обоих дочерних компонентах и в родительском компоненте. Поэтому введем новое понятие для многократно используемого кода: примесь, или миксин (mixin).

Примесью называют объект JavaScript, содержащий коллекцию методов и свойств. Примеси предназначены не для самостоятельного использования, а для включения (подмешивания) в свойства другого объекта. В примере регистрации примесь может иметь следующий вид:

var logMixin = {

  _log: function(methodName, args) {

    console.log(this.name + '::' + methodName, args);

  },

  componentWillUpdate: function() {

    this._log('componentWillUpdate', arguments);

  },

  componentDidUpdate: function() {

    this._log('componentDidUpdate', arguments);

  },

  componentWillMount: function() {

    this._log('componentWillMount', arguments);

  },

  componentDidMount: function() {

    this._log('componentDidMount', arguments);

  },

  componentWillUnmount: function() {

    this._log('componentWillUnmount', arguments);

  },

};

В мире, где React не используется, можно применить цикл for-in и скопировать все свойства в новый объект, получив все функциональные возможности примеси. В мире React в вашем распоряжении имеется сокращенная форма записи: свойство mixins, имеющее следующий вид:

var MyComponent = React.createClass({

 

  mixins: [obj1, obj2, obj3],

 

  // все остальные методы ...

 

};

Вы присваиваете свойству mixins массив объектов JavaScript, а React берет на себя всю остальную работу. Включение logMixin в ваш компонент выглядит следующим образом:

var TextAreaCounter = React.createClass({

  name: 'TextAreaCounter',

  mixins: [logMixin],

  // все остальное ..

};

Как видите, во фрагменте кода также добавляется удобное свойство name, чтобы идентифицировать вызывающий код.

Если запустить пример с примесью, вы увидите регистрацию в действии (рис. 2.12).

02_12.tif 

Рис. 2.12. Использование примеси и идентификация компонента

Применение дочернего компонента

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

Часть счетчика может быть выделена в свой собственный компонент:

var Counter = React.createClass({

  name: 'Counter',

  mixins: [logMixin],

  propTypes: {

    count: React.PropTypes.number.isRequired,

  },

  render: function() {

    return React.DOM.span(null, this.props.count);

  }

});

Этот компонент составляет только часть счетчика — он выводит на экран контейнер <span> и не работает с состоянием, а только отображает свойство count, задаваемое родительским компонентом. Он также подмешивает logMixin для регистрации вызовов методов управления жизненным циклом.

Теперь обновим метод render() родительского компонента TextAreaCounter. При определенных условиях он должен использовать компонент Counter, и если count имеет значение 0 — число даже не показывается:

render: function() {

  var counter = null;

  if (this.state.text.length > 0) {

    counter = React.DOM.h3(null,

        React.createElement(Counter, {

            count: this.state.text.length,

        })

    );

    }

    return React.DOM.div(null,

        React.DOM.textarea({

            value: this.state.text,

            onChange: this._textChange,

        }),

        counter

    );

}

Когда текстовая область пуста, переменная counter имеет значение null. Когда в ней имеется какой-нибудь текст, переменная counter содержит часть пользовательского интерфейса, отвечающую за отображение количества символов. Быть встроенным в качестве аргументов главного компонента React.DOM.div всему пользовательскому интерфейсу нет никакой необходимости. Вы можете присвоить фрагменты пользовательского интерфейса переменным и применять их при возникновении определенных условий.

Теперь можно наблюдать за регистрируемыми методами управления жизненным циклом для обоих компонентов. На рис. 2.13 показано, что произойдет, когда загрузится страница, а затем изменится содержимое текстовой области.

02_13.tif 

Рис. 2.13. Установка и обновление двух компонентов

Можно отследить, как дочерний компонент устанавливается и обновляется раньше своего родительского компонента.

На рис. 2.14 показано, что произойдет после удаления текста из текстовой области; значение count станет нулевым. В этом случае дочерний компонент Counter получит значение null и его DOM-узел будет удален из дерева DOM-модели после того, как вас уведомят с помощью функции обратного вызова compo­nentWillUnmount.

02_14.tif 

Рис. 2.14. Демонтаж компонента counter

Выигрыш в производительности: предотвращение обновлений компонентов

Последний метод управления жизненным циклом, о котором вы должны знать, особенно при создании критичных к производительности частей вашего приложения, называется shouldCompo­nentUpdate(nextProps, nextState). Он вызывается перед вызовом метода componentWillUpdate() и предоставляет вам возможность отменить обновление (если вы посчитаете его ненужным).

Существует класс компонентов, использующих в своих методах render() только this.props и this.state, не прибегая к дополнительным вызовам функций. Эти компоненты называются чистыми компонентами. Они могут реализовать метод shouldComponentUpdate() и сравнить состояние и свойства до и после, а при отсутствии каких-либо изменений — возвращать false, экономя при этом долю вычислительных мощностей. Кроме того, бывают чисто статичные компоненты, не использующие ни свойства, ни состояние. Такие компоненты могут сразу же возвращать false.

Посмотрим, что произойдет с вызовом методов render() и реализацией shouldComponentUpdate() для получения выгоды в отношении производительности.

Сначала возьмем новый компонент Counter. Удалим из него примесь регистрирования и вместо этого станем отправлять регистрационную запись на консоль при каждом вызове метода render():

var Counter = React.createClass({

  name: 'Counter',

  // mixins: [logMixin],

  propTypes: {

    count: React.PropTypes.number.isRequired,

  },

  render() {

    console.log(this.name + '::render()');

    return React.DOM.span(null, this.props.count);

  }

});

Сделаем то же самое в компоненте TextAreaCounter:

var TextAreaCounter = React.createClass({

  name: 'TextAreaCounter',

  // mixins: [logMixin],

 

  // все остальные методы ...

 

  render: function() {

    console.log(this.name + '::render()');

    // ... и вся остальная часть вывода на экран

  }

});

Когда страница будет загружена и вместо "Bob" будет вставлена строка "LOL", вы сможете увидеть результат, показанный на рис. 2.15.

02_15.tif 

Рис. 2.15. Повторный вывод на экран обоих компонентов

Как видите, обновление текста приводит к вызову метода render() компонента TextAreaCounter, который, в свою очередь, становится причиной вызова метода render() компонента Counter. При замене "Bob" строкой "LOL" количество символов до и после обновления не меняется, следовательно, изменений в пользовательском интерфейсе счетчика не происходит и в вызове метода render() компонента Counter нет никакой необходимости. Вы можете помочь React оптимизировать этот случай, реализовав метод shouldCompo­nentUpdate() и возвратив false, когда в последующем выводе на экран нет необходимости. Метод получает будущие значения свойств и состояния (в состоянии данный компонент не нуждается) и внутри себя сравнивает текущие и следующие значения:

shouldComponentUpdate(nextProps, nextState_ignore) {

  return nextProps.count !== this.props.count;

},

Выполнение замены "Bob" на "LOL" не заставляет больше Counter заново выводиться на экран (рис. 2.16).

02_16.tif 

Рис. 2.16. Выигрыш в производительности: экономия одного цикла повторного вывода на экран

PureRenderMixin

Реализация shouldComponentUpdate() не отличается особой сложностью. И не такая уж трудная задача — превратить эту реализацию в универсальную, поскольку вы всегда сравниваете this.props с nextProps и this.state с nextState. React предоставляет одну такую универсальную реализацию в виде примеси, которую можно включать в любой компонент.

Вот как это делается:

<script src="react/build/react-with-addons.js"></script>

<script src="react/build/react-dom.js"></script>

<script>

 

  var Counter = React.createClass({

    name: 'Counter',

    mixins: [React.addons.PureRenderMixin],

    propTypes: {

      count: React.PropTypes.number.isRequired,

    },

    render: function() {

      console.log(this.name + '::render()');

      return React.DOM.span(null, this.props.count);

    }

  });

 

  // ...

</script>

Результат (рис. 2.17) не отличается от предыдущего — когда количество символов не меняется, метод render() компонента Counter не вызывается.

Следует отметить, что примесь PureRenderMixin не является частью ядра React, но входит в расширенную версию дополнений к React. Следовательно, чтобы получить к нему доступ, вместо react/build/react.js следует включить в код react/build/react-with-addons.js. Это предоставит вам новое пространство имен React.addons, где наряду с другими полезными дополнениями можно найти и PureRenderMixin.

02_17.tif 

Рис. 2.17. Упрощенное получение выигрыша в производительности: подмешивание PureRenderMixin

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

var ReactComponentWithPureRenderMixin = {

  shouldComponentUpdate: function(nextProps, nextState) {

    return !shallowEqual(this.props, nextProps) ||

           !shallowEqual(this.state, nextState);

  }

};

Назад: 1. Hello World
Дальше: 3. Excel: необычный табличный компонент

DalExcax
Весьма полезная штука ---- продажа оборудования окон пвх Здесь не может быть ошибки? ---- магазин большой одежды для женщин Я думаю, что Вы ошибаетесь. Пишите мне в PM, пообщаемся. ---- купить землю в сочи под строительство Теперь всё понятно, большое спасибо за информацию. ---- фонарь bailong bl Это просто замечательный ответ ---- отдых на море краснодарский край Присоединяюсь. Всё выше сказанное правда. Можем пообщаться на эту тему. ---- купить дом в сочи Я извиняюсь, но, по-моему, Вы не правы. Пишите мне в PM, пообщаемся. ---- как правильно кидать снюс Конечно. И я с этим столкнулся. Можем пообщаться на эту тему. ---- пицца краснодар меню Офигенно! Спасибо!!! ---- где купить аккаунт фейсбук для рекламы По моему мнению Вы не правы. ---- колумб играть бесплатно без регистрации
AnthonyTap
Это условность, ни больше, ни меньше minecraft who killed noob
stilnyeokna
Высококачественные пластиковые окошки плюс двери ото прямого изготовителя дают возможность заказчику сделать вполне индивидуальный приобретение в надобном наличие, потому наш магазин хотим клиентам сделать самые качественный сервис по производству и доставке окошек к тому же дверных проемов от иркутского изготовителя. На сайте указанного компании изготовление окон Вы можете посмотреть изготавливаемый ассортимент такой уникальной дверей затем сделать оснащение для индивидуальное помещение, коттедж либо большого жилого жилья. Наша компания указана единственным изготовщиком, в которого покупатель напрямую сможете купить пластиковые или алюминиевые окошки или двери, остекление лоджии, фасадные ансамбли у помещение, двойные входные концепции также иные виды застекления для личного помещения. Не включая низких расценок без наценок плюс диллеров компания предоставляем новым заказчикам гибкую систему уценок до сорока пяти процентов, потому по нашем сайте пользователи имеют возможность заказать окна и двери вовсе не только в Иркутске, но и на ближнюю район страны.
ecorfru
Разработка проектов касательно экологической консультации также подобная услуги, сведенная на полевыми-экологическими анализом плюс разработкой плана всяческих направлений –такая обязательная процедура, что исполняется только по закону и предоставляет выполнение норм исходя с охране внешней сферы. Компания Сиблидер выполняет полноценный величину действий именно в теме окружающей среды и предоставляет именно Вам лучшие условия сотрудничества касательно исполнение системного аспекта разработки всех видов документов и аутсорсинга на счет экологии нашими профессионалами. На источнике указанной фирмы организация государственной экологической экспертизы Вы имеют возможность пролистать всяческие подтверждающие удостоверения также разрешение, еще и заказать рекомендации от наших менеджеров либо услуги, какие фирма предоставляет. Наша компания максимально качественно просмотрим ваш заказ затем указываем наилучшие пути резолюции любой случая в небольшой промежуток времени, также еще специалисты совершаем хорошее инжиниринг юрилического лица от самого старта также к завершению договора. Заполняйте номер телефону на страничке и делайте работу в руки специалистами в области экологии каковы клиентам содействуют.
LikefilmsNet
Киносайт – является место, собственно где кинозритель найдет индивидуально такое-же по своей вкусу, определенно не найдете лучшего отвлечения от вашего рутины, чем включение захватывающего сериала обожаемого почерка, тот что зритель выбираете по личное предпочтение. Сайт качественных кинотеатра Зверополис 2 (2022) смотреть мультфильм онлайн бесплатно вмещает огромную число известных кинолент последних выпусков плюс анонсы данного 2021 года, какие постоянные посетители смогут просмотреть полностью в открытом доступе плюс без личного кабинета. Приспособленная навигация по сайту фильмов, мультфильмов либо сериалов легко обозначит кино у хорошем разрешении и звук, также Вы постоянно можете разделить популярное фильм из личными знакомыми с помощью нажатия кнопки социальной сети также написать свой отзыв, есле ж предполагаете разделить первые впечатлениями из другими зрителями. Здесь на презентованном сайте разных фильмов зритель сумеет подобрать фильмы именно по стилю, годам либо рейтингу, заходите и всегда пересматривайте лучшим кинокартиной с большим восторгом на нашему Like Films .
hd1080pred
День кинофильмов сейчас есть целостная порция вашего досуга по вечернее время и в течении дня, на свободных днях и регулярно, в момент изоляции или же небольшим сообщества потому вполне ценно найти сайт высококачественных фильмов, что постоянно рядом с добавленных. На нашем веб-сайте сборника кино Смотреть фильм Гарри Поттер и Тайная комната (2002) онлайн бесплатно и в хорошем качестве, ценители высококачественных показа смогут найти пред вышедшее показ, серийное кино или мультики, или определить видеофильм по жанру. В случае если посетитель всемирный обожатель фильмов, тогда благодаря веб-сайте фильмов есть возможность создать личный аккаунт, чтоб сохранять примечания, обозначить фильм, тот что надо посмотреть. На нашей визитной шапке всегда можно отследить популярные сюжеты, каковы ожидают на больших экранах к тому же увидеть видеоролик, а ожидаемое фильмы мы предоставляем под наших читателей только с хорошем hd разрешении, потому уверенно можете входить на ресурс потом кликнуть «Старт» указанный кино.
KinogoBlue
Зачастую мучаетесь касательно предмету, что стоит запустить интересное на вечерний досуг? На нашем онлайн кинотеатре бесплатного плюс новых кино Киного Я иду искать (2019) смотреть онлайн бесплатно пользователи можете мгновенно обнаружить хороший вариант кинофильма распространенного стиля с подмогой функционала навигации, отбора или же окна ввода. Сайт все это определил за посетителей также разработали просмотр нового кино более проще, именно на начальной стороне Вы сможет обозреть вновь свежую кино, известные многосерийное кино также предельно высокие показы, ну а во время когда надумаете увидеть трейлеры мировых фильмов этого времени, тогда кликайте в шапку «В скором времени в кино» и включайте известные спешные фильмы в прокате. Короткое воссоздание сюжета, сформированный показатель со стороны пользователей и подходящие примечания подсказывают польователю подобрать кино, какое понравится вовсе не лишь посетителю, однако и всем близким. Кликайте и ищите новую фильмы непосредственно сегодня!