關於React的高階組件

什麼是高階組件?

高階組件是什麼?乍一聽,感受是個很高級的概念,可是不要被這個名詞嚇到,說簡單點 就是給已有的一個組件外面「包一層」。 咱們知道 「高階函數」 是傳入函數做爲參數, 高階組件 其實就是傳入 組件做爲參數,並返回一個新組件。html

高階組件的做用

高階組件的做用是什麼?項目開發當中,一般咱們會把一些公用的邏輯抽離出來, 而且應用到不少組件上,給組件賦予一個新的能力,這時候就須要用到它。前端

常見的應用場景

簡而言之,若是你須要給不少組件都寫相同的判斷邏輯,那麼能夠考慮提取出一個高階組件vue

實際場景: 路由權限控制

記得之前開發vue 項目的時候也遇到過相似的問題,那時用的vue-router,實現方式是在全局的router.beforeEach方法中獲取用戶信息,根據用戶的角色和權限跳轉到不一樣的頁面。參考官方文檔react

一個實現路由權限控制的高階組件:git

const LodingUserTip = () => {
  return (
    <p>
      <Icon type="loading-3-quarters" />
      正在獲取用戶信息,稍後...
    </p>
  )
}
const UnAuthoriedTip = () => {
  return (
    <p>
      <Icon type="lock" />
      <h5 style={{color: 'red'}}>抱歉,您沒有訪問該頁面的權限...</h5>
    </p>
  )
}

const authDecorator = WrappedComponent => {
// auth 組件是最終返回的高階組件
  const Auth = props => {
    const {
      match: {path, params},
      currentUser: {role_type: roleType},
      userLoading,
      history,
      location,
    } = props
    const {match} = props
    // 用戶信息正在加載
    if (userLoading) {
      return <LodingUserTip />
    }

    // 當前登陸用戶沒有訪問權限
    if (!roles.includes(roleType)) {
      return <UnAuthoriedTip />
    }

    // 若是沒有找到對應頁面,則跳轉至該權限對應的默認頁面
    const {allowedUrl, defaultUrl} = AUTH_MAP[roleType]
    if (!allowedUrl.includes(path)) {
      history.push(defaultUrl)
      return null
    }
    
    // 還能夠把額外的 props傳遞給你使用的組件
    return (
      <WrappedComponent
        params={params}
        match={match}
        location={location}
        history={history}
        roleType={roleType}
        {...props}
      />
    )
  }

  const mapStateToProps = ({
    users,
    loading: {models: {users: userLoading}},
  }) => {
    return {
      currentUser: users.currentUser,
      userLoading,
    }
  }

  return withRouter(connect(mapStateToProps)(Auth))
}

export default authDecorator
複製代碼

上面這段代碼即是一個高階組件, 一般咱們會請求後端接口返回給咱們一個當前用戶的roleType, 咱們在前端會存一個map,根據不一樣的roleType映射到的不一樣的路由, map結構以下:github

export const AUTH_MAP = {
  admin: {
    allowedUrl: [
      '/a/b',
    ],
    defaultUrl: '/f',
  },
  superAdmin: {
    allowedUrl: [
      '/a/b',
      '/c/d',
    ],
    defaultUrl: '/e',
    }
}
複製代碼

使用的時候能夠配合裝飾器,更簡單方便:vue-router

@authDecorator
class PageOneComponent extends React.Component {
...
複製代碼

高階組件中會判斷 當前的用戶角色與當前的路由是否匹配,若匹配不成功則顯示默認信息,或者跳轉到默認url(或者登錄頁)redux

遇到的問題

在使用antd的Form.create(onFieldsChange:(props, fields) => {}) 我很疑惑這裏 onFieldsChange 爲何能獲取到組件的props, 最後明白: 其實Form.create()返回了一個高階組件,會接收到全部傳給組件的props, 當fields變化的時候,antd執行onFieldsChange並把props傳進去即可以。segmentfault

但有一個要注意,若是一個組件有多個裝飾器(須要被多個高階組件包裹),須要注意順序,好比下面這個圖片,我但願在onFieldsChange 中獲取 從 redux 拿到的 dictionary信息,這樣是獲取不到的,由於 最早執行的connect, 而後才執行的 Form.create()方法,Form.create()返回的高階組件只能獲取到以後傳遞給組件的props. 後端

如圖,debugger打斷點 看到 props中 沒有 dictionary

要想拿到全部的props,請按照下圖這樣作:(最早執行的是Form.create()返回的函數,因此後面執行的函數以及傳遞的props都會被onFieldsChange接收到):

須要注意的點

  • ref 沒法傳遞的問題: 因爲你使用的組件其實被包裹了一層, 因此上層組件獲取的ref, 其實是獲取的高階組件。 具體詳情請看個人另一篇博客關於React的ref
  • 被包裹的組件自身的靜態方法默認是不會出如今高階組件中的,要想解決這個問題,咱們能夠 MyHOC.staticMethod = WrappedComponent.staticMethod 這樣明確的將靜態方法傳遞給高階組件,可是這樣太不嚴謹了,很容易漏掉。官方推薦的最佳實踐是使用 hoist-non-react-statics 第三方庫 自動copy 靜態方法
  • 高階組件(HOC)應該是無反作用的純函數,且不該該修改原組件
  • 給高階組件函數傳參除了傳入 一個組件 還想傳入 其餘參數怎麼辦? 我以爲能夠寫成 相似 connect的柯里化的形式比較好。 本文中使用到的高階組件基本都是單參數,若是想傳遞多個參數也是沒問題的,能夠看官網的例子, 例子中selectData 即是第二個參數,做爲獲取數據的回調,獲取數據的邏輯即可以從高階組件中解耦。

參考連接

hcysun.me/2018/01/05/… segmentfault.com/a/119000000… segmentfault.com/a/119000000… www.zhihu.com/question/58… hacpai.com/article/151…

相關文章
相關標籤/搜索