До сих пор в книге рассматривались способы определения пользовательских интерфейсов в функциях render() с использованием вызовов методов React.createElement() и семейства методов React.DOM.* (например, метода React.DOM.span()). Среди неудобств, создаваемых множеством вызовов функций, можно отметить трудность отслеживания расстановки всех необходимых закрывающих круглых и фигурных скобок. Но есть более простой способ определения пользовательских интерфейсов — использование JSX.
JSX представляет собой отдельную от React технологию, и ее применение не носит обязательного характера. Как вы видели, в первых трех главах она так ни разу и не использовалась. Вы можете вообще отказаться от JSX. Но все же высока вероятность того, что, один раз попробовав, вы уже не захотите возвращаться к вызовам функций.
Не совсем понятно, что именно означает акроним JSX, но наиболее вероятно это сокращение от термина JavaScriptXML или JavaScript Syntax eXtension. Официальная страница проекта находится по адресу /.
Еще раз посмотрим на окончательный вариант примера Hello World из главы 1:
<script src="react/build/react.js"></script>
<script src="react/build/react-dom.js"></script>
<script>
ReactDOM.render(
React.DOM.h1(
{id: "my-heading"},
React.DOM.span(null,
React.DOM.em(null, "Hell"),
"o"
),
" world!"
),
document.getElementById('app')
);
</script>
В функции render() имеется довольно много вызовов других функций. Использование JSX существенно упрощает код:
ReactDOM.render(
<h1 id="my-heading">
<span><em>Hell</em>o</span> world!
</h1>,
document.getElementById('app')
);
Этот синтаксис очень похож на уже известный вам HTML. Есть только одна загвоздка: поскольку этот код не является допустимым синтаксисом JavaScript, его нельзя запустить в браузере в неизменном виде. Это код следует преобразовать (транспилировать) в чистый JavaScript, который может быть запущен браузером.
Процесс транспиляции заключается в том, что берется исходный код и переписывается с целью получения таких же результатов, но уже с использованием синтаксиса, понимаемого устаревшими браузерами. Этот процесс отличается от использования полифиллов.
Примером полифилла может служить добавление к Array.prototype метода, аналогичного методу map(), который был введен стандартом ECMAScript5, чтобы заставить его работать в браузерах, поддерживающих стандарт ECMAScript3:
if (!Array.prototype.map) {
Array.prototype.map = function() {
// реализация метода
};
}
// использование
typeof [].map === 'function';
// true, теперь 'map()' можно использовать
Полифилл представляет собой решение из области чистого JavaScript. Он хорошо справляется с поставленной задачей при добавлении новых методов к существующим объектам или при реализации новых объектов (например, JSON). Но когда в язык вводится новый синтаксис, этого недостаточно. Такой синтаксис, например вводящий в работу ключевое слово class, недопустим и вызывает в браузерах без поддержки class ошибку анализа кода, а создать для него полифилл не представляется возможным. Поэтому для нового синтаксиса требуется проведение компиляции (транспиляции), чтобы преобразовать его до того, как он будет представлен для обработки браузером.
Транспиляция JavaScript проводится довольно часто, поскольку программистам хочется использовать функциональные возможности, объявленные в стандарте ECMAScript6 (известном также как ECMAScript2015) и в последующих стандартах, не дожидаясь выхода поддерживающих эти возможности браузеров. Если у вас уже есть отработанный процесс сборки (который выполняет, к примеру, минификацию или ECMAScript6-to-5-транспиляцию), можно просто добавить к нему этап JSX-преобразования. Но предположим, что у вас еще нет процесса сборки, и пройдем все этапы настройки упрощенного процесса на стороне клиента.
Babel (ранее известный как 6to5) является транспилятором с открытым кодом, поддерживающим самые последние функциональные возможности JavaScript, а также включающим поддержку JSX. Он необходим как предварительное условие для использования JSX. В следующей главе будет показано, как настроить процесс сборки, позволяющий вам поставлять React-приложения реальным пользователям. Но в целях изучения JSX не будем ничего усложнять и проведем транспиляцию на стороне клиента.
Необходимое предупреждение: преобразование на стороне клиента предназначено исключительно для создания прототипов, обучения и проведения исследований. Из соображений производительности такое преобразование в приложениях, предназначенных для обычного применения, использоваться не должно.
Для внутрибраузерных преобразований (на стороне клиента) нужен файл browser.js. В Babel, начиная с версии 6, он больше не предоставляется, но вы всегда можете взять последнюю работоспособную копию этого файла:
$ mkdir ~/reactbook/babel
$ cd ~/reactbook/babel
$ curl -
core/5.8.34/browser.js >
browser.js
До выхода версии v0.14 React включал выполняемый на стороне клиента сценарий JSXTransformer. Кроме этого, в предыдущих версиях NPM-пакет react-tools устанавливал утилиту командной строки jsx. Теперь они считаются устаревшими и приоритет отдается транспилятору Babel.
Чтобы JSX заработал, на вашей странице нужно сделать две вещи:
• включить browser.js, сценарий, позволяющий транспилировать JSX;
• пометить теги script, использующие JSX, чтобы Babel знал, что для него в этих тегах есть работа.
До сих пор все примеры в этой книге включали библиотеку React, используя для этого следующий код:
<script src="react/build/react.js"></script>
<script src="react/build/react-dom.js"></script>
Теперь нужно включить еще и преобразователь:
<script src="react/build/react.js"></script>
<script src="react/build/react-dom.js"></script>
<script src="babel/browser.js"></script>
Второй шаг — добавление в теги <script> атрибута type со значением text/babel (которое не поддерживается браузерами) — там, где их содержимое требует преобразования.
До:
<script>
ReactDOM.render(/*...*/);
</script>
После:
<script type="text/babel">
ReactDOM.render(/*...*/);
</script>
При загрузке страницы запускается код browser.js, который находит все сценарии text/babel и преобразует их содержимое в код, который может использоваться браузером. На рис. 4.1 показано, что произойдет в браузере Chrome при попытке запуска сценария с синтаксисом JSX в его исходном виде. Как и ожидалось, будет получена ошибка синтаксиса.
Рис. 4.1. Браузеры не понимают JSX-синтаксис
На рис. 4.2 можно увидеть, что после того, как сценарий browser.js провел транспиляцию блоков сценариев с атрибутом type="text/babel", страница заработала успешно.
Рис. 4.2. Браузерный сценарий Babel и тип содержимого text/babel
Для проведения экспериментов и ознакомления с преобразованиями JSX вы можете воспользоваться интерактивным редактором, находящимся по адресу / (рис. 4.3).
Рис. 4.3. Интерактивное средство преобразования JSX
На рис. 4.4 можно увидеть, что преобразование JSX осуществляется легко и просто: исходный JSX-код Hello World становится серией вызовов React.createElement() с использованием функционального синтаксиса, с которым вы уже знакомы. Это обычный код JavaScript, поэтому он легко читается и понимается.
Рис. 4.4. Преобразованный пример Hello World
Есть еще одно интерактивное инструментальное средство, которое может пригодиться при изучении JSX или при переводе существующей разметки приложения из HTML, — преобразователь HTML-to-JSX (рис. 4.5).
Рис. 4.5. Инструментальное средство HTML-to-JSX
Зачастую при создании пользовательского интерфейса нужно применять переменные, условные выражения и циклы. Вместо того чтобы выдумывать еще один шаблонный синтаксис, можно воспользоваться тем, что JSX позволяет работать с кодом JavaScript внутри разметки. Вам нужно только заключить свой код JavaScript в фигурные скобки.
Рассмотрим пример компонента Excel из предыдущей главы. При замене функционального синтаксиса кодом JSX в конечном итоге будет получено примерно следующее:
var Excel = React.createClass({
/* фрагмент... */
render: function() {
var state = this.state;
return (
<table>
<thead onClick={this._sort}>
<tr>
{this.props.headers.map(function(title, idx) {
if (state.sortby === idx) {
title += state.descending ? ' \u2191' : ' \u2193';
}
return <th key={idx}>{title}</th>;
})}
</tr>
</thead>
<tbody>
{state.data.map(function(row, idx) {
return (
<tr key={idx}>
{row.map(function(cell, idx) {
return <td key={idx}>{cell}</td>;
})}
</tr>
);
})}
</tbody>
</table>
);
}
});
Как видите, чтобы воспользоваться переменными, их заключают в фигурные скобки:
<th key={idx}>{title}</th>
Для циклов можно заключить в фигурные скобки в том числе map():
<tr key={idx}>
{row.map(function(cell, idx) {
return <td key={idx}>{cell}</td>;
})}
</tr>
Код JavaScript в JSX может быть вложен на любую необходимую глубину. Код JSX можно воспринимать как JavaScript (после небольшого преобразования), но со знакомым синтаксисом HTML. С написанием кода JSX могут справиться даже те члены вашей команды, которые разбираются в JavaScript хуже вас, но при этом неплохо знают HTML. А для построения пользовательского интерфейса с живыми данными они могут изучить JavaScript в минимально необходимом объеме, всего лишь на уровне использования переменных и циклов.
В только что приведенном примере с компонентом Excel в функции обратного вызова map() есть условное выражение if. Хотя это условие и является вложенным, после небольшого дополнительного форматирования его можно превратить в легкочитаемый единый оператор, использующий три операнда:
return (
<th key={idx}>{
state.sortby === idx
? state.descending
? title + ' \u2191'
: title + ' \u2193'
: title
}</th>
);
Заметили повторяющуюся переменную title в последнем примере? От этого повторения можно избавиться:
return (
<th key={idx}>{title}{
state.sortby === idx
? state.descending
? ' \u2191'
: ' \u2193'
: null
}</th>
);
Однако в таком случае придется модифицировать имеющуюся в примере функцию сортировки. В этой функции предусматривается, что пользователь будет щелкать на элементе <th>, а для определения, на каком именно элементе <th> был произведен щелчок, будет использоваться метод cellIndex. Но когда в коде JSX имеются смежные блоки {}, для их обособления применяются теги <span>. Иными словами, фрагмент кода <th>{1}{2}</th> превращается в DOM-модели в код <th><span>1</span><span>2</span></th>.
Пробельные символы воспринимаются в коде JSX почти так же, как и в коде HTML, но с небольшими отличиями:
<h1>
{1} plus {2} is {3}
</h1>
превращается в:
<h1>
<span>1</span><span> plus </span><span>2</span><span>
is </span><span>3</span>
</h1>
что отображается как 1 plus 2 is 3 — в точном соответствии с вашими ожиданиями по части HTML: несколько пробелов становятся одним пробелом.
Но в этом примере:
<h1>
{1}
plus
{2}
is
{3}
</h1>
вы в конечном итоге получаете следующий код:
<h1>
<span>1</span><span>plus</span><span>2</span><span>is
</span><span>3</span>
</h1>
Как видите, все пробельные символы вырезаны, поэтому конечный результат будет 1plus2is3. Там, где нужно, пробел можно всегда добавить с помощью конструкции {' '} (которая затем плодит еще больше тегов <span>) или же превратить строки символов в выражения и добавить пробел уже там. Иными словами, будут работать оба следующих варианта:
<h1>
{/* пробельные выражения */}
{1}
{' '}plus{' '}
{2}
{' '}is{' '}
{3}
</h1>
<h1>
{/* пробел, прикрепленный к строковым выражениям */}
{1}
{' plus '}
{2}
{' is '}
{3}
</h1>
В предыдущих примерах за счет добавления комментариев к разметке JSX можно увидеть, как в код внедряется новая концепция.
Поскольку выражение, заключенное в фигурные скобки {}, является всего лишь кодом JavaScript, можно легко добавить многострочный комментарий, воспользовавшись следующей конструкцией: /* комментарий */.
Можно также добавить комментарий в строку, воспользовавшись синтаксисом // комментарий, но при этом не стоит забывать про закрывающую фигурную скобку (}) выражения, которая должна быть на другой строке, чтобы не выглядеть как часть комментария:
<h1>
{/* многострочный комментарий */}
{/*
multi
line
comment
*/}
{
// однострочный комментарий
}
Hello
</h1>
Поскольку код {// комментарий} не работает (} теперь закомментирована), от использования однострочных комментариев мало проку, и все комментарии можно привести к единому виду, выбрав во всех случаях формат многострочного комментария.
В JSX допускается использование элементов HTML:
<h2>
More info »
</h2>
Этот пример, как показано на рис. 4.6, выдает правую угловую кавычку.
Рис. 4.6. HTML-элемент в JSX
Но, если воспользоваться элементом в качестве части выражения, вы окажетесь в ситуации, когда элемент будет закодирован дважды. В этом примере:
<h2>
{"More info »"}
</h2>
HTML-элемент будет закодирован — и вы увидите результат, показанный на рис. 4.7.
Рис. 4.7. Дважды закодированный элемент HTML
Чтобы избавиться от двойного кодирования, можно воспользоваться Юникод-версией элемента HTML, в данном случае \u00bb (см. ):
<h2>
{"More info \u00bb"}
</h2>
Для удобства можно где-нибудь в верхней части модуля определить константу вместе с любым общим пробелом. Например:
const RAQUO = ' \u00bb';
Затем этой константой можно воспользоваться в любом нужном месте:
<h2>
{"More info" + RAQUO}
</h2>
<h2>
{"More info"}{RAQUO}
</h2>
Обратили внимание на использование const вместо var? Добро пожаловать в прекрасный новый мир Babel, где можно побаловать себя всеми новшествами, предлагаемыми современным JavaScript. Подробности рассматриваются в главе 5.
Можно задаться вопросом: а зачем идти по такому сложному пути, как использование элементов HTML? Потому что здравый смысл подсказывает: они помогают бороться с XSS-атаками.
React нейтрализует все строки, противодействуя атакам класса XSS. Поэтому, когда у пользователей запрашивается какой-нибудь ввод, а они предоставляют вредоносную строку, React защищает вас от атаки. Возьмем, к примеру, следующий пользовательский ввод:
var firstname = 'John<scr'+'ipt
src=">';
При определенных обстоятельствах вы можете вписать его в DOM-модель. Например:
document.write(firstname);
Это будет иметь катастрофические последствия, поскольку на странице появится John, а тег <script> приведет к загрузке вредоносного кода JavaScript и откроет несанкционированный доступ к вашему приложению и конфиденциальной информации доверившихся вам пользователей.
React защищает от подобных случаев, и от вас не требуется никаких усилий. Если создать следующий код:
React.render(
<h2>
Hello {firstname}!
</h2>,
document.getElementById('app')
);
то React нейтрализует содержимое переменной firstname (рис. 4.8).
Рис. 4.8. Нейтрализация строк
В JSX у ECMAScript6 было позаимствовано весьма полезное свойство под названием оператор распространения, которое было внедрено в качестве удобного усовершенствования при работе с определением свойств.
Представьте, что у вас есть коллекция атрибутов, которую нужно передать компоненту <a>:
var attr = {
href: '',
target: '_blank',
};
Вы можете сделать это в любой момент с помощью следующего кода:
return (
<a
href={attr.href}
target={attr.target}>
Hello
</a>
);
Но, похоже, возникает проблема использования часто повторяющегося шаблонного кода. С помощью распространяемых атрибутов эту задачу можно выполнить всего лишь в одной строке кода:
return <a {...attr}>Hello</a>;
В примере у вас есть объект атрибутов, который вы хотите определить (возможно, при некоторых условиях) заранее. Это полезно уже само по себе, но чаще всего применяется вариант, при котором данный объект будет получен извне, зачастую из родительского компонента. Именно такой случай и рассмотрим.
Атрибуты, распространяемые от родительского к дочернему компоненту. Представьте, что вы создаете компонент FancyLink, закулисно использующий обычный компонент <a>. Желательно, чтобы ваш компонент принял все те же атрибуты, что и <a> (href, style, target и т.д.), плюс дополнительный атрибут (скажем, size). Итак, кто-нибудь может воспользоваться вашим компонентом следующим образом:
<FancyLink
href=""
style={ {color: "red"} }
target="_blank"
size="medium">
Hello
</FancyLink>
Как ваша функция render() сможет воспользоваться распространяемыми атрибутами и избежать повторного определения всех свойств компонента <a>?
var FancyLink = React.createClass({
render: function() {
switch(this.props.size) {
// совершение каких-либо действий на основе свойства 'size'
}
return <a {...this.props}>{this.props.children}</a>;
}
});
Вы заметили использование метода this.props.children? Это простой и удобный метод, позволяющий переправлять вашему компоненту любое количество дочерних компонентов и обращаться к ним при составлении интерфейса.
В предыдущем фрагменте кода вы выполняете все нужные действия на основе значения свойства size, а затем переносите все свойства в <a> (включая и свойство size). Компонент React.DOM.a не имеет ни малейшего понятия о свойстве size, поэтому он его молча игнорирует, а все остальные свойства использует.
Можно немного улучшить код и не переправлять ненужные свойства:
var FancyLink = React.createClass({
render: function() {
switch(this.props.size) {
// совершение каких-либо действий на основе свойства 'size'
}
var attribs = Object.assign({}, this.props); // поверхностное
// клонирование
delete attribs.size;
return <a {...attribs}>{this.props.children}</a>;
}
});
Если воспользоваться синтаксисом, предлагаемым ECMAScript7 (бесплатно поставляемым вам инструментальным средством Babel!), решение задачи упрощается еще больше и обходится без всякого клонирования:
var FancyLink = React.createClass({
render: function() {
var {size, ...attribs} = this.props;
switch (size) {
// совершение каких-либо действий
// на основе свойства 'size'
}
return <a {...attribs}>{this.props.children}</a>;
}
});
Из вашей функции render() всегда нужно возвращать один узел. Возвращать два узла не разрешается. Иными словами, следующий код считается ошибочным:
// Синтаксическая ошибка, связанная с необходимостью
// заключения смежных элементов JSX в охватывающий тег
var Example = React.createClass({
render: function() {
return (
<span>
Hello
</span>
<span>
World
</span>
);
}
});
Исправить ошибку нетрудно, нужно просто заключить все узлы в другой компонент, например в <div>:
var Example = React.createClass({
render: function() {
return (
<div>
<span>
Hello
</span>
<span>
World
</span>
</div>
);
}
});
Хотя возвратить из вашей функции render() массив узлов невозможно, массивы можно использовать при построении композиции — при условии, что у узлов в массиве имеются надлежащие ключевые атрибуты key:
var Example = React.createClass({
render: function() {
var greeting = [
<span key="greet">Hello</span>,
' ',
<span key="world">World</span>,
'!'
];
return (
<div>
{greeting}
</div>
);
}
});
Обратите внимание на то, как в массив вставляются пробельные символы и другие строки, а также на то, что им не нужен ключ.
Это похоже на принятие любого количества дочерних компонентов, переданных от родительского компонента, и на их распространение в вашей функции render():
var Example = React.createClass({
render: function() {
console.log(this.props.children.length); // 4
return (
<div>
{this.props.children}
</div>
);
}
});
React.render(
<Example>
<span key="greet">Hello</span>
{' '}
<span key="world">World</span>
!
</Example>,
document.getElementById('app')
);
Код JSX выглядит знакомо, поскольку похож на код HTML, но имеет преимущество — простой способ добавления динамических значений, циклов и условных выражений (нужно лишь заключить их в фигурные скобки {}). Для начала работы с JSX всегда можно воспользоваться инструментальным средством HTML-to-JSX, но чем раньше вы станете набирать свой собственный код JSX, тем лучше. Рассмотрим некоторые (возможно, несколько необычные для вас на начальном этапе обучения) различия между HTML и JSX. Кое-какие различия уже рассматривались в главе 1, но бегло повторим еще раз.
Вместо атрибутов class и for (оба они являются в ECMAScript зарезервированными словами) следует использовать названия className и htmlFor:
// Недопустимо!
var em = <em class="important" />;
var label = <label for="thatInput" />;
// Допустимо
var em = <em className="important" />;
var label = <label htmlFor="thatInput" />;
Атрибут style получает в качестве значения объект, а не строку с разделителями в виде точки с запятой. Имена свойств CSS набираются в «верблюжьем» регистре и без дефисов:
// Недопустимо!
var em = <em style="font-size: 2em; line-height: 1.6" />;
// Допустимо
var styles = {
fontSize: '2em',
lineHeight: '1.6'
};
var em = <em style={styles} />;
// Также допустима встроенная форма записи
// обратите внимание на двойные фигурные скобки { {} } –
// один комплект для динамического значения в JSX,
// а второй – для JS-объекта
var em = <em style={ {fontSize: '2em', lineHeight: '1.6'} } />;
В HTML некоторые теги не нуждаются в закрытии, а в JSX (XML) их закрывать обязательно:
// Недопустимо:
// незакрытых тегов быть не должно,
// даже если в HTML это вполне допустимо
var gimmeabreak = <br>;
var list = <ul><li>item</ul>;
var meta = <meta charset="utf-8">;
// Допустимо
var gimmeabreak = <br />;
var list = <ul><li>item</li></ul>;
var meta = <meta charSet="utf-8" />;
// или
var meta = <meta charSet="utf-8"></meta>;
Вы обратили внимание, что в предыдущем фрагменте кода charset использована форма charSet? Все атрибуты в JSX должны быть указаны в «верблюжьем» регистре (camelCase). Поначалу это часто путает все карты — вы можете набрать onclick и заметить, что ничего не происходит, до тех пор пока не вернетесь к этому месту кода и не измените прежний код на onClick:
// Недопустимо!
var a = <a onclick="reticulateSplines()" />;
// Допустимо
var a = <a onClick={reticulateSplines} />;
Исключение из этого правила составляют все атрибуты с префиксами data- и aria-, они используются в таком же виде, как и в HTML.
Имеются различия между JSX и HTML и при работе с формами. Посмотрим какие.
При использовании элементов формы пользователи вносят изменения в их значения. В React вы можете подписаться на такие изменения с помощью атрибутов onChange. Это куда логичнее использования значения checked для переключателей и флажков, а также selected — в элементах выбора <select>. При наборе в текстовых областях и полях <input type="text"> onChange активизируется по мере набора текста пользователем, что намного полезнее, чем активация при утрате элементом фокуса. Это означает, что подписываться на события мыши и клавиатуры для отслеживания набора текста при вводе изменений уже не нужно.
Если в HTML имеется тег <input id="i" value="hello" />, а затем значение атрибута value путем набора текста изменяется на "bye", то получается, что…
i.value; // "bye"
i.getAttribute('value'); // "hello"
В React содержимое свойства value всегда имеет последнее значение вводимого текста. Если нужно указать значение по умолчанию, можно воспользоваться свойством defaultValue.
В следующем фрагменте кода имеются компонент <input> с предварительно заполненным содержимым "hello" и обработчик события onChange. Удаление последнего «o» в "hello" приводит к тому, что у value появляется значение "hell", а значением свойства defaultValue остается "hello":
function log(event) {
console.log("value: ", event.target.value);
console.log("defaultValue: ", event.target.defaultValue);
}
React.render(
<input defaultValue="hello" onChange={log} />,
document.getElementById('app')
);
Такой же схемы нужно придерживаться и в собственных компонентах: если в них допускается применение свойства, имя которого намекает на поддержку актуальности (например, value, data), то сохраняйте актуальность его значения. Если этого не нужно, назовите свойство initialData (как делали в главе 3) или как-нибудь вроде defaultValue, чтобы было ясно, чего от него ожидать.
Для соответствия полям текстового ввода в имеющейся в React версии <textarea> применяются свойства value и defaultValue. В value поддерживается актуальность, а в defaultValue сохраняется исходное значение. Если следовать HTML-стилю и воспользоваться для определения значения дочерним элементом textarea (что не рекомендуется делать), он будет рассматриваться как имеющий такое же значение, что и свойство defaultValue.
В HTML поле <textarea> (как определено консорциумом W3C) применяет в качестве своего значения дочерний элемент, чтобы разработчики могли использовать во вводе новые строки. Но React, построенная целиком на основе JavaScript, не страдает от этого ограничения. Когда нужна новая строка, просто используется комбинация символов \n.
Рассмотрим следующие примеры и результаты их выполнения, показанные на рис. 4.9:
function log(event) {
console.log(event.target.value);
console.log(event.target.defaultValue);
}
React.render(
<textarea defaultValue="hello\nworld" onChange={log} />,
document.getElementById('app1')
);
React.render(
<textarea defaultValue={"hello\nworld"} onChange={log} />,
document.getElementById('app2')
);
React.render(
<textarea onChange={log}>hello
world
</textarea>,
document.getElementById('app3')
);
React.render(
<textarea onChange={log}>{"hello\n\
world"}
</textarea>,
document.getElementById('app4')
);
Рис. 4.9. Новые строки в текстовых областях
Обратите внимание на разницу между использованием в качестве значения свойства строки символов "hello\nworld" и использованием строки JavaScript {"hello\nworld"}.
Также возьмите на заметку, что многострочное строковое значение в JavaScript нуждается в нейтрализации с помощью обратного слеша, \ (четвертый пример).
И наконец, посмотрите, как React выдает предупреждение об использовании для установки значения традиционного дочернего элемента, принадлежащего <textarea>.
Когда в HTML используется поле ввода <select>, предварительно выбранные записи указываются с помощью <option selected>:
<!-- традиционный HTML-код -->
<select>
<option value="stay">Should I stay</option>
<option value="move" selected>or should I go</option>
</select>
В React для компонента <select> указывается value или же (что еще лучше) defaultValue:
// React/JSX
<select defaultValue="move">
<option value="stay">Should I stay</option>
<option value="move">or should I go</option>
</select>
То же самое действие применяется при наличии возможности выбора сразу нескольких элементов — с той лишь разницей, что предоставляется массив предварительно выбранных значений:
<select defaultValue={["stay", "move"]} multiple={true}>
<option value="stay">Should I stay</option>
<option value="move">or should I go</option>
<option value="trouble">If I stay it will be trouble</option>
</select>
Если вы перепутаете код с HTML и установите для <option> атрибут selected, то React выдаст предупреждение.
Разрешается вместо записи <select defaultValue> использовать <select value>, хотя делать это не рекомендуется, поскольку вам придется позаботиться об обновлении значения, которое видит пользователь. В противном случае, когда пользователь выбирает другой вариант, компонент <select> сохраняет прежний вид. Иными словами, вам нужен код, похожий на следующий:
var MySelect = React.createClass({
getInitialState: function() {
return {value: 'move'};
},
_onChange: function(event) {
this.setState({value: event.target.value});
},
render: function() {
return (
<select value={this.state.value}
onChange={this._onChange}>
<option value="stay">Should I stay</option>
<option value="move">or should I go</option>
<option value="trouble">If I stay it will be
trouble</option>
</select>
);
}
});
В завершение воспользуемся JSX и перепишем все методы render*() в финальной версии компонента Excel из предыдущей главы. Я предлагаю вам выполнить это упражнение самостоятельно (вы всегда сможете сравнить свое решение с примером из хранилища кода, сопровождающего эту книгу).