2016-08-05 21 views
2

我refactorizing庞大的系统工程,其中阵营高阶元素 - 一个能走多远与传递道具

  • 一些部件看起来非常相似
  • 有文件的文件的looots(非常高的水平组件VS文件造粒)

我想过和寻找方法来处理这个问题在这里和那里,发现这个伟大的文章有关高阶组件(HOC) - 基本上是组件包装另一个组件。

https://medium.com/@franleplant/react-higher-order-components-in-depth-cf9032ee6c3e#.8vr464t20

我现在给你(A)三分之二八个类似的组件类型,我需要处理,比(B)的例如我会贴上我想出了代码将这八个文件合并为一个。最后,(C)将粘贴该统一组件的使用示例。

我会尽量保持一致,并且命名不会被域驱动(我不能在这里发布项目细节),但下面不同的代码片段中的相同名称将始终指向相同的组件和数据。否则我会指出它。

(A) - 类似的组件类型

1塔巴 - 简单的一

export default class TabA extends Component { 
    render() { 
    return (
    <PageWrapper> 
     <Grid> 
     <GridItem xsSize="3"> 
      <SmartComponent something={this.props.something }/> 
     </GridItem> 
     <GridItem xsSize="9"> 
      <Tabs 
      permalink = { this.props.permalink } 
      history={ this.props.history } 
      activeTab={ Paths.somePath } 
      /> 
      <TabAContent 
      data={ this.props.data } 
      name={ this.props.name } 
      someValue={ this.props.someValue } 
      /> 
     </GridItem> 
     </Grid> 
    </PageWrapper> 
); 
} 

}

注意SomeComponentA不采取任何的孩子也有。这里没有任何条件的渲染。

2.塔布 - 更复杂的一个

同样在这里,请注意renderSomeData方法有条件地呈现SmartComponentToBeConditionallyRendered也SomeComponentB对待儿童从道具。

export default class TabB extends Component { 
    renderSomeData() { 

    let someData = { 
     header: "Header text", 
     searchPlaceHolder: 'Search (name)', 
     buttonCaption: 'button caption' 
    }; 

    return (
    <SmartComponentToBeConditionallyRendered 
     type={ 'some_type' } 
     permalink={ this.props.permalink } 
     data={ someData } 
    /> 
) 
} 

render() { 
    let { data } = this.props; 

    return (
    <div> 
     <PageWrapper> 
     <Grid> 
      <GridItem xsSize="3"> 
      <SmartComponent something={this.props.something}/> 
      </GridItem> 
     <GridItem xsSize="9"> 
      <Tabs 
      permalink = { this.props.permalink } 
      history = { this.props.history } 
      activeTab = { Paths.somePage } 
      /> 
      <TabBContent data = { data }> 
      {this.props.children} 
      </TabBContent> 
     </GridItem> 
     </Grid> 
    </PageWrapper> 
    { 
     this.context.hasPermission('somePermission') ? 
     this.renderSomeData() : 
     null 
    } 
    </div> 
) 
} 
static contextTypes = { 
    hasPermission: React.PropTypes.func.isRequired 
} 

}

这8个分量,我一开始写的 - 它们都代表三种可能性之一。 以上两图和可能性C,但C中的差异只是另一个有条件渲染的组件,所以不值得一提,因为它最终会降低传递道具中的更多标志。 -

这两个部件上述他们在不同:

  • 样SomeComponentX的 - 代替X的有可能的A,B,而且C,d,E等在每个这八个类似的部件。 SomeComponentX中的每一个也都采用不同的道具。
  • 路径。VALUE_HERE
  • 如果SomeComponentX发生在任何的儿童或者不
  • 如果他们有条件从renderSomeData方法
  • 呈现数据如果是 - 该方法中定义someData变化以及
  • 永久
  • some_type

(B)我想出了什么

let availablePartials = { 
    PartialA: PartialA, 
    PartialB: PartialB, 
    PartialC: PartialC 
} 

export default class GenericTab extends Component { 
    renderSomeData() { 
    return (
     <SomeData 
     type  = { this.props.type } 
     permalink = { this.props.permalink } 
     data  = { this.props.someData } //PASSED FROM PROPS NOW 
     /> 
    ); 
    } 

    render() { 
    let tabContent = React.createElement(
     availablePartials[this.props.partialView.name], 
     this.props.partialView.props, 
     this.props.renderChildren ? this.props.children : null 
    ); 

    return (
     <div> 
     <PageWrapper> 
      <Grid> 
      <GridItem xsSize="3"> 
       <SmartComponent something = { this.props.permalink }/> 
      </GridItem> 
      <GridItem xsSize = "9"> 
       <Tabs 
       permalink = { this.props.permalink } 
       history = { this.props.history } 
       activeTab = { this.props.activeTab } 
       /> 
       { tabContent } 
      </GridItem> 
      </Grid> 
     </PageWrapper> 
     { 
      this.context.hasPermission(this.props.requiredPermission) &&  this.props.dataForSomeDataMethod ? 
      this.renderSomeData() 
      : null 
     } 
     </div> 
    ) 
    } 
    static contextTypes = { 
    hasPermission: React.PropTypes.func.isRequired 
    } 
}; 

