Reusability/Scalability react-flux 应用程序的问题
Reusability/Scalability issues with react-flux app
问题:
有什么方法可以实现标准的通量工作流程 - 在组件内部使用 Actions 和 Stores 并且仍然能够使用此组件用于多种不同目的,或者如果没有,是否有任何方法可以在 flux-react 应用程序中拥有复杂的嵌套结构,而无需通过巨大的回调管道传播每个更改?
例子(如果问题不够清楚):
假设我有几个超级简单的自定义组件,例如 ToggleButton、Slider、DatePicker 等等。它们需要可重用,所以我不能在其中使用任何操作,而是定义了回调函数。例如 DatePicker 上的 onChange
像这样触发:
this.props.onChange(data);
我有一个自定义组件,我们称之为 InfoBox,其中包含几个上述的简单组件。这个组件像这样监听它的每个子组件的变化:
<DatePicker ref='startDate' onChange={this.startDate_changeHandler} />
InfoBox 用于不同的目的,所以我想它也不能绑定到特定的商店。
我还有一个自定义 Grid 组件,它呈现 InfoBox 的许多实例。这个网格用于在不同的页面上显示不同的数据,每个页面可以有多个网格 - 所以我想我不能将它与 Actions 和 Stores 绑定。
现在一切都变得疯狂了,请耐心等待 - 我有几页 - Clients, Products, Articles,等等。它们每个都有至少一个 Grid,每个 grid 都有一些过滤器(比如 search ).
页面当然可以使用操作和存储,但页面之间有很大的相似之处,我不想重复那么多代码(不仅是方法,还有标记)。
如您所见,它的结构相当复杂,在我看来,为嵌套组件中的每个更改实施回调方法管道是不正确的,如 DataPicker > InfoBox > Grid > Page > Something else
。
您完全正确,在 DatePicker
组件中更改日期不应触发 Flux 操作。 Flux 操作用于更改应用程序状态,几乎从不查看视图状态,其中视图状态表示 "input box X contains the value Z" 或 "the list Y is collapsed".
很高兴您正在创建 Grid
等可重用组件,这将帮助您使应用程序更易于维护。
解决你的问题的方法是从顶层向下传递组件。这可以通过 child 组件或简单的道具来完成。
假设您有一个页面,其中显示了两个网格,一个网格 - 比方说 - 会议约会和一个带有待办事项的网格。现在页面本身在层次结构中太高了,不知道什么时候触发动作,而你的 Grid
和 InfoBox
太笼统了,不知道触发哪些动作。你可以像你说的那样使用回调,但是这可能有点太有限了。
所以您有一个页面,并且有一组约会和一组待办事项。要渲染并连接它,您可能有这样的东西:
var TodoActions = {
markAsComplete: function (todo) {
alert('Completed: ' + todo.text);
}
};
var InfoBox = React.createClass({
render: function() {
return (
<div className="infobox">
{React.createElement(this.props.component, this.props)}
</div>
);
}
});
var Grid = React.createClass({
render: function() {
var that = this;
return (
<div className="grid">
{this.props.items.map(function (item) {
return <InfoBox component={that.props.component} item={item} />;
})}
</div>
);
}
});
var Todo = React.createClass({
render: function() {
var that = this;
return (
<div>
Todo: {this.props.item.text}
<button onClick={function () { TodoActions.markAsComplete(that.props.item); }}>Mark as complete</button>
</div>
);
}
});
var MyPage = React.createClass({
getInitialState: function () {
return {
todos: [{text: 'A todo'}]
};
},
render: function() {
return (
<Grid items={this.state.todos} component={Todo} />
);
}
});
React.render(<MyPage />, document.getElementById('app'));
如你所见,Grid
和 InfoBox
都知道的很少,除了一些数据被传递给他们,并且他们应该在底部呈现一个组件,该组件知道如何触发行动。 InfoBox
也将其所有属性传递给 Todo
,这使 Todo
将待办事项 object 传递给 InfoBox
。
所以这是处理这些事情的一种方法,但这仍然意味着您要在组件之间向下传播 props。在某些嵌套很深的情况下,传播变得很乏味,而且很容易忘记添加它,从而进一步分解组件。对于这些情况,我建议您查看 React 中的上下文,它非常棒。这是对上下文的一个很好的介绍:https://www.tildedave.com/2014/11/15/introduction-to-contexts-in-react-js.html
编辑
更新并回答您的评论。为了泛化示例中的 Todo
使其不知道要显式调用哪个操作,您可以将其包装在一个知道的新组件中。
像这样:
var Todo = React.createClass({
render: function() {
var that = this;
return (
<div>
Todo: {this.props.item.text}
<button onClick={function () { this.props.onCompleted(that.props.item); }}>Mark as complete</button>
</div>
);
}
});
var AppointmentTodo = React.createClass({
render: function() {
return <Todo {...this.props} onCompleted={function (todo) { TodoActions.markAsComplete(todo); }} />;
}
});
var MyPage = React.createClass({
getInitialState: function () {
return {
todos: [{text: 'A todo'}]
};
},
render: function() {
return (
<Grid items={this.state.todos} component={AppointmentTodo} />
);
}
});
因此,不再让 MyPage
将 Todo
传递给 Grid
,它现在传递 AppointmentTodo
,后者仅作为了解特定操作的包装器组件,释放Todo
只关心渲染它。这是 React 中非常常见的模式,其中您的组件只是将渲染委托给另一个组件,并将 props 传递给它。
问题:
有什么方法可以实现标准的通量工作流程 - 在组件内部使用 Actions 和 Stores 并且仍然能够使用此组件用于多种不同目的,或者如果没有,是否有任何方法可以在 flux-react 应用程序中拥有复杂的嵌套结构,而无需通过巨大的回调管道传播每个更改?
例子(如果问题不够清楚):
假设我有几个超级简单的自定义组件,例如 ToggleButton、Slider、DatePicker 等等。它们需要可重用,所以我不能在其中使用任何操作,而是定义了回调函数。例如 DatePicker 上的 onChange
像这样触发:
this.props.onChange(data);
我有一个自定义组件,我们称之为 InfoBox,其中包含几个上述的简单组件。这个组件像这样监听它的每个子组件的变化:
<DatePicker ref='startDate' onChange={this.startDate_changeHandler} />
InfoBox 用于不同的目的,所以我想它也不能绑定到特定的商店。
我还有一个自定义 Grid 组件,它呈现 InfoBox 的许多实例。这个网格用于在不同的页面上显示不同的数据,每个页面可以有多个网格 - 所以我想我不能将它与 Actions 和 Stores 绑定。
现在一切都变得疯狂了,请耐心等待 - 我有几页 - Clients, Products, Articles,等等。它们每个都有至少一个 Grid,每个 grid 都有一些过滤器(比如 search ).
页面当然可以使用操作和存储,但页面之间有很大的相似之处,我不想重复那么多代码(不仅是方法,还有标记)。
如您所见,它的结构相当复杂,在我看来,为嵌套组件中的每个更改实施回调方法管道是不正确的,如 DataPicker > InfoBox > Grid > Page > Something else
。
您完全正确,在 DatePicker
组件中更改日期不应触发 Flux 操作。 Flux 操作用于更改应用程序状态,几乎从不查看视图状态,其中视图状态表示 "input box X contains the value Z" 或 "the list Y is collapsed".
很高兴您正在创建 Grid
等可重用组件,这将帮助您使应用程序更易于维护。
解决你的问题的方法是从顶层向下传递组件。这可以通过 child 组件或简单的道具来完成。
假设您有一个页面,其中显示了两个网格,一个网格 - 比方说 - 会议约会和一个带有待办事项的网格。现在页面本身在层次结构中太高了,不知道什么时候触发动作,而你的 Grid
和 InfoBox
太笼统了,不知道触发哪些动作。你可以像你说的那样使用回调,但是这可能有点太有限了。
所以您有一个页面,并且有一组约会和一组待办事项。要渲染并连接它,您可能有这样的东西:
var TodoActions = {
markAsComplete: function (todo) {
alert('Completed: ' + todo.text);
}
};
var InfoBox = React.createClass({
render: function() {
return (
<div className="infobox">
{React.createElement(this.props.component, this.props)}
</div>
);
}
});
var Grid = React.createClass({
render: function() {
var that = this;
return (
<div className="grid">
{this.props.items.map(function (item) {
return <InfoBox component={that.props.component} item={item} />;
})}
</div>
);
}
});
var Todo = React.createClass({
render: function() {
var that = this;
return (
<div>
Todo: {this.props.item.text}
<button onClick={function () { TodoActions.markAsComplete(that.props.item); }}>Mark as complete</button>
</div>
);
}
});
var MyPage = React.createClass({
getInitialState: function () {
return {
todos: [{text: 'A todo'}]
};
},
render: function() {
return (
<Grid items={this.state.todos} component={Todo} />
);
}
});
React.render(<MyPage />, document.getElementById('app'));
如你所见,Grid
和 InfoBox
都知道的很少,除了一些数据被传递给他们,并且他们应该在底部呈现一个组件,该组件知道如何触发行动。 InfoBox
也将其所有属性传递给 Todo
,这使 Todo
将待办事项 object 传递给 InfoBox
。
所以这是处理这些事情的一种方法,但这仍然意味着您要在组件之间向下传播 props。在某些嵌套很深的情况下,传播变得很乏味,而且很容易忘记添加它,从而进一步分解组件。对于这些情况,我建议您查看 React 中的上下文,它非常棒。这是对上下文的一个很好的介绍:https://www.tildedave.com/2014/11/15/introduction-to-contexts-in-react-js.html
编辑
更新并回答您的评论。为了泛化示例中的 Todo
使其不知道要显式调用哪个操作,您可以将其包装在一个知道的新组件中。
像这样:
var Todo = React.createClass({
render: function() {
var that = this;
return (
<div>
Todo: {this.props.item.text}
<button onClick={function () { this.props.onCompleted(that.props.item); }}>Mark as complete</button>
</div>
);
}
});
var AppointmentTodo = React.createClass({
render: function() {
return <Todo {...this.props} onCompleted={function (todo) { TodoActions.markAsComplete(todo); }} />;
}
});
var MyPage = React.createClass({
getInitialState: function () {
return {
todos: [{text: 'A todo'}]
};
},
render: function() {
return (
<Grid items={this.state.todos} component={AppointmentTodo} />
);
}
});
因此,不再让 MyPage
将 Todo
传递给 Grid
,它现在传递 AppointmentTodo
,后者仅作为了解特定操作的包装器组件,释放Todo
只关心渲染它。这是 React 中非常常见的模式,其中您的组件只是将渲染委托给另一个组件,并将 props 传递给它。