如何寫出漂亮的React組件

本文翻譯自Make-Your-React-Components-Pretty。歡迎轉載,註明出處。
本文從屬於筆者的Web前端入門與最佳實踐 中的React入門與最佳實踐系列,同類型文章還包括React 代碼風格約定前端

Walmart Labs的產品開發中,咱們進行了大量的Code Review工做,這也保證了我有機會從不少優秀的工程師的代碼中學習他們的代碼風格與樣式。在這篇博文裏我會分享出我最欣賞的五種組件模式與代碼片。不過我首先仍是要談談爲何咱們須要執着於提升代碼的閱讀體驗。就好像你有不少種方式去裝扮一隻貓,若是你把你的愛貓裝扮成了以下這樣子:
react

你或許能夠認爲蘿蔔青菜各有所愛,可是代碼自己是應當保證其可讀性,特別是在一個團隊中,你的代碼是註定要被其餘人閱讀的。電腦是不會在乎這些的,無論你朝它們扔過去什麼,它們都會老老實實的解釋,可是你的隊友們可不會這樣,他們會把醜陋的代碼扔回到你的臉上。而所謂的Pretty Components,應該包含以下的特性:git

  • 即便沒有任何註釋的狀況下也易於理解github

  • 比亂麻般的代碼有更好的性能表現less

  • 更易於進行Bug追溯ide

  • 簡潔明瞭,一句頂一萬句函數

SFC:Stateless Functional Component

我以爲咱們在開發中常常忽略掉的一個模式就是所謂的Stateless Functional Component,不過這是我我的最愛的React組件優化模式,沒有之一。我喜好這種模式不只僅由於它們可以減小大量的模板代碼,並且由於它們可以有效地提升組件的性能表現。總而言之,SFC可以讓你的應用跑的更快,長的更帥。
性能

直觀來看,SFC就是指那些僅有一個渲染函數的組件,不過這簡單的改變就能夠避免不少的無心義的檢測與內存分配。下面咱們來看一個實踐的例子來看下SFC的具體做用,譬如:

若是咱們用正統的React組件的寫法,能夠得出以下代碼:學習

export default class RelatedSearch extends React.Component {
  constructor(props) {
    super(props);
    this._handleClick = this._handleClick.bind(this);
  }
  _handleClick(suggestedUrl, event) {
    event.preventDefault();
    this.props.onClick(suggestedUrl);
  }
  render() {
    return (
      <section className="related-search-container">
        <h1 className="related-search-title">Related Searches:</h1>
        <Layout x-small={2} small={3} medium={4} padded={true}>
          {this.props.relatedQueries.map((query, index) =>
            <Link
              className="related-search-link"
              onClick={(event) =>
                this._handleClick(query.searchQuery, event)}
              key={index}>
              {query.searchText}
            </Link>
          )}
        </Layout>
      </section>
    );
  }
}

而使用SFC模式的話,大概能夠省下29%的代碼:優化

const _handleClick(suggestedUrl, onClick, event) => {
  event.preventDefault();
  onClick(suggestedUrl);
};
const RelatedSearch = ({ relatedQueries, onClick }) =>
  <section className="related-search-container">
    <h1 className="related-search-title">Related Searches:</h1>
    <Layout x-small={2} small={3} medium={4} padded={true}>
      {relatedQueries.map((query, index) =>
        <Link
          className="related-search-link"
          onClick={(event) =>
            _handleClick(query.searchQuery, onClick, event)}
          key={index}>
          {query.searchText}
        </Link>
      )}
    </Layout>
  </section>
export default RelatedSearch;

代碼量的減小主要來源兩個方面:

  • 沒有構造函數(5行)

  • 以Arrow Function的方式替代Render語句(4行)

實際上,SFC最迷人的地方不只僅是其代碼量的減小,還有就是對於可讀性的提升。SFC模式自己就是所謂純組件的一種最佳實踐範式,而移除了構造函數而且將_handleClick()這個點擊事件回調函數提取出組件外,可使JSX代碼變得更加純粹。另外一個不錯的地方就是SFC以Arrow Function的方式來定義了輸入的Props變量,即以Object Destructring語法來聲明組件所依賴的Props:

const RelatedSearch = ({ relatedQueries, onClick }) =>

這樣不只可以使組件的Props更加清晰明確,還可以避免冗餘的this.props表達式,從而使代碼的可讀性更好。

最後,我還想要強調下雖然我很推崇SFC,不過也不能濫用它。最合適使用SFC的地方就是以前你用純組件的地方。在Walmart Labs中,咱們使用Redux來管理應用的狀態,也就意味着咱們絕大部分的組件都是純組件,也就給了SFC廣闊的應用空間。通常來講,有如下特徵的組件式絕對不適合使用SFC的:

  • 須要自定義整個組件的生命週期管理

  • 須要使用到refs

