React 组件设想和剖析思索

之前分享过几篇关于React手艺栈的文章:

本日再来同人人议论 React 组件设想的一个风趣话题:剖析 React 组件的几种进阶要领。

React 组件魔力无限,同时灵活性超强。我们能够在组件的设想上,玩转出许多名堂。然则保证组件的Single responsibility principle: 单一准绳异常重要,它能够使得我们的组件更简朴、更轻易保护,更重要的是使得组件越发具有复用性。

然则,怎样对一个功用庞杂且痴肥的 React 组件举行剖析,或许并非一件简朴的事变。本文由浅入深,引见三个剖析 React 组件的要领。

切割 render() 要领

这是一个最轻易想到的要领:当一个组件衬着了许多元素时,就须要尝试星散这些元素的衬着逻辑。最敏捷的体式格局就是切割 render() 要领为多个 sub-render 要领。

看下面的例子会越发直观:

class Panel extends React.Component {
  renderHeading() {
    // ...
  }

  renderBody() {
    // ...
  }

  render() {
    return (
      <div>
        {this.renderHeading()}
        {this.renderBody()}
      </div>
    );
  }
  

仔细的读者很快就可以发明,实在这并没有剖析组件本身,该 Panel 组件依然保持有本来的 state, props, 以及 class methods。

怎样真正地做到削减庞杂度呢?我们须要建立一些子组件。此时,采纳最新版 React 支撑并引荐的函数式组件/无状况组件肯定会是一个很好的尝试:

const PanelHeader = (props) => (
  // ...
);

const PanelBody = (props) => (
  // ...
);

class Panel extends React.Component {
  render() {
    return (
      <div>
        // Nice and explicit about which props are used
        <PanelHeader title={this.props.title}/>
        <PanelBody content={this.props.content}/>
      </div>
    );
   }
 }
 

同之前的体式格局比拟,这个玄妙的革新是革命性的。我们新建了两个单位组件:PanelHeader 和 PanelBody。如许带来了测试的方便,我们能够直接星散测试差别的组件。同时,借助于 React 新的算法引擎 React Fiber,两个单位组件在衬着的效力上,乐观地估计会有较大幅度的提拔。

模版化组件

回到题目的出发点,为何一个组件会变的痴肥而庞杂呢?其一是衬着元素较多且嵌套,别的就是组件内部变化较多,或许存在多种 configurations 的状况。

此时,我们便能够将组件革新为模版:父组件相似一个模版,只专注于种种 configurations。

照样要举例来说,如许明白起来越发清楚。

比方我们有一个 Comment 组件,这个组件存在多种行动或事宜。同时组件所展现的信息依据用户的身份差别而有所变化:用户是不是是此 comment 的作者,此 comment 是不是被准确保留,种种权限差别等等都邑引发这个组件的差别展现行动。这时候,与其把一切的逻辑殽杂在一起,或许更好的做法是应用 React 能够通报 React element 的特征,我们将 React element 举行组件间通报,如许就越发像一个模版:

class CommentTemplate extends React.Component {
  static propTypes = {
    // Declare slots as type node
    metadata: PropTypes.node,
    actions: PropTypes.node,
  };
  
