Как легко установить состояние компонентов sibling в React?
у меня есть начало кликабельного компонента списка, который будет служить для управления элементом select. Как вы можете видеть снизу, onClick
на ListItem
, Я передаю состояние дочернего элемента (ListItem
в этом случае) родителям (SelectableList
и CustomSelect
компонент). Это работает нормально. Однако то, что я также хотел бы сделать, это изменить состояние брат компоненты (другие ListItems), чтобы я мог переключать их выбранные состояния, когда один из ListItems щелкает.
на данный момент я просто с помощью document.querySelectorAll('ul.cs-select li)
чтобы захватить элементы и изменить класс на выбранный, когда он не соответствует индексу нажатого ListItem
. Это работает в какой - то степени. Однако после нескольких кликов состояние компонента не было обновлено React (только на стороне клиента JS), и все начинает ломаться. То, что я хотел бы сделать, это изменить this.state.isSelected
элементов списка братьев и используйте это состояние для обновления компонента SelectableList. Мог кто предложит лучшую альтернативу тому, что я написал ниже?
var React = require('react');
var SelectBox = require('./select-box');
var ListItem = React.createClass({
getInitialState: function() {
return {
isSelected: false
};
},
toggleSelected: function () {
if (this.state.isSelected == true) {
this.setState({
isSelected: false
})
} else {
this.setState({
isSelected: true
})
}
},
handleClick: function(listItem) {
this.toggleSelected();
this.props.onListItemChange(listItem.props.value);
var unboundForEach = Array.prototype.forEach,
forEach = Function.prototype.call.bind(unboundForEach);
forEach(document.querySelectorAll('ul.cs-select li'), function (el) {
// below is trying to
// make sure that when a user clicks on a list
// item in the SelectableList, then all the *other*
// list items get class="selected" removed.
// this works for the first time that you move through the
// list clicking the other items, but then, on the second
// pass through, starts to fail, requiring *two clicks* before the
// list item is selected again.
// maybe there's a better more "reactive" method of doing this?
if (el.dataset.index != listItem.props.index && el.classList.contains('selected') ) {
el.classList.remove('selected');
}
});
},
render: function() {
return (
<li ref={"listSel"+this.props.key}
data-value={this.props.value}
data-index={this.props.index}
className={this.state.isSelected == true ? 'selected' : '' }
onClick={this.handleClick.bind(null, this)}>
{this.props.content}
</li>
);
}
});
var SelectableList = React.createClass({
render: function() {
var listItems = this.props.options.map(function(opt, index) {
return <ListItem key={index} index={index}
value={opt.value} content={opt.label}
onListItemChange={this.props.onListItemChange.bind(null, index)} />;
}, this);
return <ul className="cs-select">{ listItems }</ul>;
}
})
var CustomSelect = React.createClass({
getInitialState: function () {
return {
selectedOption: ''
}
},
handleListItemChange: function(listIndex, listItem) {
this.setState({
selectedOption: listItem.props.value
})
},
render: function () {
var options = [{value:"One", label: "One"},{value:"Two", label: "Two"},{value:"Three", label: "Three"}];
return (
<div className="group">
<div className="cs-select">
<SelectableList options={options}
onListItemChange={this.handleListItemChange} />
<SelectBox className="cs-select"
initialValue={this.state.selectedOption}
fieldName="custom-select" options={options}/>
</div>
</div>
)
}
})
module.exports = CustomSelect;
3 ответов
родительский компонент должен передать обратный вызов дочерним элементам, и каждый дочерний элемент инициирует этот обратный вызов при изменении его состояния. Вы можете фактически удерживать все состояние в родителе, используя его как одну точку истины, и передавать "выбранное" значение вниз каждому ребенку в качестве опоры.
в таком случае ребенок может выглядеть так:
var Child = React.createClass({
onToggle: function() {
this.props.onToggle(this.props.id, !this.props.selected);
},
render: function() {
return <button onClick={this.onToggle}>Toggle {this.props.label} - {this.props.selected ? 'Selected!' : ''}!</button>;
}
});
он не имеет состояния, он просто запускает onToggle
обратный вызов при нажатии. Родитель будет выглядеть как это:
var Parent = React.createClass({
getInitialState: function() {
return {
selections: []
};
},
onChildToggle: function(id, selected) {
var selections = this.state.selections;
selections[id] = selected;
this.setState({
selections: selections
});
},
buildChildren: function(dataItem) {
return <Child
id={dataItem.id}
label={dataItem.label}
selected={this.state.selections[dataItem.id]}
onToggle={this.onChildToggle} />
},
render: function() {
return <div>{this.props.data.map(this.buildChildren)}</div>
}
});
Он содержит массив выборок в состоянии, и когда он обрабатывает обратный вызов от ребенка, он использует setState
для повторного рендеринга детей, передавая свое состояние вниз в selected
опора для каждого ребенка.
вы можете увидеть рабочий пример здесь:
еще одна стратегия для общения братьев и сестер-использовать шаблон наблюдателя.
шаблон Observer-это шаблон проектирования программного обеспечения, в котором объект может отправлять сообщения нескольким другим объектам.
для использования этой стратегии не требуется родственных или родительско-дочерних отношений.
в контексте React это означает, что некоторые компоненты подписываются на получение определенных сообщений, а другие компоненты публикуют сообщения для этих подписчик.
компоненты обычно подписываются в методе componentDidMount и отменяют подписку в методе componentWillUnmount.
здесь 4 библиотеки, которые реализуют шаблон Observer. Различия между ними тонкие-EventEmitter является самым популярным.
- PubSubJS: "тематическая библиотека публикации / подписки, написанная на JavaScript."
- EventEmitter: "Evented JavaScript для браузера."На самом деле это реализация библиотеки, которая уже существует как часть ядра nodejs, но для браузера.
- MicroEvent.js: "микролибрарий эмиттера событий-20 линий-для узла и браузера"
- mobx: "простое масштабируемое управление состоянием."
взято из: 8 стратегий no-Flux для коммуникации компонентов React что также является отличным чтением в генеральный.
следующий код поможет мне настроить связь между двумя братьями. Настройка выполняется в родительском элементе во время вызовов render() и componentDidMount ().
class App extends React.Component<IAppProps, IAppState> {
private _navigationPanel: NavigationPanel;
private _mapPanel: MapPanel;
constructor() {
super();
this.state = {};
}
// `componentDidMount()` is called by ReactJS after `render()`
componentDidMount() {
// Pass _mapPanel to _navigationPanel
// It will allow _navigationPanel to call _mapPanel directly
this._navigationPanel.setMapPanel(this._mapPanel);
}
render() {
return (
<div id="appDiv" style={divStyle}>
// `ref=` helps to get reference to a child during rendering
<NavigationPanel ref={(child) => { this._navigationPanel = child; }} />
<MapPanel ref={(child) => { this._mapPanel = child; }} />
</div>
);
}
}