Conditional Components

JSX自己不支持if表達式,不過咱們可使用邏輯表達式的方式來避免將代碼切分到不一樣的子模塊中,大概是以下樣子:

render() {
  <div class="search-results-container">
    {this.props.isGrid
      ? <SearchResultsGrid />
      : <SearchResultsList />}
  </div>
}

這種表達式在二選一渲染的時候頗有效果,不過對於選擇性渲染一個的狀況很不友好,譬如以下的狀況:

render() {
  <div class="search-results-list">
    {this.props.isSoftSort
      ? <SoftSortBanner />
      : null
    }
  </div>
}

這樣子確實能起做用,不過看上去感受怪怪的。咱們能夠選用另外一種更加語義化與友好的方式來實現這個功能,即便用邏輯與表達式而後返回組件:

render() {
  <div class="search-results-list">
    {!!this.props.isSoftSort && <SoftSortBanner />}
  </div>
}

不過這一點也是見仁見智,每一個人按照本身的喜愛來就好了。

Arrow Syntax In React And Redux

ES2015裏包含了很多可口的語法糖,我最愛的就是那個Arrow Notation。這個特性在編寫組件時頗有做用:

const SoftSort = ({ hardSortUrl, sortByName, onClick }) => {
  return (
    <div className="SearchInfoMessage">
      Showing results sorted by both Relevance and {sortByName}.
      <Link
        href={`?${hardSortUrl}`}
        onClick={(ev) => onClick(ev, hardSortUrl)}>
        Sort results by {sortByName} only
      </Link>
    </div>
  );
};

該函數的功能就是返回JSX對象,咱們也能夠忽略return語句:

const SoftSort = ({ hardSortUrl, sortByName, onClick }) =>
  <div className="SearchInfoMessage">
    Showing results sorted by both Relevance and {sortByName}.
    <Link
      href={`?${hardSortUrl}`}
      onClick={(ev) => onClick(ev, hardSortUrl)}>
      Sort results by {sortByName} only
    </Link>
  </div>

代碼行數又少了很多咯!

另外一塊我以爲很是適用Arrow Function的地方就是Redux的mapStateToProps函數:

const mapStateToProps = ({isLoading}) => {
  return ({
    loading: isLoading,
  });
};

須要注意的是,若是你返回的是Object,你須要包裹在大括號內:

const mapStateToProps = ({isLoading}) => ({
  loading: isLoading
});

使用Arrow Function優化的核心點在於其可以經過專一於函數的重要部分而提高代碼的總體可讀性,而且避免過多的模板代碼帶來的噪音。

合理使用Object Destructing與Spread Attributes

大的組件每每受困於this.props過長的窘境,典型的以下所示:

render() {
  return (
    <ProductPrice
      hidePriceFulfillmentDisplay=
       {this.props.hidePriceFulfillmentDisplay}
      primaryOffer={this.props.primaryOffer}
      productType={this.props.productType}
      productPageUrl={this.props.productPageUrl}
      inventory={this.props.inventory}
      submapType={this.props.submapType}
      ppu={this.props.ppu}
      isLoggedIn={this.props.isLoggedIn}
      gridView={this.props.isGridView}
    />
  );
}

這麼多的Props估計看着都頭疼,若是咱們要將這些Props繼續傳入下一層,大概就要變成下面這個樣子了:

render() {
  const {
    hidePriceFulfillmentDisplay,
    primaryOffer,
    productType,
    productPageUrl,
    inventory,
    submapType,
    ppu,
    isLoggedIn,
    gridView
  } = this.props;
  return (
    <ProductPrice
      hidePriceFulfillmentDisplay={hidePriceFulfillmentDisplay}
      primaryOffer={primaryOffer}
      productType={productType}
      productPageUrl={productPageUrl}
      inventory={inventory}
      submapType={submapType}
      ppu={ppu}
      isLoggedIn={isLoggedIn}
      gridView={isGridView}
    />
  );
}

暫時不考慮unKnown Props,咱們可使用解構賦值來實現這個功能:

render() {
  const props = this.props;
  return <ProductPrice {...props} />
}

Method Definition Shorthand

最後這個方法不必定多有用,不過仍是能讓你的代碼變得更加漂亮。若是你但願在Object中添加函數,你可使用ES2015 Method Definition Shorthand來代替傳統的ES5的表達式,譬如:

Link.defaultProps = {
  onClick(event) {
    event.preventDefault();
    Logger.log(event);
  }
};

若是你想設置一個默認的空方法,也能夠利用這種方式:

ProductRating.defaultProps = {
  onStarsClick() {}
};
相關文章
相關標籤/搜索