Reusability/Scalability react-flux 应用程序的问题

Reusability/Scalability issues with react-flux app

问题:

有什么方法可以实现标准的通量工作流程 - 在组件内部使用 ActionsStores 并且仍然能够使用此组件用于多种不同目的,或者如果没有,是否有任何方法可以在 flux-react 应用程序中拥有复杂的嵌套结构,而无需通过巨大的回调管道传播每个更改?


例子(如果问题不够清楚):

假设我有几个超级简单的自定义组件,例如 ToggleButtonSliderDatePicker 等等。它们需要可重用,所以我不能在其中使用任何操作,而是定义了回调函数。例如 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 组件或简单的道具来完成。

假设您有一个页面,其中显示了两个网格,一个网格 - 比方说 - 会议约会和一个带有待办事项的网格。现在页面本身在层次结构中太高了,不知道什么时候触发动作,而你的 GridInfoBox 太笼统了,不知道触发哪些动作。你可以像你说的那样使用回调,但是这可能有点太有限了。

所以您有一个页面,并且有一组约会和一组待办事项。要渲染并连接它,您可能有这样的东西:

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'));

如你所见,GridInfoBox 都知道的很少,除了一些数据被传递给他们,并且他们应该在底部呈现一个组件,该组件知道如何触发行动。 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} />
    );
  }
});

因此,不再让 MyPageTodo 传递给 Grid,它现在传递 AppointmentTodo,后者仅作为了解特定操作的包装器组件,释放Todo 只关心渲染它。这是 React 中非常常见的模式,其中您的组件只是将渲染委托给另一个组件,并将 props 传递给它。