![]() |
VOOZH | about |
原文:Clean Code vs. Dirty Code: React Best Practices
作者:Donavon West
本文主要介紹了適用於現代 React 軟體開發的整潔代碼實踐,順便談談 ES6/ES2015 帶來的一些好用的「語法糖」。
什麼是整潔代碼,為什麼要在乎?整潔代碼代表的是一種一致的編碼風格,目的是讓代碼更易於編寫,閱讀和維護。通常情況下,開發者在解決問題的時候,一旦問題解決就發起一個 Pull Request(譯註:合併請求,在 Gitlab 上叫 Merge Request)。但我認為,這時候工作並沒有真正完成,我們不能僅僅滿足於代碼可以工作。
這時候其實就是整理代碼的最好時機,可以通過刪除死代碼(殭屍代碼),重構以及刪除注釋掉的代碼,來保持代碼的可維護性。不妨問問自己,「從現在開始再過六個月,其他人還能理解這些代碼嗎?」簡而言之,對於自己編寫的代碼,你應該保證能很自豪地拿給別人看。
至於為什麼要在乎這點?因為我們常說一個優秀的開發者大都比較」懶「。在遇到需要重複做某些事情的情況下,他們會去找到一個自動化(或更好的)解決方案來完成這些任務。
整潔代碼能夠通過「味道測試」整潔代碼應該可以通過「味道測試」。什麼意思呢?我們在看代碼的時候,包括我們自己寫的或或是別人的,會說:「這裡不太對勁。」如果感覺不對,那可能就真的是有問題的。如果你覺得你正在試圖把一個方形釘子裝進一個圓形的洞裡,那麼就暫停一下,然後休息一下。多次嘗試之後,你會找到一個更好的解決方案。
整潔代碼是符合 DRY 原則的DRY 是一個縮略詞,意思是「不要重複自己」(Don’t Repeat Yourself)。如果發現多個地方在做同樣的事情,那麼這時候就應該合併重複代碼。如果在代碼中看到了模式,那麼表明需要實行 DRY。
//
DirtyconstMyComponent==>(<div<OtherComponenttype="a"className="colorful"foo={123}bar={456}/><OtherComponenttype="b"className="colorful"foo={123}bar={456}/></div>);//
CleanconstMyOtherComponent=({type})=>(<OtherComponenttype={type}className="colorful"foo={123}bar={456}/>);constMyComponent==>(<div<MyOtherComponenttype="a"/><MyOtherComponenttype="b"/></div>);有時候,比如在上面的例子中,實行 DRY 原則反而可能會增加代碼量。但是,DRY 通常也能夠提高代碼的可維護性。
注意,很容易陷入過分使用 DRY 原則的陷阱,應該學會適可而止。
整潔代碼是可預測和可測試的編寫單元測試不僅僅只是一個好想法,而且應該是強制性的。不然,怎麼能確保新功能不會在其他地方引起 Bug 呢?
許多 React 開發人員選擇 作為一個零配置測試運行器,然後生成代碼覆蓋率報告。如果對測試前後對比可視化感興趣,請查看美國運通的 Jest Image snanshot。
整潔代碼是自注釋的以前發生過這種情況嗎?你寫了一些代碼,並且包含詳細的注釋。後來你發現一個 bug,於是回去修改代碼。但是,你有沒有改變注釋來體現新的邏輯?也許會,也許不會。下一個看你代碼的人可能因為注意到這些注釋而掉進一個陷阱。
注釋只是為了解釋複雜的想法,也就是說,不要對顯而易見的代碼進行注釋。同時,更少的注釋也減少了視覺上的干擾。
//
DirtyconstfetchUser=(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);在整潔代碼的版本中,我們對一些函數進行重命名,以便更好地描述它們的功能,從而消除注釋的必要性,減少視覺干擾。並且避免後續因代碼與注釋不匹配導致的混淆。
//
CleanconstfetchUser=(id)=>(fetch(buildUri`/users/${id}`).then(snakeToCamelCase).then(validateUser));在我之前的文章 將函數作為子組件是一種反模式,強調了命名的重要性。每個開發者都應該認真考慮變量名,函數名,甚至是文件名。
這裡列舉一下命名原則:
// Dirtyconstdone=current>=goal;//
CleanconstisComplete=current>=goal;// DirtyconstloadConfigFromServer==>{...};//
CleanconstloadConfig==>{...};計算機已經存在很長一段時間了。多年以來,程式設計師通過解決某些特定問題,發現了一些固有套路,被稱為設計模式。換言之,有些算法已經被證明是可以工作的,所以應該站在前人的肩膀上,避免犯同樣的錯誤。
那麼,什麼是最佳實踐,與設計模式類似,但是適用範圍更廣,不僅僅針對編碼算法。比如,「應該對代碼進行靜態檢查」或者「當編寫一個庫時,應該將 React 作為peerDependency」,這些都可以稱為最佳實踐。
構建 React 應用程式時,應該遵循以下最佳實踐:
總會聽到這樣的說法:編寫整潔代碼會降低生產力。簡直是在胡說八道。是的,可能剛開始需要放慢速度,但最終會隨著編寫更少的代碼而節奏加快。
而且,不要小看代碼評審導致的重寫重構,以及修復問題花費的時間。如果把代碼分解成小的模塊,每個模塊都是單一職責,那麼很可能以後再也不用去碰大多數模塊了。時間就省下來了,也就是說 「write it and forget it」。
槽糕代碼與整潔代碼的實例使用 DRY 原則看看下面的代碼示例。如上所述,從你的顯示器退後一步,發現什麼模式了嗎?注意Thingie組件與ThingieWithTitle組件除了組件幾乎完全相同,這是實行 DRY
原則的最佳情形。在這裡,我們將children傳遞給Thingie。然後創建,這個組件包含Thingie,並將Title作為其子組件傳給Thingie。
//
CleanimportTitlefrom'./Title';exportconstThingie=({descriptionchildren})=>(<divclass="thingie"{children}<divclass="description-wrapper"<Descriptionvalue={description}/></div></div>);exportconstThingieWithTitle=({title...others})=>(<Thingie{...others}<Titlevalue={title}/></Thingie>);className的默認值設置成 「icon-large」,看起來像是上個世紀的人才會寫的代碼。
//
DirtyconstIcon=({classNameonClick})=>{constadditionalClasses=className||'icon-large';return(<spanclassName={`icon-hover
${additionalClasses}`}onClick={onClick}</span>);};undefined時的值,而且還能使用 ES6 的箭頭函數表達式寫成單一語句形式,從而去除對return的依賴。
//
CleanconstIcon=({className='icon-large'onClick})=>(<spanclassName={`icon-hover
${className}`}onClick={onClick}/>);在下面這個更整潔的版本中,使用 React 中的 API 來設置默認值。
//
CleanerconstIcon=({classNameonClick})=>(<spanclassName={`icon-hover
${className}`}onClick={onClick}/>);Icon.defaultProps={className:'icon-large'};Class的生命周期組件中允許通過propTypes檢查默認值。還有一個優點是:將默認邏輯從組件本身抽離出來。
例如,你可以執行以下操作,將所有默認屬性放到一個地方。當然,並不是建議你這樣做,只是說具有這樣的靈活性。
importdefaultPropsfrom'./defaultProps';//
...Icon.defaultProps=defaultProps.Icon;將有狀態的數據加載邏輯與渲染邏輯混合可能增加組件複雜性。更好的方式是,寫一個負責完成數據加載的有狀態的容器組件,然後編寫另一個負責顯示數據的組件。這被稱為 容器模式。
在下面的示例中,用戶數據加載和顯示功能放在一個組件中。
//
DirtyclassUserextendsComponent{state={loading:true};render{const{loadinguser}=this.state;returnloading?<divLoading...</div>:<div<divFirstname:{user.firstName}</div><divFirstname:{user.lastName}</div>...</div>;}componentDidMount{fetchUser(this.props.id).then((user)=>{this.setState({loading:falseuser})})}}RenderUser是一個無狀態組件,所以結果是可預測的。
//
CleanimportRenderUserfrom'./RenderUser';classUserextendsComponent{state={loading:true};render{const{loadinguser}=this.state;returnloading?<Loading/>:<RenderUseruser={user}/>;}componentDidMount{fetchUser(this.props.id).then(user=>{this.setState({loading:falseuser})})}}React v0.14.0 中引入了無狀態函數組件(SFC),被簡化成純渲染組件,但有些開發者還在使用過去的方式。例如,以下組件就應該轉換為 SFC。
//
DirtyclassTableRowWrapperextendsComponent{render{return(<tr{this.props.children}</tr>);}}整潔版本清除了很多可能導致干擾的信息。通過 React 核心的優化,使用無狀態組件將占用更少的內存,因為沒有創建 Component 實例。
//
CleanconstTableRowWrapper=({children})=>(<tr{children}</tr>);className,但是需要將其他所有 props 傳遞到子組件。這時,你可能會這樣做:
//
DirtyconstMyComponent=(props)=>{constothers=Object.assign({},props);deleteothers.className;return(<divclassName={props.className}{React.createElement(MyOtherComponentothers)}</div>);};這不是一個非常優雅的解決方案。但是使用 rest/spread,就能輕而易舉地實現,
//
CleanconstMyComponent=({className...others})=>(<divclassName={className}<MyOtherComponent{...others}/></div>);MyOtherComponent組件。合理使用解構
ES6 引入 解構(destructuring) 的概念,這是一個非常棒的特性,用類似對象或數組字面量的語法獲取一個對象的屬性或一個數組的元素。
對象解構
在這個例子中,componentWillReceiveProps組件接收參數,然後將其active屬性設置為新的state.active在整潔版本中,我們解構newProps成active。這樣我們不僅不需要引用newProps.active,而且也可以使用
ES6 的簡短屬性特性來調用setState。
//
CleancomponentWillReceiveProps({active}){this.setState({active});}數組解構
一個經常被忽視的 ES6 特性是數組解構。以下面的代碼為例,它獲取locale的值,比如「en-US」,並將其分成language(en)和country(US)。
//
DirtyconstsplitLocale=locale.split('-');constlanguage=splitLocale[0];constcountry=splitLocale[1];在整潔版本,使用 ES6 的數組解構特性可以自動完成上述過程:
//
Cleanconst[languagecountry]=locale.split('-');希望這篇文章能有助於你看到編寫整潔代碼的好處,甚至可以直接使用這裡介紹的一些代碼示例。一旦你習慣編寫整潔代碼,將很快就會體會到 「write it and forget it」 的生活方式。