我刚开始使用ReactJS,我有一个问题。
我的应用程序本质上是一个带有过滤器和更改布局的按钮的列表。 目前我正在使用三个组件:<list />, < Filters />和<TopBar />,现在显然,当我在< Filters />中更改设置时,我想在<list />中触发一些方法来更新我的视图。
我如何使这3个组件相互交互,或者我是否需要某种全局数据模型,我只需要对其进行更改?
我刚开始使用ReactJS,我有一个问题。
我的应用程序本质上是一个带有过滤器和更改布局的按钮的列表。 目前我正在使用三个组件:<list />, < Filters />和<TopBar />,现在显然,当我在< Filters />中更改设置时,我想在<list />中触发一些方法来更新我的视图。
我如何使这3个组件相互交互,或者我是否需要某种全局数据模型,我只需要对其进行更改?
当前回答
扩展@MichaelLaCroix的回答,当组件不能在任何类型的父子关系之间进行通信时,文档建议设置一个全局事件系统。
在<Filters />和<TopBar />没有任何上述关系的情况下,一个简单的全局发射器可以像这样使用:
componentDidMount -订阅事件
componentWillUnmount -从事件中取消订阅
js和EventSystem代码
EventSystem.js
class EventSystem{
constructor() {
this.queue = {};
this.maxNamespaceSize = 50;
}
publish(/** namespace **/ /** arguments **/) {
if(arguments.length < 1) {
throw "Invalid namespace to publish";
}
var namespace = arguments[0];
var queue = this.queue[namespace];
if (typeof queue === 'undefined' || queue.length < 1) {
console.log('did not find queue for %s', namespace);
return false;
}
var valueArgs = Array.prototype.slice.call(arguments);
valueArgs.shift(); // remove namespace value from value args
queue.forEach(function(callback) {
callback.apply(null, valueArgs);
});
return true;
}
subscribe(/** namespace **/ /** callback **/) {
const namespace = arguments[0];
if(!namespace) throw "Invalid namespace";
const callback = arguments[arguments.length - 1];
if(typeof callback !== 'function') throw "Invalid callback method";
if (typeof this.queue[namespace] === 'undefined') {
this.queue[namespace] = [];
}
const queue = this.queue[namespace];
if(queue.length === this.maxNamespaceSize) {
console.warn('Shifting first element in queue: `%s` since it reached max namespace queue count : %d', namespace, this.maxNamespaceSize);
queue.shift();
}
// Check if this callback already exists for this namespace
for(var i = 0; i < queue.length; i++) {
if(queue[i] === callback) {
throw ("The exact same callback exists on this namespace: " + namespace);
}
}
this.queue[namespace].push(callback);
return [namespace, callback];
}
unsubscribe(/** array or topic, method **/) {
let namespace;
let callback;
if(arguments.length === 1) {
let arg = arguments[0];
if(!arg || !Array.isArray(arg)) throw "Unsubscribe argument must be an array";
namespace = arg[0];
callback = arg[1];
}
else if(arguments.length === 2) {
namespace = arguments[0];
callback = arguments[1];
}
if(!namespace || typeof callback !== 'function') throw "Namespace must exist or callback must be a function";
const queue = this.queue[namespace];
if(queue) {
for(var i = 0; i < queue.length; i++) {
if(queue[i] === callback) {
queue.splice(i, 1); // only unique callbacks can be pushed to same namespace queue
return;
}
}
}
}
setNamespaceSize(size) {
if(!this.isNumber(size)) throw "Queue size must be a number";
this.maxNamespaceSize = size;
return true;
}
isNumber(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
}
NotificationComponent.js
class NotificationComponent extends React.Component {
getInitialState() {
return {
// optional. see alternative below
subscriber: null
};
}
errorHandler() {
const topic = arguments[0];
const label = arguments[1];
console.log('Topic %s label %s', topic, label);
}
componentDidMount() {
var subscriber = EventSystem.subscribe('error.http', this.errorHandler);
this.state.subscriber = subscriber;
}
componentWillUnmount() {
EventSystem.unsubscribe('error.http', this.errorHandler);
// alternatively
// EventSystem.unsubscribe(this.state.subscriber);
}
render() {
}
}
其他回答
扩展@MichaelLaCroix的回答,当组件不能在任何类型的父子关系之间进行通信时,文档建议设置一个全局事件系统。
在<Filters />和<TopBar />没有任何上述关系的情况下,一个简单的全局发射器可以像这样使用:
componentDidMount -订阅事件
componentWillUnmount -从事件中取消订阅
js和EventSystem代码
EventSystem.js
class EventSystem{
constructor() {
this.queue = {};
this.maxNamespaceSize = 50;
}
publish(/** namespace **/ /** arguments **/) {
if(arguments.length < 1) {
throw "Invalid namespace to publish";
}
var namespace = arguments[0];
var queue = this.queue[namespace];
if (typeof queue === 'undefined' || queue.length < 1) {
console.log('did not find queue for %s', namespace);
return false;
}
var valueArgs = Array.prototype.slice.call(arguments);
valueArgs.shift(); // remove namespace value from value args
queue.forEach(function(callback) {
callback.apply(null, valueArgs);
});
return true;
}
subscribe(/** namespace **/ /** callback **/) {
const namespace = arguments[0];
if(!namespace) throw "Invalid namespace";
const callback = arguments[arguments.length - 1];
if(typeof callback !== 'function') throw "Invalid callback method";
if (typeof this.queue[namespace] === 'undefined') {
this.queue[namespace] = [];
}
const queue = this.queue[namespace];
if(queue.length === this.maxNamespaceSize) {
console.warn('Shifting first element in queue: `%s` since it reached max namespace queue count : %d', namespace, this.maxNamespaceSize);
queue.shift();
}
// Check if this callback already exists for this namespace
for(var i = 0; i < queue.length; i++) {
if(queue[i] === callback) {
throw ("The exact same callback exists on this namespace: " + namespace);
}
}
this.queue[namespace].push(callback);
return [namespace, callback];
}
unsubscribe(/** array or topic, method **/) {
let namespace;
let callback;
if(arguments.length === 1) {
let arg = arguments[0];
if(!arg || !Array.isArray(arg)) throw "Unsubscribe argument must be an array";
namespace = arg[0];
callback = arg[1];
}
else if(arguments.length === 2) {
namespace = arguments[0];
callback = arguments[1];
}
if(!namespace || typeof callback !== 'function') throw "Namespace must exist or callback must be a function";
const queue = this.queue[namespace];
if(queue) {
for(var i = 0; i < queue.length; i++) {
if(queue[i] === callback) {
queue.splice(i, 1); // only unique callbacks can be pushed to same namespace queue
return;
}
}
}
}
setNamespaceSize(size) {
if(!this.isNumber(size)) throw "Queue size must be a number";
this.maxNamespaceSize = size;
return true;
}
isNumber(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
}
NotificationComponent.js
class NotificationComponent extends React.Component {
getInitialState() {
return {
// optional. see alternative below
subscriber: null
};
}
errorHandler() {
const topic = arguments[0];
const label = arguments[1];
console.log('Topic %s label %s', topic, label);
}
componentDidMount() {
var subscriber = EventSystem.subscribe('error.http', this.errorHandler);
this.state.subscriber = subscriber;
}
componentWillUnmount() {
EventSystem.unsubscribe('error.http', this.errorHandler);
// alternatively
// EventSystem.unsubscribe(this.state.subscriber);
}
render() {
}
}
如果您想探索组件之间通信的选项,并且感觉通信变得越来越困难,那么您可能会考虑采用一种好的设计模式:Flux。
它只是一组规则,定义了如何存储和更改应用程序范围内的状态,以及如何使用该状态来呈现组件。
Flux有很多实现,Facebook的官方实现就是其中之一。虽然它被认为包含了最多的样板代码,但它更容易理解,因为大部分内容都是显式的。
其他的一些替代品是flumox, flumox和redux。
奇怪的是,没有人提到mobx。这个想法类似于redux。如果我有一段数据,多个组件订阅了它,那么我可以使用该数据来驱动多个组件。
即使他们不是父母-孩子关系,也有这样的可能性——那就是流动。有一个很好的(对我个人来说)实现称为Alt.JS(与Alt-Container)。
例如,您可以拥有依赖于组件详细信息设置的边栏。Component Sidebar与SidebarActions和SidebarStore连接,而Details是DetailsActions和DetailsStore。
你可以像那样使用AltContainer
<AltContainer stores={{
SidebarStore: SidebarStore
}}>
<Sidebar/>
</AltContainer>
{this.props.content}
这将保留商店(我可以用“商店”而不是“商店”道具)。现在,{this.props。content}可以是细节,取决于路由。假设/Details将我们重定向到那个视图。 例如,细节将有一个复选框,如果选中的话,将把侧边栏元素从X更改为Y。
从技术上讲,它们之间没有关系,没有通量是很难做到的。但这样就相当容易了。
现在让我们进入DetailsActions。我们会在那里创建
class SiteActions {
constructor() {
this.generateActions(
'setSiteComponentStore'
);
}
setSiteComponent(value) {
this.dispatch({value: value});
}
}
和DetailsStore
class SiteStore {
constructor() {
this.siteComponents = {
Prop: true
};
this.bindListeners({
setSiteComponent: SidebarActions.COMPONENT_STATUS_CHANGED
})
}
setSiteComponent(data) {
this.siteComponents.Prop = data.value;
}
}
现在,这就是魔法开始的地方。
如你所见,这里有SidebarActions的bindListener。如果使用setSiteComponent,将使用ComponentStatusChanged。
现在在SidebarActions
componentStatusChanged(value){
this.dispatch({value: value});
}
我们有这样的东西。它会在调用时分派那个对象。如果setSiteComponent in store将被使用(你可以在组件中使用例如onChange on Button或其他)它将被调用
现在在边栏商店中我们会有
constructor() {
this.structures = [];
this.bindListeners({
componentStatusChanged: SidebarActions.COMPONENT_STATUS_CHANGED
})
}
componentStatusChanged(data) {
this.waitFor(DetailsStore);
_.findWhere(this.structures[0].elem, {title: 'Example'}).enabled = data.value;
}
这里你可以看到,它会等待DetailsStore。这是什么意思?这或多或少意味着这个方法需要等待DetailsStoreto更新才能更新自己。
博士tl; 一个商店正在监听商店中的方法,并将从组件操作中触发一个操作,该操作将更新自己的商店。
我希望它能对你有所帮助。
下面的代码帮助我建立两个兄弟姐妹之间的通信。设置是在render()和componentDidMount()调用期间在它们的父节点中完成的。 它基于https://reactjs.org/docs/refs-and-the-dom.html 希望能有所帮助。
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>
);
}
}