翻譯:劉小夕原文連接:https://dmitripavlutin.com/7-...javascript
本篇文章重點闡述可測試和富有意義。因水平有限,文中部分翻譯可能不夠準確,若是你有更好的想法,歡迎在評論區指出。java
儘管 組合
、複用
和 純組件
三個準則閱讀量不高,不過本着善始善終的原則,固然我我的始終仍是以爲此篇文章很是優質,仍是堅持翻譯完了。本篇是最後 可靠React組件設計
的最後一篇,但願對你的組件設計有所幫助。react
更多文章可戳: https://github.com/YvetteLau/...git
———————————————我是一條分割線————————————————github
若是你尚未閱讀過前幾個原則:服務器
通過測試的組件驗證了其在給定輸入的狀況下,輸出是否符合預期。可測試組件易於測試。網絡
如何確保組件按預期工做?你能夠不覺得然地說:「我經過手動驗證其正確性」。架構
若是你計劃手動驗證每一個組件的修改,那麼早晚,你會跳過這個繁瑣的環節,進而致使你的組件早晚會出現缺陷。app
這就是爲何自動化組件驗證很重要:進行單元測試。單元測試確保每次進行修改時,組件都能正常工做。ide
單元測試不只涉及早期錯誤檢測。 另外一個重要方面是可以驗證組件架構是否合理。
我發現如下幾點特別重要:
一個不可測試或難以測試的組件極可能設計得很糟糕。
組件很難測試每每是由於它有不少 props
、依賴項、須要原型和訪問全局變量,而這些都是設計糟糕的標誌。
當組件的架構設計很脆弱時,就會變得難以測試,而當組件難以測試的時候,你大概念會跳過編寫單元測試的過程,最終的結果就是:組件未測試。
總之,須要應用程序未經測試的緣由都是由於設計不當,即便你想測試這樣的應用,你也作不到。
咱們來測試一下 封裝章節的兩個版本的 <Controls>
組件。
import assert from 'assert'; import { shallow } from 'enzyme'; class Controls extends Component { render() { return ( <div className="controls"> <button onClick={() => this.updateNumber(+1)}> Increase </button> <button onClick={() => this.updateNumber(-1)}> Decrease </button> </div> ); } updateNumber(toAdd) { this.props.parent.setState(prevState => ({ number: prevState.number + toAdd })); } } class Temp extends Component { constructor(props) { super(props); this.state = { number: 0 }; } render() { return null; } } describe('<Controls />', function () { it('should update parent state', function () { const parent = shallow(<Temp />); const wrapper = shallow(<Controls parent={parent} />); assert(parent.state('number') === 0); wrapper.find('button').at(0).simulate('click'); assert(parent.state('number') === 1); wrapper.find('button').at(1).simulate('click'); assert(parent.state('number') === 0); }); });
咱們能夠看到 <Controls>
測試起來很複雜,由於它依賴父組件的實現細節。
測試時,須要一個額外的組件 <Temp>
,它模擬父組件,驗證 <Controls>
是否正確修改了父組件的狀態。
當 <Controls>
獨立於父組件的實現細節時,測試會變得更加容易。如今咱們來看看正確封裝的版本是如何測試的:
import assert from 'assert'; import { shallow } from 'enzyme'; import { spy } from 'sinon'; function Controls({ onIncrease, onDecrease }) { return ( <div className="controls"> <button onClick={onIncrease}>Increase</button> <button onClick={onDecrease}>Decrease</button> </div> ); } describe('<Controls />', function () { it('should execute callback on buttons click', function () { const increase = sinon.spy(); const descrease = sinon.spy(); const wrapper = shallow( <Controls onIncrease={increase} onDecrease={descrease} /> ); wrapper.find('button').at(0).simulate('click'); assert(increase.calledOnce); wrapper.find('button').at(1).simulate('click'); assert(descrease.calledOnce); }); });
良好封裝的組件,測試起來簡單明瞭,相反,沒有正確封裝的組件難以測試。
可測試性是肯定組件結構良好程度的實用標準。
很容易一個有意義的組件做用是什麼。
代碼的可讀性是很是重要的,你使用了多少次模糊代碼?你看到了字符串,可是不知道意思是什麼。
開發人員大部分時間都在閱讀和理解代碼,而不是實際編寫代碼。咱們花75%的時間理解代碼,花20%的時間修改現有代碼,只有5%編寫新的代碼。
在可讀性方面花費的額外時間能夠減小將來隊友和本身的理解時間。當應用程序增加時,命名實踐變得很重要,由於理解工做隨着代碼量的增長而增長。
閱讀有意義的代碼很容易。然而,想要編寫有意義的代碼,須要清晰的代碼實踐和不斷的努力來清楚地表達本身。
組件名是由一個或多個帕斯卡單詞(主要是名詞)串聯起來的,好比:<DatePicker>
、<GridItem>
、Application
、<Header>
。
組件越專業化,其名稱可能包含的單詞越多。
名爲 <HeaderMenu>
的組件表示頭部有一個菜單。 名稱 <SidebarMenuItem>
表示位於側欄中的菜單項。
當名稱有意義地暗示意圖時,組件易於理解。爲了實現這一點,一般你必須使用詳細的名稱。那很好:更詳細比不太清楚要好。
假設您導航一些項目文件並識別2個組件: <Authors>
和 <AuthorsList>
。 僅基於名稱,您可否得出它們之間的區別? 極可能不能。
爲了獲取詳細信息,你不得不打開 <Authors>
源文件並瀏覽代碼。字後,你知道 <Authors>
從服務器獲取做者列表並呈現 <AuthorsList>
組件。
更專業的名稱而不是 <Authors>
不會建立這種狀況。更好的名稱如:<FetchAuthors>
,<AuthorsContainer>
或 <AuthorsPage>
。
一個詞表明一個概念。例如,呈現項目概念的集合由列表單詞表示。
每一個概念對應一個單詞,而後在整個應用程序中保持關係一致。結果是可預測的單詞概念映射關係。
當許多單詞表示相同的概念時,可讀性會受到影響。例如,你定義一個呈現訂單列表組件爲:<OrdersList>
,定義另外一個呈現費用列表的組件爲: <ExpensesTable>
。
渲染項集合的相同概念由2個不一樣的單詞表示:list
和 table
。 對於相同的概念,沒有理由使用不一樣的詞。 它增長了混亂並打破了命名的一致性。
將組件命名爲 <OrdersList>
和 <ExpensesList>
(使用 list
)或 <OrdersTable>
和<ExpensesTable>
(使用 table
)。使用你以爲更好的詞,只要保持一致。
組件,方法和變量的有意義的名稱足以使代碼可讀。 所以,註釋大可能是多餘的。
常見的濫用註釋是對沒法識別和模糊命名的解釋。讓咱們看看這樣的例子:
// <Games> 渲染 games 列表 // "data" prop contains a list of game data function Games({ data }) { // display up to 10 first games const data1 = data.slice(0, 10); // Map data1 to <Game> component // "list" has an array of <Game> components const list = data1.map(function (v) { // "v" has game data return <Game key={v.id} name={v.name} />; }); return <ul>{list}</ul>; } <Games data=[{ id: 1, name: 'Mario' }, { id: 2, name: 'Doom' }] />
上例中的註釋澄清了模糊的代碼。 <Games>
,data
, data1
, v
,,數字 10
都是無心義的,難以理解。
若是重構組件,使用有意義的 props
名和變量名,那麼註釋就能夠被省略:
const GAMES_LIMIT = 10; function GamesList({ items }) { const itemsSlice = items.slice(0, GAMES_LIMIT); const games = itemsSlice.map(function (gameItem) { return <Game key={gameItem.id} name={gameItem.name} />; }); return <ul>{games}</ul>; } <GamesList items=[{ id: 1, name: 'Mario' }, { id: 2, name: 'Doom' }] />
不要使用註釋去解釋你的代碼,而是代碼即註釋。(小夕注:代碼即註釋不少人未必能作到,另外因團隊成員水平不一致,你們仍是應該編寫適當的註釋)
我將一個組件表現力分爲4個臺階。 組件在樓梯上的位置越低,意味着須要更多的努力才能理解。
你能夠經過如下幾種方式來了解組件的做用:
props
若是變量名和 props
提供了足夠的信息足以讓你理解這個組件的做用和使用方式,那就是一種超強的表達能力。 儘可能保持這種高質量水平。
有些組件具備複雜的邏輯,即便是好的命名也沒法提供必要的細節。那麼就須要閱讀文檔。
若是缺乏文檔或沒有文檔中沒有回答全部問題,則必須瀏覽代碼。因爲花費了額外的時間,這不是最佳選擇,但這是能夠接受的。
在瀏覽代碼也無助於理解組件時,下一步是向組件的做者詢問詳細信息。這絕對是錯誤的命名,並應該避免進入這一步。最好讓做者重構代碼,或者本身重構代碼。
重寫是寫做的本質。專業做家一遍又一遍地重寫他們的句子。
要生成高質量的文本,您必須屢次重寫句子。閱讀書面文字,簡化使人困惑的地方,使用更多的同義詞,刪除雜亂的單詞 - 而後重複,直到你有一段愉快的文字。
有趣的是,相同的重寫概念適用於設計組件。有時,在第一次嘗試時幾乎不可能建立正確的組件結構,由於:
組件越複雜,就越須要驗證和重構。
組件是否實現了單一職責,是否封裝良好,是否通過充分測試?若是您沒法回答某個確定,請肯定脆弱部分(經過與上述7個原則進行比較)並重構該組件。
實際上,開發是一個永不中止的過程,能夠審查之前的決策並進行改進。
組件的質量保證須要努力和按期審查。這個投資是值得的,由於正確的組件是精心設計的系統的基礎。這種系統易於維護和增加,其複雜性線性增長。
所以,在任何項目階段,開發都相對方便。
另外一方面,隨着系統大小的增長,你可能忘記計劃並按期校訂結構,減小耦合。僅僅是是實現功能。
可是,在系統變得足夠緊密耦合的不可避免的時刻,知足新要求變得呈指數級複雜化。你沒法控制代碼,系統的脆弱反而控制了你。錯誤修復會產生新的錯誤,代碼更新須要一系列相關的修改。
悲傷的故事要怎麼結束?你可能會拋棄當前系統並從頭開始重寫代碼,或者極可能繼續吃仙人掌。我吃了不少仙人掌,你可能也是,這不是最好的感受。
解決方案簡單但要求苛刻:編寫可靠的組件。
前文所說的7個準則從不用的角度闡述了同一個思想:
可靠的組件實現一個職責,隱藏其內部結構並提供有效的
props
來控制其行爲。
單一職責和封裝是 solid
設計的基礎。(maybe你須要瞭解一下 solid 原則是什麼。)
單一職責建議建立一個只實現一個職責的組件,並有一個改變的理由。
良好封裝的組件隱藏其內部結構和實現細節,並定義 props
來控制行爲和輸出。
組合結構大的組件。只需將它們分紅較小的塊,而後使用組合進行整合,使複雜組件變得簡單。
可複用的組件是精心設計的系統的結果。儘量重複使用代碼以免重複。
網絡請求或全局變量等反作用使組件依賴於環境。經過爲相同的 prop
值返回相同的輸出來使它們變得純淨。
有意義的組件命名和表達性代碼是可讀性的關鍵。你的代碼必須易於理解和閱讀。
測試不只是一種自動檢測錯誤的方式。若是你發現某個組件難以測試,則極可能是設計不正確。
成功的應用站在可靠的組件的肩膀上,所以編寫可靠、可擴展和可維護的組件很是中重要。
在編寫React組件時,你認爲哪些原則有用?
最後謝謝各位小夥伴願意花費寶貴的時間閱讀本文,若是本文給了您一點幫助或者是啓發,請不要吝嗇你的贊和Star,您的確定是我前進的最大動力。https://github.com/YvetteLau/...
推薦關注本人公衆號