CityPageTab.propTypes = { 
    permalink: PropTypes.string, 
    dataForSomeDataMethod: PropTypes.object, 
    type: PropTypes.string, 
    activeTab: PropTypes.string, 
    renderChildren: PropTypes.bool, 
    partialView: PropTypes.object, 
    requiredPermission: PropTypes.string 
}; 

基本上是所有从道具构成。我唯一不喜欢的部分是availablePartials [this.props.partialView.name]。它需要开发者保持availablePartials对象的状态一致并且纠结一点。不是很好的解决方案,但仍然是最好的我到目前为止。

(C)新GenericTab使用例

componentThatUseGenericTabRenderMethod() { 
    let { valueA, valueB, valueC, history } = this.props; 
    let someData = { 
    header: 'header text', 
    searchPlaceHolder: 'Search (name)', 
    buttonCaption: 'buttonCaption' 
    } 

    return (
    <GenericTab 
     partialView = {{ 
     name: 'PartialA', 
     props: { 
      A: valueA, 
      B: valueB, 
      C: valueC, 
      history: history, 
      permalink: this.props.params.permalink 
     } 
     }} 
     permalink = { this.props.params.permalink } 
     activeTab = { Paths.somePath } 
     someData = { someData } 
     type = { 'SOME_TYPE' } 
     renderChildren = { false } 
     requiredPermission = { 'some_required_permision' } 
    /> 
); 
} 

所以这是。用法有点复杂,但我摆脱了七个文件(并且摆脱文件是主要目标,因为它们太多了),并且将以类似的方式进一步推动它 - 通用文件。 具有通用性的东西 - 使用起来更加困难,但节省了大量空间。

项目利用终极版,所以不要太在意通过道具把树砍倒。他们总是只来自一些SmartParentComponent呈现GenericTab

下面是它的外观在页面上的可视化。 GenericTab负责渲染Tabs和TabContent部分。 是的,我知道这是很糟糕的解决方案,但我不负责它的体系结构。这里有很多事情需要重构,所问的仅仅是旅程中的一步。所以请让我们专注于所问的问题,而不是其他与此代码错误的事情。我知道了。:)

VISUALISATION

想我可以做的文章出来的,但我真的没有博客做:)。

请告诉我你的想法,建议升级,处理该问题的不同方式等

+0

您可以加入的是什么选项卡看起来像一些视觉上的表现?这里很难理解整个事情,但我已经可以告诉你,你是组件负责渲染很多东西,你应该把它分成几个较小的组件。 – Pcriulan

+0

嗯,唯一对我来说合理的分割方法是'renderSomeData'。我可以提取它,这是真的。除此之外,我在这里没有看到很多可能性。项目很混乱,有很多事情要做。是的,我会尝试编辑我的问题并添加可视化。 – azrahel

+0

我相信这是有点可能的,当你编辑你的问题我想我可以帮你这么做 – Pcriulan

回答

1

当这样的架构处理(即,你的情况的选项卡),你基本上没有要隐藏的架构在引擎盖下,因为在这种情况下,您最终会为每个想要处理的新案例添加越来越多的属性。

相反,你wan't让反应处理嵌套结构,因为它是其中反应真正的亮点。这可以让你通过处理内置的children道具来写出非常通用的东西。您通常想写类似:

const PageWithTabs = (props) => (
    <Tabs defaultActive={'targaryens-panel'}> 
    <TabBar> 
     <TabLink href="#starks-panel">{'Starks'}</TabLink> 
     <TabLink href="#lannisters-panel">{'Lannisters'}</TabLink> 
     <TabLink href="#targaryens-panel">{'Targaryens'}</TabLink> 
    </TabBar> 
    <TabPanel id="starks-panel"> 
     <ul style={{ listStyleType: 'none' }}> 
     <li>Eddard</li> 
     <li>Catelyn</li> 
     <li>Robb</li> 
     <li>Sansa</li> 
     <li>Brandon</li> 
     <li>Arya</li> 
     <li>Rickon</li> 
     </ul> 
    </TabPanel> 
    <TabPanel id="lannisters-panel"> 
     <ul style={{ listStyleType: 'none' }}> 
     <li>Tywin</li> 
     <li>Cersei</li> 
     <li>Jamie</li> 
     <li>Tyrion</li> 
     </ul> 
    </TabPanel> 
    <TabPanel id="targaryens-panel"> 
     <ul style={{ listStyleType: 'none' }}> 
     <li>Viserys</li> 
     <li>Daenerys</li> 
     </ul> 
    </TabPanel> 
    </Tabs> 
) 

