2016-08-05 20 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