[譯] 理解 React 中的高階組件

理解 React 中的高階組件

一篇關於 React 高階組件的完整指南前端

在個人上一篇文章中,咱們討論了 React 中的類型檢查(type-checking);咱們瞭解瞭如何在 React 組件中指定 props 的類型,儘管是在用 JS 編寫代碼。react

在這篇文章中,繼續研究 React,咱們將學習 React 中的 HOC。android

高階組件(HOC)是什麼?

HOC 是 React 中的一種進階用法,函數(組件)接收組件做爲參數並返回一個新的組件。ios

function composeComponent(Component) {
    return class extends React.Component {
        render() {
            return <Component /> } } } 複製代碼

在這裏,函數 composeComponent 接收了一個 Component 變量做爲參數並返回一個 ES6 class 定義的組件。返回的 class 組件中使用了參數中的 Component 變量。Component 參數會是一個 React 組件,它將被返回的 class 組件調用。git

例如:github

class CatComponent extends React.Component {
    render() {
        return <div>Cat Component</div>
    }
}
複製代碼

咱們有一個 CatComponent 組件,渲染結果以下:編程

Cat Component
複製代碼

咱們能夠將 CatComponet 做爲參數傳遞給 composeComponent 函數獲得另外一個組件:redux

const composedCatComponent = composeComponent(CatComponent)
複製代碼

composedCatComponent 組件也可以被渲染:後端

<composedCatComponent />
複製代碼

渲染結果以下:瀏覽器

Cat Component
複製代碼

這和 JS 中的高階函數是相似的。

高階函數

高階函數是 JS 中的一種模式,函數接收一個函數做爲參數並返回另外一個函數做爲結果。由於 JS 自己的語法特性使得這是可行的。這意味着如下類型的數據:

  • objects
  • arrays
  • strings
  • numbers
  • boolean
  • functions

均可以做爲參數傳遞給函數,也能夠從函數中返回。

function mul(x) {
    return (y) => {
        return x * y
    }
}
const mulTwo = mul(2)

mulTwo(2) // 4
mulTwo(3) // 6
mulTwo(4) // 8
mulTwo(5) // 10
複製代碼

mul 函數返回了一個函數,該函數在閉包中捕獲變量 x 的值。如今,返回的函數可使用這個 xmul 如今就是一個高階函數,由於它返回了一個函數。這意味着咱們能夠調用它經過傳遞不一樣的參數來構造其它更具體的函數。

咱們能夠用它來建立一個函數,返回參數的 3 倍:

function mul(x) {
    return (y) => {
        return x * y
    }
}
const triple = mul(3)

triple(2) // 6
triple(3) // 9
triple(4) // 12
triple(5) // 15
複製代碼

**那高階函數和高階組件有什麼好處呢?**當咱們發現本身一遍又一遍地重複相同的邏輯時。咱們須要找到一種方法把相同的邏輯封裝在一塊兒,而後從那裏調用它。高階函數就提供了一個咱們能夠用來實現它的模式。

從上面的例子中,若是在咱們的程序中須要屢次乘以 3,咱們就能夠建立一個函數,返回另外一個乘以參數 3 倍的函數,因此每當咱們須要進行 3 倍乘法時,咱們能夠簡單地調用經過傳遞參數 3 得到的 triple 函數。

使用高階組件(HOC)

因此,在 React 中使用高階組件又有什麼好處呢?

一樣,咱們在 React 項目的編程過程當中,也可能會發現本身一次又一次地重複相同的邏輯。

例如,咱們有一個應用程序是用來查看和編輯文檔的。咱們但願對應用程序的用戶進行身份驗證,這樣只有通過身份驗證的用戶才能訪問主頁、編輯文檔、查看文檔或刪除文檔。咱們的路由是這樣設計的:

<Route path="/" component={App}>
    <Route path="/dashboard" component={Documents}/>
    <Route path="document/:id/view" component={ViewDocument} />
    <Route path="documents/:id/delete" component={DelDocument} />
    <Route path="documents/:id/edit" component={EditDocument}/>
</Route>
複製代碼

咱們必須在 Documents 組件中進行驗證,這樣只有經過驗證的用戶才能訪問它。以下:

class Doucments extends React.Component {
    componentwillMount() {
        if(!this.props.isAuth){
            this.context.router.push("/")
        }
    }
    componentWillUpdate(nextProps) {
        if(!nextProps.isAuth) {
            this.context.router.push("/")            
        }
    }
    render() {
        return <div>Documents Paegs!!!</div>
    }
}

function mapstateToProps(state) {
    isAuth: state.auth
}
export default connect(mapStateToProps)(Documents)
複製代碼

