React 整潔代碼最佳實踐

原文:Clean Code vs. Dirty Code: React Best Practicesgit

做者:Donavon West程序員

本文主要介紹了適用於現代 React 軟件開發的整潔代碼實踐,順便談談 ES6/ES2015 帶來的一些好用的「語法糖」。github

什麼是整潔代碼,爲何要在意?

整潔代碼表明的是一種一致的編碼風格,目的是讓代碼更易於編寫,閱讀和維護。一般狀況下,開發者在解決問題的時候,一旦問題解決就發起一個 Pull Request(譯註:合併請求,在 Gitlab 上叫 Merge Request)。但我認爲,這時候工做並無真正完成,咱們不能僅僅知足於代碼能夠工做。算法

這時候其實就是整理代碼的最好時機,能夠經過刪除死代碼(殭屍代碼),重構以及刪除註釋掉的代碼,來保持代碼的可維護性。不妨問問本身,「從如今開始再過六個月,其餘人還能理解這些代碼嗎?」簡而言之,對於本身編寫的代碼,你應該保證能很自豪地拿給別人看。express

至於爲何要在意這點?由於咱們常說一個優秀的開發者大都比較」懶「。在遇到須要重複作某些事情的狀況下,他們會去找到一個自動化(或更好的)解決方案來完成這些任務。設計模式

整潔代碼可以經過「味道測試」

整潔代碼應該能夠經過「味道測試」。什麼意思呢?咱們在看代碼的時候,包括咱們本身寫的或或是別人的,會說:「這裏不太對勁。」若是感受不對,那可能就真的是有問題的。若是你以爲你正在試圖把一個方形釘子裝進一個圓形的洞裏,那麼就暫停一下,而後休息一下。屢次嘗試以後,你會找到一個更好的解決方案。數組

整潔代碼是符合 DRY 原則的

DRY 是一個縮略詞,意思是「不要重複本身」(Don’t Repeat Yourself)。若是發現多個地方在作一樣的事情,那麼這時候就應該合併重複代碼。若是在代碼中看到了模式,那麼代表須要實行 DRY。app

// Dirty
const MyComponent = () => (
  <div>
    <OtherComponent type="a" className="colorful" foo={123} bar={456} />
    <OtherComponent type="b" className="colorful" foo={123} bar={456} />    
  </div>
);
// Clean
const MyOtherComponent = ({ type }) => (
  <OtherComponent type={type} className="colorful" foo={123} bar={456} />
);
const MyComponent = () => (
  <div>
    <MyOtherComponent type="a" />
    <MyOtherComponent type="b" />
  </div>
);

有時候,好比在上面的例子中,實行 DRY 原則反而可能會增長代碼量。可是,DRY 一般也可以提升代碼的可維護性。函數

注意,很容易陷入過度使用 DRY 原則的陷阱,應該學會適可而止。單元測試

整潔代碼是可預測和可測試的

編寫單元測試不只僅只是一個好想法,並且應該是強制性的。否則,怎麼能確保新功能不會在其餘地方引發 Bug 呢?

許多 React 開發人員選擇 Jest 做爲一個零配置測試運行器,而後生成代碼覆蓋率報告。若是對測試先後對比可視化感興趣,請查看美國運通的 Jest Image snanshot

整潔代碼是自注釋的

之前發生過這種狀況嗎?你寫了一些代碼,而且包含詳細的註釋。後來你發現一個 bug,因而回去修改代碼。可是,你有沒有改變註釋來體現新的邏輯?也許會,也許不會。下一個看你代碼的人可能由於注意到這些註釋而掉進一個陷阱。

註釋只是爲了解釋複雜的想法,也就是說,不要對顯而易見的代碼進行註釋。同時,更少的註釋也減小了視覺上的干擾。

// Dirty
const fetchUser = (id) => (
  fetch(buildUri`/users/${id}`) // Get User DTO record from REST API
    .then(convertFormat) // Convert to snakeCase
    .then(validateUser) // Make sure the the user is valid
);

在整潔代碼的版本中,咱們對一些函數進行重命名,以便更好地描述它們的功能,從而消除註釋的必要性,減小視覺干擾。而且避免後續因代碼與註釋不匹配致使的混淆。

// Clean
const fetchUser = (id) => (
  fetch(buildUri`/users/${id}`)
    .then(snakeToCamelCase)
    .then(validateUser)
);

命名

在我以前的文章 將函數做爲子組件是一種反模式,強調了命名的重要性。每一個開發者都應該認真考慮變量名,函數名,甚至是文件名。

