可靠React組件設計的7個準則之終篇

翻譯:劉小夕

原文連接:https://dmitripavlutin.com/7-...javascript

本篇文章重點闡述可測試和富有意義。因水平有限,文中部分翻譯可能不夠準確,若是你有更好的想法,歡迎在評論區指出。java

儘管 組合複用純組件 三個準則閱讀量不高,不過本着善始善終的原則,固然我我的始終仍是以爲此篇文章很是優質,仍是堅持翻譯完了。本篇是最後 可靠React組件設計 的最後一篇,但願對你的組件設計有所幫助。react

更多文章可戳: https://github.com/YvetteLau/...git

———————————————我是一條分割線————————————————github

若是你尚未閱讀過前幾個原則:服務器

可測試和通過測試

通過測試的組件驗證了其在給定輸入的狀況下,輸出是否符合預期。

可測試組件易於測試。網絡

如何確保組件按預期工做?你能夠不覺得然地說:「我經過手動驗證其正確性」。架構

若是你計劃手動驗證每一個組件的修改,那麼早晚,你會跳過這個繁瑣的環節,進而致使你的組件早晚會出現缺陷。app

這就是爲何自動化組件驗證很重要:進行單元測試。單元測試確保每次進行修改時,組件都能正常工做。ide

單元測試不只涉及早期錯誤檢測。 另外一個重要方面是可以驗證組件架構是否合理。

我發現如下幾點特別重要:

一個不可測試或難以測試的組件極可能設計得很糟糕。

組件很難測試每每是由於它有不少 props、依賴項、須要原型和訪問全局變量,而這些都是設計糟糕的標誌。

當組件的架構設計很脆弱時,就會變得難以測試,而當組件難以測試的時候,你大概念會跳過編寫單元測試的過程,最終的結果就是:組件未測試。

clipboard.png

總之,須要應用程序未經測試的緣由都是由於設計不當,即便你想測試這樣的應用,你也作不到。

案例學習:可測試意味着良好的設計

咱們來測試一下 封裝章節的兩個版本的 <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個不一樣的單詞表示:listtable。 對於相同的概念,沒有理由使用不一樣的詞。 它增長了混亂並打破了命名的一致性。

將組件命名爲 <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個臺階。 組件在樓梯上的位置越低,意味着須要更多的努力才能理解。

clipboard.png

你能夠經過如下幾種方式來了解組件的做用:
  • 讀取變量名 和 props
  • 閱讀文檔/註釋
  • 瀏覽代碼
  • 諮詢做者

若是變量名和 props 提供了足夠的信息足以讓你理解這個組件的做用和使用方式,那就是一種超強的表達能力。 儘可能保持這種高質量水平。

有些組件具備複雜的邏輯,即便是好的命名也沒法提供必要的細節。那麼就須要閱讀文檔。

若是缺乏文檔或沒有文檔中沒有回答全部問題,則必須瀏覽代碼。因爲花費了額外的時間,這不是最佳選擇,但這是能夠接受的。

在瀏覽代碼也無助於理解組件時,下一步是向組件的做者詢問詳細信息。這絕對是錯誤的命名,並應該避免進入這一步。最好讓做者重構代碼,或者本身重構代碼。

持續改進

重寫是寫做的本質。專業做家一遍又一遍地重寫他們的句子。

要生成高質量的文本,您必須屢次重寫句子。閱讀書面文字,簡化使人困惑的地方,使用更多的同義詞,刪除雜亂的單詞 - 而後重複,直到你有一段愉快的文字。

有趣的是,相同的重寫概念適用於設計組件。有時,在第一次嘗試時幾乎不可能建立正確的組件結構,由於:

  • 緊迫的項目排期不容許在系統設計上花費足夠的時間
  • 最初選擇的方法是錯誤的
  • 剛剛找到了一個能夠更好地解決問題的開源庫
  • 或任何其餘緣由。

組件越複雜,就越須要驗證和重構。

clipboard.png

組件是否實現了單一職責,是否封裝良好,是否通過充分測試?若是您沒法回答某個確定,請肯定脆弱部分(經過與上述7個原則進行比較)並重構該組件。

實際上,開發是一個永不中止的過程,能夠審查之前的決策並進行改進。

可靠性很重要

組件的質量保證須要努力和按期審查。這個投資是值得的,由於正確的組件是精心設計的系統的基礎。這種系統易於維護和增加,其複雜性線性增長。

所以,在任何項目階段,開發都相對方便。

另外一方面,隨着系統大小的增長,你可能忘記計劃並按期校訂結構,減小耦合。僅僅是是實現功能。

可是,在系統變得足夠緊密耦合的不可避免的時刻,知足新要求變得呈指數級複雜化。你沒法控制代碼,系統的脆弱反而控制了你。錯誤修復會產生新的錯誤,代碼更新須要一系列相關的修改。

悲傷的故事要怎麼結束?你可能會拋棄當前系統並從頭開始重寫代碼,或者極可能繼續吃仙人掌。我吃了不少仙人掌,你可能也是,這不是最好的感受。

解決方案簡單但要求苛刻:編寫可靠的組件。

結論

前文所說的7個準則從不用的角度闡述了同一個思想:

可靠的組件實現一個職責,隱藏其內部結構並提供有效的 props 來控制其行爲。

單一職責和封裝是 solid 設計的基礎。(maybe你須要瞭解一下 solid 原則是什麼。)

單一職責建議建立一個只實現一個職責的組件,並有一個改變的理由。

良好封裝的組件隱藏其內部結構和實現細節,並定義 props 來控制行爲和輸出。

組合結構大的組件。只需將它們分紅較小的塊,而後使用組合進行整合,使複雜組件變得簡單。

可複用的組件是精心設計的系統的結果。儘量重複使用代碼以免重複。

網絡請求或全局變量等反作用使組件依賴於環境。經過爲相同的 prop 值返回相同的輸出來使它們變得純淨。

有意義的組件命名和表達性代碼是可讀性的關鍵。你的代碼必須易於理解和閱讀。

測試不只是一種自動檢測錯誤的方式。若是你發現某個組件難以測試,則極可能是設計不正確。

成功的應用站在可靠的組件的肩膀上,所以編寫可靠、可擴展和可維護的組件很是中重要。

在編寫React組件時,你認爲哪些原則有用?

最後謝謝各位小夥伴願意花費寶貴的時間閱讀本文,若是本文給了您一點幫助或者是啓發,請不要吝嗇你的贊和Star,您的確定是我前進的最大動力。https://github.com/YvetteLau/...

推薦關注本人公衆號

clipboard.png

相關文章
相關標籤/搜索