【问题标题】:React container reusability (with Redux, Immutable and Reselect)React 容器可重用性(使用 Redux、Immutable 和 Reselect)
【发布时间】:2018-07-03 11:42:03
【问题描述】:

我有一个用例,我有一个容器,比如 BarChartContainer。这个容器把 URL 作为一个 prop 接收进来,我的 saga 在这个 URL 的基础上得到了需要的数据,然后发送到 BarChartComponent(PureComponent),这个 BarChartContainer 调用了这个组件来渲染柱状图。 现在,我希望它是动态的,比如说如果我想在容器中的 3 个地方使用它,我只需传入所需 URL 的属性,容器的每个实例都应该根据 URL 呈现数据。我面临的问题是,即使对这个容器的每次调用都有不同的道具,但容器只有一个商店。所以最终,只有 1 个 URL 存储在商店中(即来自 BarChartContainer 第三个实例的道具的那个。我应该如何配置商店,以便它维护一棵树,以保存实例的状态。

这是我的容器的代码

export class BarChartContainer extends React.PureComponent { 
  static propTypes = {
    initializeBarChart: PropTypes.func,
    data: PropTypes.object.isRequired,
    dataUrl: PropTypes.string,
    addUrlToStore: PropTypes.func,
  };
  /**
   *Defines the default values of certain props
   *
   * @static
   * @memberof BarChartContainer
   */
  static defaultProps = {
    data: {},
    dataUrl: '',
    };
  /**
   *This function is called on the initial load of BarChartContainer
   *
   * @memberof BarChartContainer
   */
  componentWillMount() {
    this.props.addUrlToStore(this.props.dataUrl);
    this.props.initializeBarChart();
  }
  render() {
    return (
      <div className="container-barchartcontainer">
        <BarChart data={this.props.data.dataPoints} />
      </div>
    );
  }
}

const mapStateToProps = createStructuredSelector({
  data: selectData(),
});

function mapDispatchToProps(dispatch) {
  return {
    initializeBarChart: () => dispatch(defaultAction()),
    addUrlToStore: (url) => dispatch(addUrlToStoreAction(url)),
  };
}

const withConnect = connect(mapStateToProps, mapDispatchToProps);

const withReducer = injectReducer({ key: 'barChartContainer', reducer });
const withSaga = injectSaga({ key: 'barChartContainer`, saga });

export default compose(
  withReducer,
  withSaga,
  withConnect,
)(BarChartContainer);

这是三个容器的支架

    export default class Holder extends React.PureComponent {
      render() {
        return (
          <div className="container-holder">
            <BarChartContainer dataUrl='url1' />

            <BarChartContainer dataUrl='url2' />

            <BarChartContainer dataUrl='url3' />
          </div>

        );

  }
}

【问题讨论】:

  • 有办法,但我会重新考虑设计。您可以为每个容器分配一个 id 并使用该 id 调用您的 selectData ,或者您可以将容器转换为 PureComponent 并将选择它的责任移至其父级。 IMO 第二种选择会更好。
  • 感谢 Facundo,但是对于这两种情况中的任何一种,我都没有达到我最初的基本目标。我想以最少的代码更改注入一个容器到一个持有者,这样容器可以根据我传递的道具以及存储在其存储中的数据进行配置。您能否建议另一种技术,以便我能够为商店维护树状结构。比如说,将父级作为 barchartcontainer 的存储,然后将数据关联到作为子级创建的容器的每个实例,树?

标签: reactjs redux react-redux reactive-programming code-reuse


【解决方案1】:

为了完成您想要的,(IMO)您必须重新考虑模型/流程。

与商店绑定的容器不可重复使用,如果它们代表商店的不同部分,它们也不应该重复使用。在这种情况下,由于数据不同,在我看来,它们确实代表商店的不同部分。所以可重用的部分只是渲染部分,没有别的。

如果是这种情况,那么 (IMO) 我将为我需要表示的商店的每个部分创建一个容器,并且我还将创建用于呈现数据的组件。

由于每个图在语义上代表不同的事物,因此将它们分开在简单性和可扩展性方面都有好处。

另外一点是,由于数据来自不同的来源,每个容器可能需要不同的方式来处理和管理与其各自来源的通信(异常、身份验证、授权等)。

选项 1:

在您的情况下,例如,我将创建三个主要容器和一个 BarChartContainer:

// If this URL is likely going to be hardcoded, then It does not make any sense to be passed as a prop.
// If this URL comes from a dynamic source, then it should have already been injected to the store when the URL was got
const dataUrl = 'http://url.com'; 

class ParticularBussinessBarChart extends React.Component { 
  componentWillMount() {
    this.props.addUrlToStore(dataUrl); //This could not make sense
    this.props.initializeParticularBussinessBarChart(); // Why not injecting the url here directly?
  }

  render() {
    return (
        <BarChartContainer dataPoints={this.props.data.dataPoints} />
    );
  }
}

const mapStateToProps = createStructuredSelector({
  data: selectData(),
});

function mapDispatchToProps(dispatch) {
  return {
    initializeParticularBussinessBarChart: () => dispatch(defaultAction()),
    addUrlToStore: (url) => dispatch(addUrlToStoreAction(url)),
  };
}

const withConnect = connect(mapStateToProps, mapDispatchToProps);

const withReducer = injectReducer({ key: 'particularBussinessBarChart', reducer });
const withSaga = injectSaga({ key: 'particularBussinessBarChart`, saga });

export default compose(
  withReducer,
  withSaga,
  withConnect,
)(ParticularBussinessBarChart);

然后我将有一个可重用的组件来负责渲染图形,但仅此而已。

//This class might become useless with this workaround
export class BarChartContainer extends React.PureComponent { 
  render() {
    return (
      <div className="container-barchartcontainer">
        <BarChart data={this.props.dataPoints} />
      </div>
    );
  }
}

而用法是:

export default class Holder extends React.Component {
  componentWillMount() {
    this.props.addUrlToStore(dataUrl); //This could not make sense
    this.props.initializeParticularBussinessBarChart(); // Why not injecting the url here directly?
  }

  render() {
    return (
      <div className="container-holder">
        <ParticularBussinessBarChart/>

        <OtherParticularBussinessBarChart />

        <AnotherParticularBussinessBarChart />
      </div>
    );
  }
}

选项 2:

另一种选择是让一个 Holder 负责分别获取相应数据并将其传递给每个图表。 此选项一开始会更简单,但如果添加更多图表,可能会变得更加复杂。

export class BarChartContainer extends React.PureComponent { 
  render() {
    return (
      <div className="container-barchartcontainer">
        <BarChart data={this.props.dataPoints} />
      </div>
    );
  }
}

用法是:

class Holder extends React.Component {
  componentWillMount() {
    this.props.initializeBarChart1('url1');
    this.props.initializeBarChart2('url2');
    this.props.initializeBarChart3('url3');
  }

  render() {
    return (
      <div className="container-holder">
        <BarChartContainer dataPoints={this.props.dataPointsChart1} />

        <BarChartContainer dataPoints={this.props.dataPointsChart2} />

        <BarChartContainer dataPoints={this.props.dataPointsChart3} />
      </div>
    );
  }
}

const mapStateToProps = createStructuredSelector({
  dataPointsChart1: selectDataForChar1(),
  dataPointsChart2: selectDataForChar2(),
  dataPointsChart3: selectDataForChar3(),
});

function mapDispatchToProps(dispatch) {
  return {
    initializeBarChart1: () => dispatch(correspondingAction()),
    initializeBarChart2: () => dispatch(correspondingAction()),
    initializeBarChart3: () => dispatch(correspondingAction()),
  };
}

const withConnect = connect(mapStateToProps, mapDispatchToProps);

const withReducer = injectReducer({ key: 'holder', reducer });
const withSaga = injectSaga({ key: 'holder`, saga });

export default compose(
  withReducer,
  withSaga,
  withConnect,
)(ParticularBussinessBarChart);

我更喜欢第一个选项,因为它对我来说似乎更具可扩展性和更简单。但这实际上取决于您提供的业务和图表的性质。

【讨论】:

  • 感谢 Facundo,但是对于这两种情况中的任何一种,我都没有达到我最初的基本目标。我想以最少的代码更改注入一个容器到一个持有者,这样容器可以根据我传递的道具以及存储在其存储中的数据进行配置。您能否建议另一种技术,以便我能够为商店维护树状结构。比如说,将父级作为 barchartcontainer 的存储,然后将数据关联到作为子级创建的容器的每个实例,树?
  • 好吧,我只能想到拥有第三个存储,它维护您需要在容器之间共享的基本信息,因此每个容器将从两个不同的存储读取,公共存储和特定存储。但这里的主要概念是容器应该独立于任何其他容器,并从存储中获取所有内容。你明白了吗?如果需要,我可以提供一个从两个不同商店读取容器的示例。
  • 是的,这种方法的一个例子会很棒
猜你喜欢
  • 1970-01-01
  • 2019-01-27
  • 1970-01-01
  • 2017-02-09
  • 2020-08-07
  • 2019-03-19
  • 2019-09-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多