整潔代碼 or 糟糕代碼: 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