  render() {
    return (
      <div>
        <CommentHeading>
          <Avatar user={...}/>
          
          // Slot for metadata
          <span>{this.props.metadata}</span>
          
        </CommentHeading>
    
        <CommentBody/>
        
        <CommentFooter>
          <Timestamp time={...}/>
          
          // Slot for actions
          <span>{this.props.actions}</span>
          
        </CommentFooter>
      </div>
      ...
      

此时,我们真正的 Comment 组件构造为:

class Comment extends React.Component {
  render() {
    const metadata = this.props.publishTime ?
      <PublishTime time={this.props.publishTime} /> :
      <span>Saving...</span>;
    
    const actions = [];
    if (this.props.isSignedIn) {
      actions.push(<LikeAction />);
      actions.push(<ReplyAction />);
    }
    if (this.props.isAuthor) {
      actions.push(<DeleteAction />);
    }
    
    return <CommentTemplate metadata={metadata} actions={actions} />;
  }
  

metadata 和 actions 实在就是在特定状况下须要衬着的 React element。

比方,假如 this.props.publishTime 存在,metadata 就是 <PublishTime time={this.props.publishTime} />;横竖则为 <span>Saving…</span>。

假如用户已上岸,则须要衬着(即actions值为) <LikeAction /> 和 <ReplyAction />,假如是作者本身,须要衬着的内容就要到场 <DeleteAction />。

高阶组件

在现实开辟当中,组件常常会被其他需求所污染。

比方,我们想统计页面中一切链接的点击信息。在链接点击时,发送统计要求,同时包括此页面 document 的 id 值。罕见的做法是在 Document 组件的性命周期函数 componentDidMount 和 componentWillUnmount 增添代码逻辑:

class Document extends React.Component {
  componentDidMount() {
    ReactDOM.findDOMNode(this).addEventListener('click', this.onClick);
  }
  
  componentWillUnmount() {
    ReactDOM.findDOMNode(this).removeEventListener('click', this.onClick);
  }
  
  onClick = (e) => {
    if (e.target.tagName === 'A') { // Naive check for <a> elements
      sendAnalytics('link clicked', {
        documentId: this.props.documentId // Specific information to be sent
      });
    }
  };
  
  render() {
    // ...
    

这么做的几个题目在于:

  • 相干组件 Document 除了本身的重要逻辑:显现主页面以外,多了其他统计逻辑;

  • 假如 Document 组件的性命周期函数中,还存在其他逻辑,那末这个组件就会变的越发暧昧不合理;

  • 统计逻辑代码没法复用;

  • 组件重构、保护都邑变的越发难题。

为了处理这个题目,我们提出了高阶组件这个观点: higher-order components (HOCs)。不去艰涩地诠释这个名词,我们来直接看看运用高阶组件怎样来重构上面的代码:

function withLinkAnalytics(mapPropsToData, WrappedComponent) {
  class LinkAnalyticsWrapper extends React.Component {
    componentDidMount() {
      ReactDOM.findDOMNode(this).addEventListener('click', this.onClick);
    }

    componentWillUnmount() {
      ReactDOM.findDOMNode(this).removeEventListener('click', this.onClick);
    }

    onClick = (e) => {
      if (e.target.tagName === 'A') { // Naive check for <a> elements
        const data = mapPropsToData ? mapPropsToData(this.props) : {};
        sendAnalytics('link clicked', data);
      }
    };
    
    render() {
      // Simply render the WrappedComponent with all props
      return <WrappedComponent {...this.props} />;
    }
  }

须要注重的是,withLinkAnalytics 函数并不会去转变 WrappedComponent 组件本身,更不会去转变 WrappedComponent 组件的行动。而是返回了一个被包裹的新组件。现实用法为:

class Document extends React.Component {
  render() {
    // ...
  }
}

export default withLinkAnalytics((props) => ({
  documentId: props.documentId
}), Document);

如许一来,Document 组件依然只需体贴本身该体贴的部份,而 withLinkAnalytics 给予了复用统计逻辑的才能。

高阶组件的存在,圆满展现了 React 生成的复合(compositional)才能,在 React 社区当中,react-redux,styled-components,react-intl 等都广泛采纳了这个体式格局。值得一提的是,recompose 类库又应用高阶组件,并发扬光大,做到了“脑洞大开”的事变。

总结

React 及其周边社区的兴起,让函数式编程风靡一时,遭到追捧。个中关于 decomposing 和 composing 的头脑,我以为异常值得进修。同时,对开辟设想的一个发起是,不要犹疑将你的组件拆分的更小、更单一,由于如许能换来强壮和复用。

本文意译了David Tang的:Techniques for decomposing React components一文。

Happy Coding!

PS: 作者Github堆栈,迎接经由过程代码种种形式交换。

    原文作者:lucas_580e331d326b4
    原文地址: https://segmentfault.com/a/1190000009952681
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