Редактирование богатой структуры данных в React.js
Я пытаюсь создать простой редактор на основе сетки для структуры данных, и у меня возникает пара концептуальных проблем с React.js. Их документация не очень помогает в этом, поэтому я надеюсь, что кто-то здесь может помочь.
Во-первых, каков правильный способ передачи состояния из внешнего компонента во внутренний компонент? Возможно ли иметь изменения состояния во внутреннем компоненте "пузырь" вверх по внешнему компоненту (компонентам)?
Во-вторых, могут ли два отдельных компонента обмениваться данными, так что мутация в одном видима в другом?
Ниже приведен упрощенный пример того, что я хочу сделать (версия JSFiddle):
У меня есть объект company
, содержащий массив объектов employee
. Я хочу выложить список employee
в редактируемой сетке. Когда я нажимаю кнопку, я хочу увидеть результирующий объект company
, а также любые мутации (запись на консоль).
/** @jsx React.DOM */
var Cell = React.createClass({
getInitialState: function () {
return {data: ""};
},
componentWillMount: function () {
this.setState({data: this.props.data});
},
onChange: function (evt) {
console.log(this.state, evt.target.value);
this.setState({data: evt.target.value});
},
render: function () {
var data = this.props.data;
return <input value={this.state.data} onChange={this.onChange} />
}
});
var Row = React.createClass({
render: function () {
return (<div className="row">
<Cell data={this.props.data.name} />
<Cell data={this.props.data.location} />
<Cell data={this.props.data.phone} />
</div>);
}
});
var Grid = React.createClass({
render: function () {
var rows = this.props.data.map(function (rowData, index) {
return <Row key={index} data={rowData}>Row</Row>;
});
return <div className="table">{rows}</div>;
}
});
var Button = React.createClass({
getInitialState: function () {
return {data: {}}
},
componentWillMount: function () {
this.setState({data: this.props.data});
},
onClick: function () {
console.log(this.state);
},
render: function () {
return <button onClick={this.onClick}>Update</button>;
}
});
var company = {
name: "Innotech",
employees: [
{id: "1", name: "Peter", location: "IT", phone: "555-1212"},
{id: "2", name: "Samir", location: "IT", phone: "555-1213"},
{id: "3", name: "Milton", location: "loading dock", phone: "none"}
]
};
React.renderComponent(
<div><Grid data={company.employees} /><Button data={company} /></div>,
document.getElementById('employees')
);
Ответы
Ответ 1
Я думаю, что это самая недокументированная часть React прямо сейчас. Предлагаемый способ взаимодействия между компонентами - просто установить реквизиты при общении от родителя к ребенку и передавать обратные вызовы через реквизиты при общении от потомка к родителю.
Когда вы чувствуете, что хотите обмениваться данными между одноуровневыми компонентами, это означает, что должен быть родительский компонент, управляющий состоянием и передающий его обоим компонентам. В большинстве случаев ваше состояние должно находиться в верхней части иерархии компонентов, а каждая часть информации должна находиться (не более) в одном состоянии компонента, не более.
Подробнее об этом читайте в блоге Пита Ханта Thinking in React.
Имея это в виду, я обновил вашу скрипку.
Я изменил Grid
так, чтобы он не поддерживал свое собственное состояние, а вместо этого всегда отображал данные, переданные через его реквизиты, и вызывает onCellChange
из своих реквизитов, когда он хочет запросить изменение данных у своего родителя. (Компонент Grid
будет ожидать, что его родитель обновит свойство сетки data
с измененными данными. Если родитель отказывается (возможно, из-за неудачной проверки данных или подобного), вы в конечном итоге получаете сетку только для чтения.)
Вы также заметите, что я создал новый компонент Editor
, чтобы обернуть сетку и кнопку ее родного брата. Компонент Editor
теперь по существу управляет всей страницей. В реальном приложении вполне вероятно, что содержимое сетки понадобится где-то еще, и поэтому состояние будет перемещено выше. Я удалил ваш компонент Button
, потому что он мало чем отличался от встроенного тега <button>
; Я оставил Cell
, но его тоже можно было удалить - Row
мог легко использовать теги <input>
напрямую.
Надеюсь, это имеет смысл. Не стесняйтесь спрашивать, если что-то неясно. В IRC-комнате #reactjs обычно также есть люди, если вы хотите больше поговорить об этом.
Ответ 2
Я изучаю ReactJS на прошлой неделе или около того. Мой вклад в ваш вопрос задает новый вопрос: почему вы отделяете компонент Cell от компонентов Row и Grid?
Исходя из фона Backbone, Cell, Row и Grid имеют смысл, иметь гранулированный контроль над отдельными Backbone.Views. Тем не менее, похоже, что подробный контроль и обновление DOM - это то, что ReactJS пытается решить для вас, что для меня говорит о наличии компонента Grid, который реализует Row/Cell внутри себя:
var Grid = React.createClass({
onChange: function(evt, field) {
this.props.data[field] = evt.target.value;
},
render: function () {
var rows = this.state.data.map(function (rowData, index) {
return (
<div className="row" key={index}>
<input value={rowData.name} onChange={this.onChange.bind(null, "name")} />
<input value={rowData.location} onChange={this.onChange.bind(null, "location")} />
<input value={rowData.phone} onChange={this.onChange.bind(null, "phone")} />
</div>
);
});
return <div className="table">
{rows}
</div>;
}
});
(Игнорировать включенную * Изменить обработку, место для улучшения там. Неподтвержденный код)
Вопрос: неужели вы когда-либо повторно использовали Cell или Row в качестве отдельных компонентов в другом месте? Для меня ответ "очень вероятно нет". В этом случае мое решение выше имеет смысл, ИМХО, и избавляется от проблемы передачи данных и изменений вверх и вниз.
Ответ 3
Другой способ обмена данными между компонентами-братьями, когда родительский компонент не имеет смысла, - это использовать события между компонентами. Например, вы можете использовать Backbone.Events, Node.js Emitter, портированный в браузер или любую другую lib. Вы даже можете использовать Bacon.js, если вы предпочитаете реактивные потоки. Здесь есть большой и простой пример объединения Bacon.js и React.js здесь: http://joshbassett.info/2014/reactive-uis-with-react-and-bacon/