這裏列舉一下命名原則:

  • 布爾變量或返回布爾值的函數應該以「is」,「has」或「should」開頭。

    // Dirty
    const done = current >= goal;
    // Clean
    const isComplete = current >= goal;
  • 函數命名應該體現作了什麼,而不是是怎樣作的。換言之,不要在命名中體現出實現細節。假若有天出現變化,就不須要所以而重構引用該函數的代碼。好比,今天可能會從 REST API 加載配置,可是可能明天就會將其直接寫入到 JavaScript 中。

    // Dirty
    const loadConfigFromServer = () => {
      ...
    };
    // Clean
    const loadConfig = () => {
      ...
    };

整潔代碼遵循成熟的設計模式和最佳實踐

計算機已經存在很長一段時間了。多年以來,程序員經過解決某些特定問題,發現了一些固有套路,被稱爲設計模式。換言之,有些算法已經被證實是能夠工做的,因此應該站在前人的肩膀上,避免犯一樣的錯誤。

那麼,什麼是最佳實踐,與設計模式相似,可是適用範圍更廣,不只僅針對編碼算法。好比,「應該對代碼進行靜態檢查」或者「當編寫一個庫時,應該將 React 做爲 peerDependency」,這些均可以稱爲最佳實踐。

構建 React 應用程序時,應該遵循如下最佳實踐:

  • 使用小函數,每一個函數具有單一功能,即所謂的單一職責原則(Single responsibility principle)。確保每一個函數都能完成一項工做,並作得很好。這樣就能將複雜的組件分解成許多較小的組件。同時,將具有更好的可測試性。
  • 當心抽象泄露(leaky abstractions)。換言之,不要強迫消費方去了解內部代碼實現細節。
  • 遵循嚴格的代碼檢查規則。這將有助於編寫整潔,一致的代碼。

整潔代碼不須要花長時間來編寫

總會聽到這樣的說法:編寫整潔代碼會下降生產力。簡直是在胡說八道。是的,可能剛開始須要放慢速度,但最終會隨着編寫更少的代碼而節奏加快。

並且,不要小看代碼評審致使的重寫重構,以及修復問題花費的時間。若是把代碼分解成小的模塊,每一個模塊都是單一職責,那麼極可能之後不再用去碰大多數模塊了。時間就省下來了,也就是說 「write it and forget it」。

槽糕代碼與整潔代碼的實例

使用 DRY 原則

看看下面的代碼示例。如上所述,從你的顯示器退後一步,發現什麼模式了嗎?注意 Thingie 組件與 ThingieWithTitle 組件除了 Title 組件幾乎徹底相同,這是實行 DRY 原則的最佳情形。

// Dirty
import Title from './Title';

export const Thingie = ({ description }) => (
  <div class="thingie">
    <div class="description-wrapper">
      <Description value={description} />
    </div>
  </div>
);

export const ThingieWithTitle = ({ title, description }) => (
  <div>
    <Title value={title} />
    <div class="description-wrapper">
      <Description value={description} />
    </div>
  </div>
);

在這裏,咱們將 children 傳遞給 Thingie。而後建立 ThingieWithTitle,這個組件包含 Thingie,並將 Title 做爲其子組件傳給 Thingie

// Clean
import Title from './Title';

export const Thingie = ({ description, children }) => (
  <div class="thingie">
    {children}
    <div class="description-wrapper">
      <Description value={description} />
    </div>
  </div>
);

export const ThingieWithTitle = ({ title, ...others }) => (
  <Thingie {...others}>
    <Title value={title} />
  </Thingie>
);

默認值

看看下面的代碼。使用邏輯或將 className 的默認值設置成 「icon-large」,看起來像是上個世紀的人才會寫的代碼。

// Dirty
const Icon = ({ className, onClick }) => {
  const additionalClasses = className || 'icon-large';
  
  return (
    <span
      className={`icon-hover ${additionalClasses}`}
      onClick={onClick}>
    </span>
  );
};

這裏咱們使用 ES6 的默認語法來替換 undefined 時的值,並且還能使用 ES6 的箭頭函數表達式寫成單一語句形式,從而去除對 return 的依賴。

// Clean
const Icon = ({ className = 'icon-large', onClick }) => (
  <span className={`icon-hover ${className}`} onClick={onClick} />
);

在下面這個更整潔的版本中,使用 React 中的 API 來設置默認值。

// Cleaner
const Icon = ({ className, onClick }) => (
  <span className={`icon-hover ${className}`} onClick={onClick} />
);

Icon.defaultProps = {
  className: 'icon-large',
};

爲何這樣顯得更加整潔?並且它真的會更好嗎?三個版本不是都在作一樣的事情嗎?某種意義上來講,是對的。讓 React 設置 prop 默認值的好處是,能夠產生更高效的代碼,並且在基於 Class 的生命週期組件中容許經過 propTypes 檢查默認值。還有一個優勢是:將默認邏輯從組件自己抽離出來。

