构建兄弟组件需要通信的ReactJS组件

时间:2014-12-06 21:12:05

标签: javascript reactjs

所以我试图找出构建特定类型的ReactJS元素的最佳方法。

所以我想说我有一个名为ContentArea的元素。 ContentArea可以由许多其他自定义元素组成,ContentAreaHeader,ContentAreaContent和ContentAreaAction。 ContentArea,ContentAreaHeader和ContentAreaContent基本上是包装元素,它使用适当的类将其子元素包装在正确的HTML元素中。 ContentAreaAction的实现对于这个问题并不重要,只是想提一下它显示有许多不同的元素。 ContentArea应该只有1个头元素,但应该能够支持多个其他项(ContentAreaContent和/或ContentAreaAction)。

一个功能是能够点击标题并切换显示标题旁边的其他元素。来自AngularJS世界,我最初的目的是创建一个我可以重用的指令,所以我在ReactJS中尝试了这个,我的代码看起来像这样:

var MyPage  = React.createClass({
  render: function() {
    return (
      <ContentArea>
        <ContentAreaHeader>My Header</ContentAreaHeader>
        <ContentAreaContent className={cssClasses.join(' ')}>My Content</ContentAreaContent>
      </ContentArea>
    );
  }
});

现在我可以在MyPage组件中添加事件和折叠状态,但是每个页面元素只能有1个ContentArea,或者每个ContentArea都有多个复制,这两个都不好。在AngularJS中,每个组件都可以拥有自己的范围,并从其父组件继承,从而防止出现此问题。

我目前的解决方案是我创建了以下mixin:

var ContentAreaCollapsableMixin = {
  getInitialState: function() {
    return {
      collapsed: false
    };
  },

  toggleCollapse: function() {
    this.setState({
      collapsed: !this.state.collapsed
    });
  }
}

现在每个页面元素有多个ContentAreas,我创建了一个自定义的ContentArea元素来满足页面的需求:

var MyContentArea  = React.createClass({
  mixins: [
    contentArea.mixins.collapsable
  ],

  render: function() {
    var cssClasses = [];

    console.log(this.state.collapsed);

    if(this.state.collapsed) {
      cssClasses.push('u-hide');
    }

    return (
      <ContentArea>
        <span onClick={this.toggleCollapse}><ContentAreaHeader>My Header</ContentAreaHeader></span>
        <ContentAreaContent className={cssClasses.join(' ')}>My Content</ContentAreaContent>
      </ContentArea>
    );
  }
});

var MyContentArea2  = React.createClass({
  mixins: [
    contentArea.mixins.collapsable
  ],

  render: function() {
    var cssClasses = [];

    if(this.state.collapsed) {
      cssClasses.push('u-hide');
    }

    return (
      <ContentArea>
        <span onClick={this.toggleCollapse}><ContentAreaHeader>My Header</ContentAreaHeader></span>
        <ContentAreaContent className={cssClasses.join(' ')}>My Content</ContentAreaContent>
        <ContentAreaContent className={cssClasses.join(' ')}>My Content2</ContentAreaContent>
      </ContentArea>
    );
  }
});

var ContentAreaComponents = React.createClass({
  render: function() {
    return (
      <div>
        <h1 id="test" className="test">Content Area</h1>
        <MyContentArea />
        <MyContentArea2 />
      </div>
    );
  }
});

注意我使用span来附加我的事件,因为据我所知我不能将事件附加到自定义/子元素,并且标题不应该总是有这个事件所以我不想污染header指令使用该内容(也许我可能想将该事件添加到标题中的图标而不是整个标题中。)

在处理包装器并具有这样的层次结构的元素时,这是构建此类功能的正确方法吗?

1 个答案:

答案 0 :(得分:1)

最干净的方法是将组件作为道具传递。例如:

<ContentArea
  header={"My Header"}
  content={[
    <div>My Content</div>,
    <div>My Other Content</div>
  ]}
  />

这在JSX中看起来有点奇怪,所以如果你愿意,你可以不用。

React.createElement(ContentArea, {
  header: "My Header",
  content: [
    <div>My Content</div>,
    <div>My Other Content</div>
  ]
})

在ContentArea中,你可以简单地渲染这些道具,因为你可以渲染props.children,但需要更多控制。

var ContentArea = React.createClass({
  getInitialState: function(){ return {open: true} },
  toggleOpen: function(){ this.setState({open: !this.state.open}) },

  render: function(){
    var className = this.state.open ? "" : "hidden";

    return (
      <div>
        <ContentAreaHeader onClick={this.toggleOpen}>
          {this.props.header}
        </ContentAreaHeader>

        {this.props.content.map(function(element, index){

          return (
            <ContentAreaContent className={className} key={index}>
              {element}
            </ContentAreaContent>
          );

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

此示例中的结果结构为:

<ContentArea>
  <div>
    <ContentAreaHeader>My Header</ContentAreaHeader>
    <ContentAreaContent className="..." key="0">
      <div>My Content</div>
    </ContentAreaContent>

    <ContentAreaContent className="..." key="1">
      <div>My Other Content</div>
    </ContentAreaContent>
  </div>
</ContentArea>

这是不违反任何规则的方式。使用您提到的API执行此操作的方法是使用React.Children.map并确定它是基于索引的标题还是内容(例如0是标题,1..infinity是内容),并且您将它包装在div中以应用click处理程序和className respectivley。