整潔代碼 or 糟糕代碼: React 最佳實踐

344236782 7年前發布 | 32K 次閱讀 React

本文將重點介紹適用于現代 React 軟件開發的整潔代碼實踐。我還會談談 ES6/ES2015 帶到臺面上的一些“糖”。

什么是整潔代碼?為什么我在乎?

整潔代碼是一種一致的編程風格,使你的代碼更易于編寫,閱讀和維護。開發人員通常花費時間解決問題,一旦問題解決,他們就會提出 pull 請求。我認為你不僅僅因為代碼“工作”就完事。

現在是去清理代碼的好時機,通過刪除死代碼(僵尸代碼)、重構和刪除任何注釋掉的代碼! 為了可維護性而努力。問問自己:“從現在開始六個月后,其他人能夠理解這段代碼嗎?”

簡單地說,寫你會很自豪地帶回家并展示給母親看的代碼。

你為什么在乎?因為如果你是一個優秀的開發人員,那么你很懶。請聽我解釋 – 那是一句褒揚的話。一個優秀的開發人員,在遇到不止一次需要做某事的情況下,通常會找到一個自動化(或更好的)解決方案來完成手頭的任務。所以,因為你很懶,認同整潔代碼的技巧將會減少來自 pull 請求代碼評審的變化頻率,以及減少一遍又一遍地回到同一段代碼的頻率。

整潔代碼通過“氣味測試”

整潔的代碼應該通過氣味測試。這是什么意思?我們都看過代碼(我們自己的或別人的)并說:“這里不太對勁。” 記住,如果覺得它不對勁,那很可能就是不對勁。好的代碼都是深思后一起來的。如果你覺得你正在試圖把一個方形的釘子釘進一個圓形的洞里,那么就暫停一下,后退一步,然后休息一下。在多次嘗試后,你會想出一個更好的解決方案。

整潔的代碼是 DRY (不要重復自己)

DRY 是代表“不要重復自己”的首字母縮略詞。如果你在多個地方做同樣的事情,請合并重復的代碼。如果你在你的代碼中看到了模式(復寫),那是表示應該進行 DRY 的重要跡象。有時這意味著從顯示屏退后,直到你無法看清文本,然后從字面上尋找模式。

// 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 的 zero-configuration 測試運行器,并生成代碼覆蓋率報告。如果你對視覺上的前后對比測試感興趣,請查看美國運通公司自有的 Jest Image Snapshot

整潔的代碼是自我注釋的

你曾遇過這種情況嗎?你寫了些代碼而且確保它有充分的注釋。后來你發現了一個 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 = () => {
      ...
    };

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

計算機已經存在了很長的一段時間。多年來,程序員通過解決某些問題,發現了模式。并稱之為設計模式。換句話說,有些算法已經被證明是可以工作的,所以你應該站在那些在你之前的人的肩膀上,這樣你就不必犯同樣的錯誤了。

之后有了最佳實踐。它們與設計模式類似,但是更廣泛,它們不是針對編碼算法。他們可能是諸如“你應該用 Lint 優化你的代碼” 或者 “當編寫一個庫包時,包含 React 作為一個 peerDependency” 這些做法。

構建 React 應用程序時,請遵循以下最佳實踐。

  • 使用函數,每個函數都有一個功能。 這就是所謂的單一責任原則。 確保每個功能都能完成一項工作,并做得很好。 這可能意味著將復雜的組件分解成許多較小的組件。 這也能使得測試更容易。

  • 留心觀察脆弱的抽象。換句話說,就是不要將您的內部需求強加給您的代碼使用者。

  • 遵循嚴格的 Lint 化規則。這將幫助您編寫整潔,一致的代碼。

整潔代碼不(一定)要花更長的時間去寫

我總是聽到這樣的說法:編寫整潔的代碼會降低生產力。這簡直是胡扯。是的,最初你可能需要放慢速度然后才能加速,但是最終你的步伐會隨著你寫更少的代碼而加快。

不要低估“重寫因子”以及修正來自代碼評審的意見所花的時間。如果你把代碼分成幾個小模塊,每個模塊都有一個單獨的職責,那么很有可能你再也不需要碰觸大多數模塊了。在“寫代碼然后忘記它”上你會節約時間。

臟代碼與整潔代碼實例

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 的時候,ThingieWithTitle 包裝了 Thingie ,把 Title 當做了 children。

// 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” ,使用邏輯或(OR)語句,類似于你祖父可能做過的方式。

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

這里我們使用 ES6 默認語法來替代空字符串表示的未定義值。這允許我們使用 ES6 中的 fat-arrow 函數的單語句,這就消除了對 return 語句的需求。

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

在這個更簡潔的版本中,React 是自動設置默認值的。

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

為什么是這個清理器呢?并且它真的很好嘛?難道這三個版本不是做類似的事情嗎?對大多數情況而言,答案是肯定的。React 支持設置 prop 默認值的優勢是這樣可以產生更高效的代碼,在基于生命周期組件的類中設置默認 prop 的值,同時支持你的默認值通過 propType 檢查。但這還有另一個優勢:它把默認值邏輯從組件自身中分離出來。

例如,你可以這么做,將你所有的默認 prop 保存到一個地方。我并不是推薦你這樣做;我只是說你可以有這么做的變通選擇。

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 })})
  }
}

使用無狀態函數組件

無狀態函數組件 (SFCs) 是在 React v0.14.0 引入的,用來簡化僅渲染組件編寫。有些開發者思維還沒有轉過來,例如下面組件是可以很容易的轉化為一個 SFC 組件的:

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

該例子的簡潔版本去掉了很多糟糕版本的無用代碼。由于沒有創建實例,通過 React 內核優化,可以節省很多內存:

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

 

來自:https://www.oschina.net/translate/clean-code-dirty-code

 

 本文由用戶 344236782 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!