这里的关键是,你不必“预测”都可能在每个TabPanel出现的事情,干脆让任何他想要的developper放! 我需要一些逻辑来处理“去标签”之类的东西。

阵营提供了一些非常方便的工具,方法,以动态地克隆元素,地图上的元素,并呈现元素的类型是否是您所期望的一个或没有(在我们的情况下,我们预计TabBarTabPanel类型,没有什么阻止developper到把任何其他组件比这两个,但没有阻止他既不把任何内置<table> html元素内的<a>标签或类似的东西奇怪)。

这里是一个小的实现与材质设计精简版,它并不完美,但你应该明白了吧:

class Tabs extends React.Component { 
 
    constructor(props) { 
 
    super(props), 
 
    this.state = { 
 
     activeTabId: props.defaultActive 
 
    } 
 
    } 
 
    tabClickHandlerFactory(id) { 
 
    return (e) => { 
 
     e.preventDefault() 
 
     this.setState({ 
 
     activeTabId: id 
 
     }) 
 
    } 
 
    } 
 
    getPanelIdFromLink(href) { 
 
     return href.split('#')[1] 
 
    } 
 
    render() { 
 
    const self = this 
 

 
    return (
 
     <div className='mdl-tabs is-upgraded' {...self.props}> 
 
     {React.Children.map(self.props.children, (child, index) => { 
 
      if (child.type == TabBar) { 
 
      return React.cloneElement(child, {}, React.Children.map(child.props.children, (link) => { 
 
       const id = self.getPanelIdFromLink(link.props.href) 
 
       return (
 
       React.cloneElement(link, { 
 
        onClick: self.tabClickHandlerFactory(id), 
 
        active: self.state.activeTabId === id 
 
       }) 
 
      ) 
 
      })) 
 
      } 
 
      if (child.type == TabPanel) { 
 
      const { id } = child.props 
 
      const active = self.state.activeTabId === id 
 
      return active && React.cloneElement(child, { active: true }) 
 
      } 
 
     })} 
 
     </div> 
 
    ) 
 
    } 
 
} 
 

 
Tabs.propTypes = { 
 
    defaultActive: React.PropTypes.string.isRequired, 
 
} 
 

 
const TabBar = (props) => <div className='mdl-tabs__tab-bar' {...props}>{props.children}</div> 
 

 
const TabLink = ({ active, ...props }) => { 
 
    return (
 
    <a className={`mdl-tabs__tab${active ? ' is-active' : ''}`} {...props}>{props.children}</a> 
 
) 
 
} 
 

 

 
const TabPanel = ({ active, ...props }) => (
 
    <div className={`mdl-tabs__panel${active ? ' is-active' : ''}`} {...props}>{props.children}</div> 
 
) 
 

 
const PageWithTabs = (props) => (
 
    <Tabs defaultActive={'targaryens-panel'}> 
 
    <TabBar> 
 
     <TabLink href="#starks-panel">{'Starks'}</TabLink> 
 
     <TabLink href="#lannisters-panel">{'Lannisters'}</TabLink> 
 
     <TabLink href="#targaryens-panel">{'Targaryens'}</TabLink> 
 
    </TabBar> 
 
    <TabPanel id="starks-panel"> 
 
     <ul style={{ listStyleType: 'none' }}> 
 
     <li>Eddard</li> 
 
     <li>Catelyn</li> 
 
     <li>Robb</li> 
 
     <li>Sansa</li> 
 
     <li>Brandon</li> 
 
     <li>Arya</li> 
 
     <li>Rickon</li> 
 
     </ul> 
 
    </TabPanel> 
 
    <TabPanel id="lannisters-panel"> 
 
     <ul style={{ listStyleType: 'none' }}> 
 
     <li>Tywin</li> 
 
     <li>Cersei</li> 
 
     <li>Jamie</li> 
 
     <li>Tyrion</li> 
 
     </ul> 
 
    </TabPanel> 
 
    <TabPanel id="targaryens-panel"> 
 
     <ul style={{ listStyleType: 'none' }}> 
 
     <li>Viserys</li> 
 
     <li>Daenerys</li> 
 
     </ul> 
 
    </TabPanel> 
 
    </Tabs> 
 
) 
 

 
ReactDOM.render(<PageWithTabs/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script> 
 
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script> 
 
<link rel="stylesheet" href="https://code.getmdl.io/1.1.3/material.brown-orange.min.css" /> 
 
<div id='app'></div>

+0

感谢您的帮助:) – azrahel

+0

不客气=) – Pcriulan