可靠React組件設計的7個準則之封裝

翻譯:劉小夕javascript

原文連接:dmitripavlutin.com/7-architect…java

原文的篇幅很是長,不過內容太過於吸引我,仍是忍不住要翻譯出來。此篇文章對編寫可重用和可維護的React組件很是有幫助。但由於篇幅實在太長,我對文章進行了分割,本篇文章重點闡述 封裝。因本人水平有限,文中部分翻譯可能不夠準確,若是您有更好的想法,歡迎在評論區指出。react

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

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

封裝

一個封裝組件提供 props 控制其行爲而不是暴露其內部結構。數組

耦合是決定組件之間依賴程度的系統特性。根據組件的依賴程度,可區分兩種耦合類型:數據結構

  • 當應用程序組件對其餘組件知之甚少或一無所知時,就會發生鬆耦合。app

  • 當應用程序組件知道彼此的許多詳細信息時,就會發生緊耦合。異步

鬆耦合是咱們設計應用結構和組件之間關係的目標。函數

鬆耦合應用(封裝組件)

鬆耦合會帶來如下好處:

  • 能夠在不影響應用其它部分的狀況下對某一塊進行修改。、
  • 任何組件均可以替換爲另外一種實現
  • 在整個應用程序中實現組件複用,從而避免重複代碼
  • 獨立組件更容易測試,增長了測試覆蓋率

相反,緊耦合的系統會失去上面描述的好處。主要缺點是很難修改高度依賴於其餘組件的組件。即便是一處修改,也可能致使一系列的依賴組件須要修改。

緊耦合應用(組件無封裝)

封裝信息隱藏 是如何設計組件的基本原則,也是鬆耦合的關鍵。

信息隱藏

封裝良好的組件隱藏其內部結構,並提供一組屬性來控制其行爲。

隱藏內部結構是必要的。其餘組件不必知道或也不依賴組件的內部結構或實現細節。

React 組件多是函數組件或類組件、定義實例方法、設置 ref、擁有 state 或使用生命週期方法。這些實現細節被封裝在組件內部,其餘組件不該該知道這些細節。

隱藏內部結構的組件彼此之間的依賴性較小,而下降依賴度會帶來鬆耦合的好處。

通訊

細節隱藏是隔離組件的關鍵。此時,你須要一種組件通訊的方法:propsporps 是組件的輸入。

建議 prop 的類型爲基本數據(例如,stringnumberboolean):

<Message text="Hello world!" modal={false} />;
複製代碼

必要時,使用複雜的數據結構,如對象或數組:

<MoviesList items={['Batman Begins', 'Blade Runner']} />
複製代碼

prop 能夠是一個事件處理函數和異步函數:

<input type="text" onChange={handleChange} />
複製代碼

prop 甚至能夠是一個組件構造函數。組件能夠處理其餘組件的實例化:

function If({ component: Component, condition }) {
    return condition ? <Component /> : null;
}
<If condition={false} component={LazyComponent} />  
複製代碼

爲了不破壞封裝,請注意經過 props 傳遞的內容。給子組件設置 props 的父組件不該該暴露其內部結構的任何細節。例如,使用 props 傳輸整個組件實例或 refs 都是一個很差的作法。

訪問全局變量一樣也會對封裝產生負面影響。

案例研究:封裝修復

組件的實例和狀態對象是封裝在組件內部的實現細節。所以,將狀態管理的父組件實例傳遞給子組件會破壞封裝。

咱們來研究一下這種狀況。

一個簡單的應用程序顯示一個數字和兩個按鈕。第一個按鈕增長數值,第二個按鈕減小數值:

class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = { number: 0 };
    }

    render() {
        return (
            <div className="app"> <span className="number">{this.state.number}</span> <Controls parent={this} /> </div> ); } } class Controls extends React.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 })); } } ReactDOM.render(<App />, document.getElementById('root')); 複製代碼

<Controls> 負責渲染按鈕,併爲其設置事件處理函數,當用戶點擊按鈕時,父組件的狀態將會被更新:number 加1或者減1((updateNumber()方法`)

// 問題: 使用父組件的內部結構
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
        }));
    }
}
複製代碼

當前的實現有什麼問題?

  • 第一個問題是: <App> 的封裝被破壞,由於它的內部結構在應用中傳遞。<App> 錯誤地容許 <Controls> 直接去修改其 state

  • 第二個問題是: 子組件 Controls 知道了太多父組件 <App> 的內部細節,它能夠訪問父組件的實例,知道父組件是一個有狀態組件,知道父組件的 state 對象的細節(知道 number 是父組件 state 的屬性),而且知道怎麼去更新父組件的 state.

這樣就會致使: <Controls> 將很難測試和重用。對 <App> 結構的細微修改會致使須要對 <Controls> 進行修改(對於更大的應用程序,也會致使相似耦合的組件須要修改)。

解決方案是設計一個方便的通訊接口,考慮到鬆耦合和封裝。讓咱們改進兩個組件的結構和屬性,以便恢復封裝。

只有組件自己應該知道它的狀態結構。<App> 的狀態管理應該從 <Controls>updateNumber()方法)移到正確的位置:即 <App> 組件中。

<App> 被修改成 <Controls> 設置屬性 onIncreaseonDecrease。這些是更新 <App> 狀態的回調函數:

// 解決: 恢復封裝
class App extends Component {
    constructor(props) {
        super(props);
        this.state = { number: 0 };
    }

    render() {
        return (
            <div className="app"> <span className="number">{this.state.number}</span> <Controls onIncrease={() => this.updateNumber(+1)} onDecrease={() => this.updateNumber(-1)} /> </div> ); } updateNumber(toAdd) { this.setState(prevState => ({ number: prevState.number + toAdd })); } } 複製代碼

如今,<Controls> 接收用於增長和減小數值的回調,注意解耦和封裝恢復時:<Controls> 再也不須要訪問父組件實例。也不會直接去修改父組件的狀態。

並且,<Controls> 被修改成了一個函數式組件:

// 解決方案: 使用回調函數去更新父組件的狀態
function Controls({ onIncrease, onDecrease }) {
    return (
        <div className="controls"> <button onClick={onIncrease}>Increase</button> <button onClick={onDecrease}>Decrease</button> </div>
    );
}
複製代碼

<App> 組件的封裝已經恢復,狀態由其自己管理,也應該如此。

此外,<Controls> 不在依賴 <App> 的實現細節,onIncreaseonDecrease 在按鈕被點擊的時候調用,<Controls> 不知道(也不該該知道)這些回調的內部實現。

<Controls> 組件的可重用性和可測試性顯著增長。

<Controls> 的複用變得很容易,由於它除了須要回調,沒有其它依賴。測試也變得簡單,只需驗證單擊按鈕時,回調是否執行。

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

關注公衆號,加入技術交流羣

相關文章
相關標籤/搜索