例如,你能夠執行如下操做,將全部默認屬性放到一個地方。固然,並非建議你這樣作,只是說具備這樣的靈活性。

import defaultProps from './defaultProps';
// ...
Icon.defaultProps = defaultProps.Icon;

從渲染分離有狀態的部分

將有狀態的數據加載邏輯與渲染邏輯混合可能增長組件複雜性。更好的方式是,寫一個負責完成數據加載的有狀態的容器組件,而後編寫另外一個負責顯示數據的組件。這被稱爲 容器模式

在下面的示例中,用戶數據加載和顯示功能放在一個組件中。

// Dirty
class User extends Component {
  state = { loading: true };

  render() {
    const { loading, user } = this.state;
    return loading
      ? <div>Loading...</div>
      : <div>
          <div>
            First name: {user.firstName}
          </div>
          <div>
            First name: {user.lastName}
          </div>
          ...
        </div>;
  }

  componentDidMount() {
    fetchUser(this.props.id)
      .then((user) => { this.setState({ loading: false, user })})
  }
}

在整潔版本中,加載數據和顯示數據已經分離。這不只使代碼更容易理解,並且能減小測試的工做量,由於能夠獨立測試每一個部分。並且因爲 RenderUser 是一個無狀態組件,因此結果是可預測的。

// Clean
import RenderUser from './RenderUser';

class User extends Component {
  state = { loading: true };

  render() {
    const { loading, user } = this.state;
    return loading ? <Loading /> : <RenderUser user={user} />;
  }

  componentDidMount() {
    fetchUser(this.props.id)
      .then(user => { this.setState({ loading: false, user })})
  }
}

使用無狀態組件

React v0.14.0 中引入了無狀態函數組件(SFC),被簡化成純渲染組件,但有些開發者還在使用過去的方式。例如,如下組件就應該轉換爲 SFC。

// Dirty
class TableRowWrapper extends Component {
  render() {
    return (
      <tr>
        {this.props.children}
      </tr>
    );
  }
}

整潔版本清除了不少可能致使干擾的信息。經過 React 核心的優化,使用無狀態組件將佔用更少的內存,由於沒有建立 Component 實例。

// Clean
const TableRowWrapper = ({ children }) => (
  <tr>
    {children}
  </tr>
);

剩餘/擴展屬性(rest/spread)

大約在一年前,我還推薦你們多用 Object.assign。但時代變化很快,在 ES2016/ES7 中引入新特性 rest/spread

好比這樣一種場景,當傳遞給一些 props 給一個組件,只但願在組件自己使用 className,可是須要將其餘全部 props 傳遞到子組件。這時,你可能會這樣作:

// Dirty
const MyComponent = (props) => {
  const others = Object.assign({}, props);
  delete others.className;
  
  return (
    <div className={props.className}>
      {React.createElement(MyOtherComponent, others)}
    </div>
  );
};

這不是一個很是優雅的解決方案。可是使用 rest/spread,就能垂手可得地實現,

// Clean
const MyComponent = ({ className, ...others }) => (
  <div className={className}>
    <MyOtherComponent {...others} />
  </div>
);

咱們將剩餘屬性展開並做爲新的 props 傳遞給 MyOtherComponent 組件。

合理使用解構

ES6 引入 解構(destructuring) 的概念,這是一個很是棒的特性,用相似對象或數組字面量的語法獲取一個對象的屬性或一個數組的元素。

對象解構

在這個例子中,componentWillReceiveProps 組件接收 newProps 參數,而後將其 active 屬性設置爲新的 state.active

// Dirty
componentWillReceiveProps(newProps) {
  this.setState({
    active: newProps.active
  });
}

在整潔版本中,咱們解構 newProps active。這樣咱們不只不須要引用 newProps.active,並且也可使用 ES6 的簡短屬性特性來調用 setState

// Clean
componentWillReceiveProps({ active }) {
  this.setState({ active });
}

數組解構

一個常常被忽視的 ES6 特性是數組解構。如下面的代碼爲例,它獲取 locale 的值,好比「en-US」,並將其分紅 language(en)和 country(US)。

// Dirty
const splitLocale = locale.split('-');
const language = splitLocale[0];
const country = splitLocale[1];

在整潔版本,使用 ES6 的數組解構特性能夠自動完成上述過程:

// Clean
const [language, country] = locale.split('-');

因此結論是

但願這篇文章能有助於你看到編寫整潔代碼的好處,甚至能夠直接使用這裏介紹的一些代碼示例。一旦你習慣編寫整潔代碼,將很快就會體會到 「write it and forget it」 的生活方式。

相關文章
相關標籤/搜索