state.auth 保存了用戶的驗證狀態。若是用戶沒有經過驗證,它的值是 false,若是經過驗證,它將是 trueconnect 函數將 state.auth 映射到組件 props 對象的 isAuth。而後,當組件將要掛載到 DOM 上時,componentWillMount 被觸發,所以咱們檢查 propsisAuth 是否爲真。若是爲真,組件會繼續渲染;不然,則該方法會將路由切換到 「/」 路徑,從而使得咱們的瀏覽器在渲染 Documents 組件時被重定向到了首頁,進而有效地阻止了未經過驗證的用戶對它的訪問。

當組件在初始渲染以後再次渲染時,咱們只在 componentWillUpdate 中執行相同的操做,以檢查用戶是否仍然具備受權,若是沒有,則一樣重定向到首頁。

而後,咱們能夠在 ViewDocument 組件中作一樣的處理:

class ViewDoucment extends React.Component {
    componentwillMount() {
        if(!this.props.isAuth){
            this.context.router.push("/")
        }
    }
    componentWillUpdate(nextProps) {
        if(!nextProps.isAuth) {
            this.context.router.push("/")            
        }
    }
    render() {
        return <div>View Document Page!!!</div>
    }
}

function mapstateToProps(state) {
    isAuth: state.auth
}
export default connect(mapStateToProps)(ViewDocument)
複製代碼

EditDocument 組件中:

class EditDocument extends React.Component {
    componentwillMount() {
        if(!this.props.isAuth){
            this.context.router.push("/")
        }
    }
    componentWillUpdate(nextProps) {
        if(!nextProps.isAuth) {
            this.context.router.push("/")            
        }
    }
    render() {
        return <div>Edit Document Page!!!</div>
    }
}

function mapstateToProps(state) {
    isAuth: state.auth
}
export default connect(mapStateToProps)(EditDocument)
複製代碼

DelDocument 組件中:

class DelDocument extends React.Component {
    componentwillMount() {
        if(!this.props.isAuth){
            this.context.router.push("/")
        }
    }
    componentWillUpdate(nextProps) {
        if(!nextProps.isAuth) {
            this.context.router.push("/")            
        }
    }
    render() {
        return <div>Delete Document Page!!!</div>
    }
}

function mapstateToProps(state) {
    isAuth: state.auth
}
export default connect(mapStateToProps)(DelDocument)
複製代碼

不一樣的頁面具備不一樣的功能,但它們的大部分實現邏輯是相同的。

在每一個組件中的操做:

  • 經過 react-redux 鏈接到 store 的 state。
  • state.auth 映射到組件的 props.isAuth 屬性。
  • componentWillMount 中檢查用戶是否受權。
  • componentWillUpdate 中檢查用戶是否受權。

假設咱們的項目擴展了更多其餘的組件,咱們發現咱們在每一個組件中都實現了上述的操做。這確定會很無聊的。

咱們須要找到只在一個地方實現邏輯的方式。最好的辦法就是使用高階組件(HOC)。

爲此,咱們將全部的邏輯封裝到一個函數中,該函數將返回一個組件:

function requireAuthentication(composedComponent) {
    class Authentication extends React.Component {
        componentwillMount() {
            if(!this.props.isAuth){
                this.context.router.push("/")
            }
        }
        componentWillUpdate(nextProps) {
            if(!nextProps.isAuth) {
                this.context.router.push("/")            
            }
        }
        render() {
            <ComposedComponent />
        }
    }
    function mapstateToProps(state) {
        isAuth: state.auth
    }
    return connect(mapStateToProps)(Authentication)
}
複製代碼

能夠看到,咱們將全部相同的邏輯都封裝到了 Authentication 組件中。requireAuthentication 函數將把 Authentication 組件鏈接到 store 並返回它。而後,Authentication 組件將渲染經過 composedCompoennt 參數傳入的組件。

咱們的路由如今這樣的:

<Route path="/" component={App}>
    <Route path="/dashboard" component={requireAuthentication(Documents)}/>
    <Route path="document/:id/view" component={requireAuthentication(ViewDocument)} />
    <Route path="documents/:id/delete" component={requireAuthentication(DelDocument)} />
    <Route path="documents/:id/edit" component={requireAuthentication(EditDocument)}/>
</Route>
複製代碼

所以,不管咱們的應用程序未來會有多少條路由,咱們都不用考慮向組件添加身份驗證的邏輯,咱們只需調用 requireAuthentication 函數,並將組件做爲參數傳遞給它。

使用高階組件(HOC)會有不少的好處。當你發現你在重複相同的邏輯時,你須要把相同的邏輯封裝到一塊兒,並使用高階組件(HOC)。

總結

高階函數:

  • 返回一個函數;
  • 可以解決 DRY [1] 問題。

React 高階組件:

  • 接收一個組件做爲參數;
  • 返回另外一個組件;
  • 返回的組件將渲染經過參數傳遞的組件;
  • 可以解決 DRY [2] 問題。

若是你對這篇文章有任何的疑問,或者你認爲有任何須要添加、更正或刪除的內容,請給我評論或者發郵件。

謝謝!

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索