如何在 React 中轻松设置兄弟组件的状态?
How do I set state of sibling components easily in React?
我已经开始制作可点击的列表组件,该组件将用于驱动 select 元素。从下面可以看出,ListItem
的 onClick
,我将子元素(在本例中为 ListItem
)的状态传递给父元素(SelectableList
和 CustomSelect
分量)。这工作正常。但是,我还想做的是更改 sibling 组件(其他 ListItems)的状态,以便我可以在其中一个 ListItems 时切换它们的 selected 状态被点击。
目前,我只是简单地使用 document.querySelectorAll('ul.cs-select li)
来抓取元素并在 class 与点击的索引不匹配时将 selected 更改为 selected =12=]。这在一定程度上有效。然而,点击几下之后,组件的状态还没有被 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;
parent 组件应该向 children 传递一个回调,并且每个 child 都会在其状态更改时触发该回调。您实际上可以将所有状态保存在 parent 中,将其用作单个事实点,并将 "selected" 值作为道具传递给每个 child。
在那种情况下,child 可能如下所示:
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
回调。 parent 看起来像这样:
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>
}
});
它在状态中保存一个选择数组,当它处理来自 child 的回调时,它使用 setState
到 re-render children 通过传递它在每个 child.
的 selected
道具中说明
你可以在这里看到一个工作示例:
以下代码帮助我设置两个兄弟姐妹之间的通信。设置是在 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>
);
}
}
兄弟姐妹间交流的另一种策略是使用观察者模式。
The Observer Pattern is a software design pattern in which an object can send messages to multiple other objects.
No sibling or parent-child relationship is required to use this strategy.
Within the context of React, this would mean some components subscribe to receive particular messages and other components publish messages to those subscribers.
Components would typically subscribe in the componentDidMount method and unsubscribe in the componentWillUnmount method.
Here are 4 libraries that implement the Observer Pattern. The differences between them are subtle - EventEmitter is the most popular.
- PubSubJS: "a topic-based publish/subscribe library written in JavaScript."
- EventEmitter: "Evented JavaScript for the browser." It's actually an implementation of a library that already exists as part of nodejs core, but for the browser.
- MicroEvent.js: "event emitter microlibrary - 20lines - for node and browser"
- mobx: "Simple, scalable state management."
摘自:8 no-Flux strategies for React component communication 总的来说也是一本好书。
我已经开始制作可点击的列表组件,该组件将用于驱动 select 元素。从下面可以看出,ListItem
的 onClick
,我将子元素(在本例中为 ListItem
)的状态传递给父元素(SelectableList
和 CustomSelect
分量)。这工作正常。但是,我还想做的是更改 sibling 组件(其他 ListItems)的状态,以便我可以在其中一个 ListItems 时切换它们的 selected 状态被点击。
目前,我只是简单地使用 document.querySelectorAll('ul.cs-select li)
来抓取元素并在 class 与点击的索引不匹配时将 selected 更改为 selected =12=]。这在一定程度上有效。然而,点击几下之后,组件的状态还没有被 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;
parent 组件应该向 children 传递一个回调,并且每个 child 都会在其状态更改时触发该回调。您实际上可以将所有状态保存在 parent 中,将其用作单个事实点,并将 "selected" 值作为道具传递给每个 child。
在那种情况下,child 可能如下所示:
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
回调。 parent 看起来像这样:
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>
}
});
它在状态中保存一个选择数组,当它处理来自 child 的回调时,它使用 setState
到 re-render children 通过传递它在每个 child.
selected
道具中说明
你可以在这里看到一个工作示例:
以下代码帮助我设置两个兄弟姐妹之间的通信。设置是在 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>
);
}
}
兄弟姐妹间交流的另一种策略是使用观察者模式。
The Observer Pattern is a software design pattern in which an object can send messages to multiple other objects.
No sibling or parent-child relationship is required to use this strategy.
Within the context of React, this would mean some components subscribe to receive particular messages and other components publish messages to those subscribers.
Components would typically subscribe in the componentDidMount method and unsubscribe in the componentWillUnmount method.
Here are 4 libraries that implement the Observer Pattern. The differences between them are subtle - EventEmitter is the most popular.
- PubSubJS: "a topic-based publish/subscribe library written in JavaScript."
- EventEmitter: "Evented JavaScript for the browser." It's actually an implementation of a library that already exists as part of nodejs core, but for the browser.
- MicroEvent.js: "event emitter microlibrary - 20lines - for node and browser"
- mobx: "Simple, scalable state management."
摘自:8 no-Flux strategies for React component communication 总的来说也是一本好书。