如何在React中使用组合而不是mixins来解决这个问题?

时间:2015-06-15 12:46:04

标签: reactjs

假设我有一个关于汽车销售的数据集,我正在构建显示图表的组件,这些图表显示了该数据的不同方面。

一个组件可以例如显示汽车的平均销售价格,而另一个组件可以显示汽缸数量与去年购买的汽车里程数的关系。

这些组件还可以进一步参数化,以便它们仅显示在给定国家/地区购买的汽车等的数据。似乎使用道具传递参数和数据似乎是合适的,因此这些组件可以像这样:

<MileageByCylinder country="US" year="2014" dataset={data} />
<AvgSellingPricesByYear country="US"  dataset={data} />

到目前为止,这么好。但是,我要说的是,我正在构建两个以不同方式组成这些组件的视图(页面)。第一个将静态显示不同国家/地区的销售价格,而另一个将具有用于选择国家/地区的UI以及按柱面显示里程数。两个视图都需要以某种方式获取它们传递给图表组件的数据集。但是如何重新获取获取数据集的逻辑呢?

使用mixins,可以按如下方式完成:

var CarDataMixin = {

  componentDidMount: {
    // fetch car data and
    // call this.setState({carData: fetchedData}),
    // once data has been (asynchronously) fetched
  }

}

var FirstView = React.createClass({

  mixins: [CarDataMixin],

  render: function() {
    return (
        <div>
          <AvgSellingPricesByYear country="US"  dataset={this.state.carData} />
          <AvgSellingPricesByYear country="UK"  dataset={this.state.carData} />
          <AvgSellingPricesByYear country="FI"  dataset={this.state.carData} />
        </div>
      )
   }

})

var SecondView = React.createClass({

  mixins: [CarDataMixin],

  handleNewCountry: function(country) {
    this.state{country: country}
  },

  render: function() {
    return (
        <div>
          <CountryChooser onChange={this.handleNewCountry} />
          <MileageByCylinder country="{country}" year="2014" dataset={this.state.carData} />
          <AvgSellingPricesByYear country="{country}"  dataset={this.state.carData} />
        </div>
      )
   }

})
好的,好的。但是很多人建议不要使用mixins,而应该使用这种成分。使用我提出的构图来解决这个问题的唯一方法如下:

  1. 创建一个将其状态传递给子节点的根组件作为props:

    var CarSalesRoot = React.createClass({
        componentDidMount: {
          // fetch car data and
          // call this.setState({carData: fetchedData}),
          // once data has been (asynchronously) fetched
        }
    
        renderChildren: function () {
            return React.Children.map(this.props.children, function (child) {
              return React.addons.cloneWithProps(child, {
                  carData: this.state.carData
              })
            }.bind(this))
        },
    
    
        render: function() {
          return <div>{this.renderChildren()}</div>
        } 
    });
    
  2. 创建没有mixin的视图:

    var FirstView = React.createClass({
        render: function() {
          return (
              <div>
                <AvgSellingPricesByYear country="US"  dataset={this.props.carData} />
                <AvgSellingPricesByYear country="UK"  dataset={this.props.carData} />
                <AvgSellingPricesByYear country="FI"  dataset={this.props.carData} />
              </div>
            )
         }
    });
    
  3. 创建一个包装根组件和主视图组件的包装器组件:

    var FirstViewMain = React.createClass({
        render: function() {
          return (
              <CarSalesRoot>
                <FirstView />
              </CarSalesRoot>
            )
         }
    });
    
  4. 这感觉有点棘手,并且使数据流不那么明确。我觉得这是非常基本的东西,应该以一种干净的方式解决。我错过了一些明显的东西吗或者这实际上是一个惯用而干净的解决方案?

1 个答案:

答案 0 :(得分:6)

Another option for using composition is to create a "higher-order component," which is analogous to a higher-order function in functional programming, but really it is just a wrapper that is a slight variation on the code you presented.

  1. Define the higher-order component:

    var bindToCarData = function (Component) {
      return React.createClass({
        componentDidMount: {
          // fetch car data and
          // call this.setState({carData: fetchedData}),
          // once data has been (asynchronously) fetched
        },
    
        render: function() {
          return <Component carData={ this.state.carData } />
        }
      });
    });
    
  2. Then wrap your component when you define it.

    var FirstView = bindToCarData(React.createClass({
      render: function() {
        return (
            <div>
              <AvgSellingPricesByYear country="US"  dataset={this.props.carData} />
              <AvgSellingPricesByYear country="UK"  dataset={this.props.carData} />
              <AvgSellingPricesByYear country="FI"  dataset={this.props.carData} />
            </div>
          )
       }
    }));
    

This save you from writing the extra component (number 3 in your question), and it ties in the data flow logic directly in the component that needs the data.

You can pass additional parameters into the bindToCarData function if you need to.